diff --git a/api/v1alpha1/jwt_types.go b/api/v1alpha1/jwt_types.go index 6573005fa10..4948669a292 100644 --- a/api/v1alpha1/jwt_types.go +++ b/api/v1alpha1/jwt_types.go @@ -19,6 +19,7 @@ type JWT struct { } // JWTProvider defines how a JSON Web Token (JWT) can be verified. +// +kubebuilder:validation:XValidation:rule="(has(self.recomputeRoute) && self.recomputeRoute) ? size(self.claimToHeaders) > 0 : true", message="claimToHeaders must be specified if recomputeRoute is enabled" type JWTProvider struct { // Name defines a unique name for the JWT provider. A name can have a variety of forms, // including RFC1123 subdomains, RFC 1123 labels, or RFC 1035 labels. @@ -57,7 +58,8 @@ type JWTProvider struct { // RecomputeRoute clears the route cache and recalculates the routing decision. // This field must be enabled if the headers generated from the claim are used for - // route matching decisions. + // route matching decisions. If the recomputation selects a new route, features targeting + // the new matched route will be applied. // // +optional RecomputeRoute *bool `json:"recomputeRoute,omitempty"` diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index bd900226038..1ed6395b781 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -465,7 +465,8 @@ spec: description: RecomputeRoute clears the route cache and recalculates the routing decision. This field must be enabled if the headers generated from the claim are used for route matching - decisions. + decisions. If the recomputation selects a new route, features + targeting the new matched route will be applied. type: boolean remoteJWKS: description: RemoteJWKS defines how to fetch and cache JSON @@ -485,6 +486,11 @@ spec: - name - remoteJWKS type: object + x-kubernetes-validations: + - message: claimToHeaders must be specified if recomputeRoute + is enabled + rule: '(has(self.recomputeRoute) && self.recomputeRoute) ? + size(self.claimToHeaders) > 0 : true' maxItems: 4 minItems: 1 type: array diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 90ea6aa3da7..fbda31d8a26 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1375,7 +1375,7 @@ _Appears in:_ | `audiences` | _string array_ | false | Audiences is a list of JWT audiences allowed access. For additional details, see https://tools.ietf.org/html/rfc7519#section-4.1.3. If not provided, JWT audiences are not checked. | | `remoteJWKS` | _[RemoteJWKS](#remotejwks)_ | true | RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote HTTP/HTTPS endpoint. | | `claimToHeaders` | _[ClaimToHeader](#claimtoheader) array_ | false | ClaimToHeaders is a list of JWT claims that must be extracted into HTTP request headers For examples, following config: The claim must be of type; string, int, double, bool. Array type claims are not supported | -| `recomputeRoute` | _boolean_ | false | RecomputeRoute clears the route cache and recalculates the routing decision. This field must be enabled if the headers generated from the claim are used for route matching decisions. | +| `recomputeRoute` | _boolean_ | false | RecomputeRoute clears the route cache and recalculates the routing decision. This field must be enabled if the headers generated from the claim are used for route matching decisions. If the recomputation selects a new route, features targeting the new matched route will be applied. | | `extractFrom` | _[JWTExtractor](#jwtextractor)_ | false | ExtractFrom defines different ways to extract the JWT token from HTTP request. If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema or access_token from query parameters. | diff --git a/test/cel-validation/securitypolicy_test.go b/test/cel-validation/securitypolicy_test.go index a6107e6531b..8d7d830eee6 100644 --- a/test/cel-validation/securitypolicy_test.go +++ b/test/cel-validation/securitypolicy_test.go @@ -548,6 +548,121 @@ func TestSecurityPolicyTarget(t *testing.T) { "spec.extAuth: Invalid value: \"object\": kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported", }, }, + // JWT + { + desc: "valid jwt", + mutate: func(sp *egv1a1.SecurityPolicy) { + sp.Spec = egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + Name: "example", + RemoteJWKS: egv1a1.RemoteJWKS{ + URI: "https://example.com/jwt/jwks.json", + }, + }, + }, + }, + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "jwt with claim to headers", + mutate: func(sp *egv1a1.SecurityPolicy) { + sp.Spec = egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + Name: "example", + RemoteJWKS: egv1a1.RemoteJWKS{ + URI: "https://example.com/jwt/jwks.json", + }, + ClaimToHeaders: []egv1a1.ClaimToHeader{ + { + Claim: "name", + Header: "x-claim-name", + }, + }, + }, + }, + }, + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "jwt with recomputeRoute", + mutate: func(sp *egv1a1.SecurityPolicy) { + sp.Spec = egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + Name: "example", + RemoteJWKS: egv1a1.RemoteJWKS{ + URI: "https://example.com/jwt/jwks.json", + }, + RecomputeRoute: ptr.To(true), + }, + }, + }, + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + } + }, + wantErrors: []string{"Invalid value: \"object\": no such key: claimToHeaders evaluating rule: claimToHeaders must be specified if recomputeRoute is enabled"}, + }, + { + desc: "jwt with claim to headers and recomputeRoute", + mutate: func(sp *egv1a1.SecurityPolicy) { + sp.Spec = egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + Name: "example", + RemoteJWKS: egv1a1.RemoteJWKS{ + URI: "https://example.com/jwt/jwks.json", + }, + ClaimToHeaders: []egv1a1.ClaimToHeader{ + { + Claim: "name", + Header: "x-claim-name", + }, + }, + RecomputeRoute: ptr.To(true), + }, + }, + }, + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + } + }, + wantErrors: []string{}, + }, } for _, tc := range cases {