Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update VirtualServer template to generate an internal jwt auth location per policy applied (#3798) #3855

Merged
merged 1 commit into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/content/configuration/policy-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,14 @@ jwt:
|Field | Description | Type | Required |
| ---| ---| ---| --- |
|``jwksURI`` | The remote URI where the request will be sent to retrieve JSON Web Key set| ``string`` | Yes |
|``keyCache`` | Enables the caching of keys that are obtained from the ``jwksURI`` and sets a valid time for expiration | ``string`` | Yes |
|``keyCache`` | Enables in-memory caching of JWKS (JSON Web Key Sets) that are obtained from the ``jwksURI`` and sets a valid time for expiration. | ``string`` | Yes |
|``realm`` | The realm of the JWT. | ``string`` | Yes |
|``token`` | The token specifies a variable that contains the JSON Web Token. By default the JWT is passed in the ``Authorization`` header as a Bearer Token. JWT may be also passed as a cookie or a part of a query string, for example: ``$cookie_auth_token``. Accepted variables are ``$http_``, ``$arg_``, ``$cookie_``. | ``string`` | No |
{{% /table %}}

> Note: Content caching is enabled by default for each JWT policy with a default time of 12 hours.
> This is done to ensure to improve resiliency by allowing the JWKS (JSON Web Key Set) to be retrieved from the cache even when it has expired.

#### JWT Merging Behavior

This behavior is similar to using a local Kubernetes secret where a VirtualServer/VirtualServerRoute can reference multiple JWT policies. However, only one can be applied: every subsequent reference will be ignored. For example, here we reference two policies:
Expand Down
3 changes: 3 additions & 0 deletions internal/configs/version2/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Server struct {
LimitReqOptions LimitReqOptions
LimitReqs []LimitReq
JWTAuth *JWTAuth
JWTAuthList map[string]*JWTAuth
JWKSAuthEnabled bool
BasicAuth *BasicAuth
IngressMTLS *IngressMTLS
EgressMTLS *EgressMTLS
Expand Down Expand Up @@ -356,6 +358,7 @@ func (rl LimitReqOptions) String() string {

// JWTAuth holds JWT authentication configuration.
type JWTAuth struct {
Key string
Secret string
Realm string
Token string
Expand Down
39 changes: 11 additions & 28 deletions internal/configs/version2/nginx-plus.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,9 @@ match {{ $m.Name }} {
{{ end }}

{{ $s := .Server }}
{{ with $s.JWTAuth }}
{{ if .KeyCache }}proxy_cache_path /var/cache/nginx/jwks_uri levels=1 keys_zone=jwks_uri:1m max_size=10m; {{ end }}
{{ end }}

{{ range $l := $s.Locations }}
{{ with $l.JWTAuth }}
{{ if .KeyCache }}proxy_cache_path /var/cache/nginx{{ $l.Path }}_jwks_uri levels=1 keys_zone={{ $l.Path }}_jwks_uri:1m max_size=10m; {{ end }}
{{ end }}
{{ with $s.JWKSAuthEnabled }}
proxy_cache_path /var/cache/nginx/jwks_uri_{{$s.VSName}} levels=1 keys_zone=jwks_uri_{{$s.VSName}}:1m max_size=10m;
{{ end }}

server {
Expand Down Expand Up @@ -190,24 +185,27 @@ server {
{{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }}
{{ if .JwksURI.JwksHost }}
{{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }}
auth_jwt_key_request /_jwks_uri_server;
auth_jwt_key_request /_jwks_uri_server_{{ .Key }};

{{ end }}
{{ end }}

location = /_jwks_uri_server {
{{ range $index, $element := $s.JWTAuthList }}
location = /_jwks_uri_server_{{ .Key }} {
internal;
proxy_method GET;
proxy_set_header Content-Length "";
{{ if .KeyCache }}
proxy_cache jwks_uri;
proxy_cache jwks_uri_{{ $s.VSName }};
proxy_cache_valid 200 12h;
{{ end }}
{{ with $s.JWTAuth.JwksURI }}
{{ with .JwksURI }}
proxy_set_header Host {{ .JwksHost }};
set $idp_backend {{ .JwksHost }};
proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }};
{{ end }}
}

{{ end }}
{{ end }}

{{ with $s.BasicAuth }}
Expand Down Expand Up @@ -391,22 +389,7 @@ server {
{{ if .Secret}}auth_jwt_key_file {{ .Secret }};{{ end }}
{{ if .JwksURI.JwksHost }}
{{ if .KeyCache }}auth_jwt_key_cache {{ .KeyCache }};{{ end }}
auth_jwt_key_request {{ $l.Path }}_jwks_uri;

location = {{ $l.Path }}_jwks_uri {
internal;
proxy_method GET;
proxy_set_header Content-Length "";
{{ if .KeyCache }}
proxy_cache {{ $l.Path }}_jwks_uri;
proxy_cache_valid 200 12h;
{{ end }}
{{ with $l.JWTAuth.JwksURI }}
proxy_set_header Host {{ .JwksHost }};
set $idp_backend {{ .JwksHost }};
proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }};
{{ end }}
}
auth_jwt_key_request /_jwks_uri_server_{{ .Key }};
{{ end }}
{{ end }}

Expand Down
36 changes: 36 additions & 0 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
}
policiesCfg := vsc.generatePolicies(ownerDetails, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, specContext, policyOpts)

if policiesCfg.JWKSAuthEnabled {
jwtAuthKey := policiesCfg.JWTAuth.Key
policiesCfg.JWTAuthList = make(map[string]*version2.JWTAuth)
policiesCfg.JWTAuthList[jwtAuthKey] = policiesCfg.JWTAuth
}

dosCfg := generateDosCfg(dosResources[""])

// enabledInternalRoutes controls if a virtual server is configured as an internal route.
Expand Down Expand Up @@ -469,6 +475,18 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
if policiesCfg.OIDC {
routePoliciesCfg.OIDC = policiesCfg.OIDC
}
if routePoliciesCfg.JWKSAuthEnabled {
policiesCfg.JWKSAuthEnabled = routePoliciesCfg.JWKSAuthEnabled

if policiesCfg.JWTAuthList == nil {
policiesCfg.JWTAuthList = make(map[string]*version2.JWTAuth)
}

jwtAuthKey := routePoliciesCfg.JWTAuth.Key
if _, exists := policiesCfg.JWTAuthList[jwtAuthKey]; !exists {
policiesCfg.JWTAuthList[jwtAuthKey] = routePoliciesCfg.JWTAuth
}
}
limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...)

dosRouteCfg := generateDosCfg(dosResources[r.Path])
Expand Down Expand Up @@ -579,6 +597,18 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
if policiesCfg.OIDC {
routePoliciesCfg.OIDC = policiesCfg.OIDC
}
if routePoliciesCfg.JWKSAuthEnabled {
policiesCfg.JWKSAuthEnabled = routePoliciesCfg.JWKSAuthEnabled

if policiesCfg.JWTAuthList == nil {
policiesCfg.JWTAuthList = make(map[string]*version2.JWTAuth)
}

jwtAuthKey := routePoliciesCfg.JWTAuth.Key
if _, exists := policiesCfg.JWTAuthList[jwtAuthKey]; !exists {
policiesCfg.JWTAuthList[jwtAuthKey] = routePoliciesCfg.JWTAuth
}
}
limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...)

dosRouteCfg := generateDosCfg(dosResources[r.Path])
Expand Down Expand Up @@ -675,6 +705,8 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
LimitReqs: policiesCfg.LimitReqs,
JWTAuth: policiesCfg.JWTAuth,
BasicAuth: policiesCfg.BasicAuth,
JWTAuthList: policiesCfg.JWTAuthList,
JWKSAuthEnabled: policiesCfg.JWKSAuthEnabled,
IngressMTLS: policiesCfg.IngressMTLS,
EgressMTLS: policiesCfg.EgressMTLS,
OIDC: vsc.oidcPolCfg.oidc,
Expand All @@ -699,6 +731,8 @@ type policiesCfg struct {
LimitReqZones []version2.LimitReqZone
LimitReqs []version2.LimitReq
JWTAuth *version2.JWTAuth
JWTAuthList map[string]*version2.JWTAuth
JWKSAuthEnabled bool
BasicAuth *version2.BasicAuth
IngressMTLS *version2.IngressMTLS
EgressMTLS *version2.EgressMTLS
Expand Down Expand Up @@ -858,11 +892,13 @@ func (p *policiesCfg) addJWTAuthConfig(
}

p.JWTAuth = &version2.JWTAuth{
Key: polKey,
JwksURI: *JwksURI,
Realm: jwtAuth.Realm,
Token: jwtAuth.Token,
KeyCache: jwtAuth.KeyCache,
}
p.JWKSAuthEnabled = true
return res
}
return res
Expand Down