Skip to content

Commit

Permalink
Update VirtualServer template to generate an internal jwt auth locati…
Browse files Browse the repository at this point in the history
…on per policy applied (#3798) (#3855)

Co-authored-by: Venktesh <ve.patel@f5.com>
(cherry picked from commit dc4c543)

Co-authored-by: Shaun <s.odonovan@f5.com>
  • Loading branch information
lucacome and shaun-nx committed May 4, 2023
1 parent 89a6898 commit a344511
Show file tree
Hide file tree
Showing 18 changed files with 949 additions and 44 deletions.
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

0 comments on commit a344511

Please sign in to comment.