diff --git a/kubernetes/customresourcedefinitions.gen.yaml b/kubernetes/customresourcedefinitions.gen.yaml index dff94e1b3a..692e434942 100644 --- a/kubernetes/customresourcedefinitions.gen.yaml +++ b/kubernetes/customresourcedefinitions.gen.yaml @@ -16174,6 +16174,14 @@ spec: description: This field specifies the header name to output a successfully verified JWT payload to the backend. type: string + spaceDelimitedClaims: + description: List of JWT claim names that should be treated + as space-delimited strings. + items: + minLength: 1 + type: string + maxItems: 64 + type: array timeout: description: The maximum amount of time that the resolver, determined by the PILOT_JWT_ENABLE_REMOTE_JWKS environment variable, @@ -16455,6 +16463,14 @@ spec: description: This field specifies the header name to output a successfully verified JWT payload to the backend. type: string + spaceDelimitedClaims: + description: List of JWT claim names that should be treated + as space-delimited strings. + items: + minLength: 1 + type: string + maxItems: 64 + type: array timeout: description: The maximum amount of time that the resolver, determined by the PILOT_JWT_ENABLE_REMOTE_JWKS environment variable, diff --git a/releasenotes/notes/space-delimited-jwt-claims.yaml b/releasenotes/notes/space-delimited-jwt-claims.yaml new file mode 100644 index 0000000000..cd4634dbb2 --- /dev/null +++ b/releasenotes/notes/space-delimited-jwt-claims.yaml @@ -0,0 +1,13 @@ +apiVersion: release-notes/v2 +kind: feature +area: security +issue: + - https://github.com/istio/istio/issues/56873 + +releaseNotes: + - | + **Added** `spaceDelimitedClaims` field in `RequestAuthentication` under `spec.jwtRules.` to configure custom JWT claims + that should be treated as space-delimited strings. + This allows authorization policies to match individual values within space-separated claim strings, + extending beyond the default `scope` and `permission` claims. + This addresses compatibility issues when upgrading from older Istio versions with custom space-delimited JWT claim fields. \ No newline at end of file diff --git a/security/v1/request_authentication_alias.gen.go b/security/v1/request_authentication_alias.gen.go index 9b1dc361d3..e1d3b7e01b 100644 --- a/security/v1/request_authentication_alias.gen.go +++ b/security/v1/request_authentication_alias.gen.go @@ -53,6 +53,21 @@ type RequestAuthentication = v1beta1.RequestAuthentication // fromHeaders: // - "x-goog-iap-jwt-assertion" // ``` +// +// This example shows how to configure custom claims to be treated as space-delimited strings. +// This is useful when JWT tokens contain custom claims with multiple space-separated values +// that should be available for individual matching in authorization policies. +// +// ```yaml +// issuer: https://example.com +// spaceDelimitedClaims: +// - "custom_scope" +// - "provider.login.scope" +// - "roles" +// ``` +// +// With this configuration, a JWT containing `"custom_scope": "read write admin"` will allow +// authorization policies to match against individual values like "read", "write", or "admin". // +kubebuilder:validation:XValidation:message="only one of jwks or jwksUri can be set",rule="oneof(self.jwksUri, self.jwks_uri, self.jwks)" type JWTRule = v1beta1.JWTRule diff --git a/security/v1beta1/request_authentication.pb.go b/security/v1beta1/request_authentication.pb.go index 9ecb99a285..7168152892 100644 --- a/security/v1beta1/request_authentication.pb.go +++ b/security/v1beta1/request_authentication.pb.go @@ -395,6 +395,21 @@ func (x *RequestAuthentication) GetJwtRules() []*JWTRule { // fromHeaders: // - "x-goog-iap-jwt-assertion" // ``` +// +// This example shows how to configure custom claims to be treated as space-delimited strings. +// This is useful when JWT tokens contain custom claims with multiple space-separated values +// that should be available for individual matching in authorization policies. +// +// ```yaml +// issuer: https://example.com +// spaceDelimitedClaims: +// - "custom_scope" +// - "provider.login.scope" +// - "roles" +// ``` +// +// With this configuration, a JWT containing `"custom_scope": "read write admin"` will allow +// authorization policies to match against individual values like "read", "write", or "admin". // +kubebuilder:validation:XValidation:message="only one of jwks or jwksUri can be set",rule="oneof(self.jwksUri, self.jwks_uri, self.jwks)" type JWTRule struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -512,9 +527,30 @@ type JWTRule struct { OutputClaimToHeaders []*ClaimToHeader `protobuf:"bytes,11,rep,name=output_claim_to_headers,json=outputClaimToHeaders,proto3" json:"output_claim_to_headers,omitempty"` // [TODO:Update the status whenever this feature is promoted.] // The maximum amount of time that the resolver, determined by the PILOT_JWT_ENABLE_REMOTE_JWKS environment variable, // will spend waiting for the JWKS to be fetched. Default is 5s. - Timeout *duration.Duration `protobuf:"bytes,13,opt,name=timeout,proto3" json:"timeout,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Timeout *duration.Duration `protobuf:"bytes,13,opt,name=timeout,proto3" json:"timeout,omitempty"` + // List of JWT claim names that should be treated as space-delimited strings. + // These claims will be split on whitespace and each individual value will be available + // for matching in authorization policies. This extends the default behavior that only + // treats 'scope' and 'permission' claims as space-delimited. + // + // Example usage for custom claims: + // ```yaml + // spaceDelimitedClaims: + // - "custom_scope" + // - "provider.login.scope" + // - "roles" + // ``` + // + // This allows authorization policies to match individual values within space-separated + // claim strings, maintaining compatibility with existing JWT token formats. + // + // Note: The default claims 'scope' and 'permission' are always treated as space-delimited + // regardless of this setting. + // +protoc-gen-crd:list-value-validation:MinLength=1 + // +kubebuilder:validation:MaxItems=64 + SpaceDelimitedClaims []string `protobuf:"bytes,14,rep,name=space_delimited_claims,json=spaceDelimitedClaims,proto3" json:"space_delimited_claims,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *JWTRule) Reset() { @@ -624,6 +660,13 @@ func (x *JWTRule) GetTimeout() *duration.Duration { return nil } +func (x *JWTRule) GetSpaceDelimitedClaims() []string { + if x != nil { + return x.SpaceDelimitedClaims + } + return nil +} + // This message specifies a header location to extract JWT token. type JWTHeader struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -752,7 +795,7 @@ const file_security_v1beta1_request_authentication_proto_rawDesc = "" + "\n" + "targetRefs\x18\x04 \x03(\v2).istio.type.v1beta1.PolicyTargetReferenceR\n" + "targetRefs\x12<\n" + - "\tjwt_rules\x18\x02 \x03(\v2\x1f.istio.security.v1beta1.JWTRuleR\bjwtRules\"\xfa\x03\n" + + "\tjwt_rules\x18\x02 \x03(\v2\x1f.istio.security.v1beta1.JWTRuleR\bjwtRules\"\xb0\x04\n" + "\aJWTRule\x12\x16\n" + "\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x1c\n" + "\taudiences\x18\x02 \x03(\tR\taudiences\x12\x19\n" + @@ -766,7 +809,8 @@ const file_security_v1beta1_request_authentication_proto_rawDesc = "" + "\ffrom_cookies\x18\f \x03(\tR\vfromCookies\x124\n" + "\x16forward_original_token\x18\t \x01(\bR\x14forwardOriginalToken\x12\\\n" + "\x17output_claim_to_headers\x18\v \x03(\v2%.istio.security.v1beta1.ClaimToHeaderR\x14outputClaimToHeaders\x123\n" + - "\atimeout\x18\r \x01(\v2\x19.google.protobuf.DurationR\atimeout\"=\n" + + "\atimeout\x18\r \x01(\v2\x19.google.protobuf.DurationR\atimeout\x124\n" + + "\x16space_delimited_claims\x18\x0e \x03(\tR\x14spaceDelimitedClaims\"=\n" + "\tJWTHeader\x12\x18\n" + "\x04name\x18\x01 \x01(\tB\x04\xe2A\x01\x02R\x04name\x12\x16\n" + "\x06prefix\x18\x02 \x01(\tR\x06prefix\"I\n" + diff --git a/security/v1beta1/request_authentication.pb.html b/security/v1beta1/request_authentication.pb.html index d318b29737..d005066caa 100644 --- a/security/v1beta1/request_authentication.pb.html +++ b/security/v1beta1/request_authentication.pb.html @@ -283,6 +283,17 @@

JWTRule

fromHeaders: - "x-goog-iap-jwt-assertion" +

This example shows how to configure custom claims to be treated as space-delimited strings. +This is useful when JWT tokens contain custom claims with multiple space-separated values +that should be available for individual matching in authorization policies.

+
issuer: https://example.com
+spaceDelimitedClaims:
+- "custom_scope"
+- "provider.login.scope"
+- "roles"
+
+

With this configuration, a JWT containing "custom_scope": "read write admin" will allow +authorization policies to match against individual values like “read”, “write”, or “admin”.

@@ -446,6 +457,28 @@

JWTRule

The maximum amount of time that the resolver, determined by the PILOT_JWT_ENABLE_REMOTE_JWKS environment variable, will spend waiting for the JWKS to be fetched. Default is 5s.

+ + + + + diff --git a/security/v1beta1/request_authentication.proto b/security/v1beta1/request_authentication.proto index 6a9a1da3c2..6770b9c996 100644 --- a/security/v1beta1/request_authentication.proto +++ b/security/v1beta1/request_authentication.proto @@ -318,6 +318,21 @@ message RequestAuthentication { // fromHeaders: // - "x-goog-iap-jwt-assertion" // ``` +// +// This example shows how to configure custom claims to be treated as space-delimited strings. +// This is useful when JWT tokens contain custom claims with multiple space-separated values +// that should be available for individual matching in authorization policies. +// +// ```yaml +// issuer: https://example.com +// spaceDelimitedClaims: +// - "custom_scope" +// - "provider.login.scope" +// - "roles" +// ``` +// +// With this configuration, a JWT containing `"custom_scope": "read write admin"` will allow +// authorization policies to match against individual values like "read", "write", or "admin". // +kubebuilder:validation:XValidation:message="only one of jwks or jwksUri can be set",rule="oneof(self.jwksUri, self.jwks_uri, self.jwks)" message JWTRule { // Identifies the issuer that issued the JWT. See @@ -450,8 +465,30 @@ message JWTRule { // will spend waiting for the JWKS to be fetched. Default is 5s. google.protobuf.Duration timeout = 13; + // List of JWT claim names that should be treated as space-delimited strings. + // These claims will be split on whitespace and each individual value will be available + // for matching in authorization policies. This extends the default behavior that only + // treats 'scope' and 'permission' claims as space-delimited. + // + // Example usage for custom claims: + // ```yaml + // spaceDelimitedClaims: + // - "custom_scope" + // - "provider.login.scope" + // - "roles" + // ``` + // + // This allows authorization policies to match individual values within space-separated + // claim strings, maintaining compatibility with existing JWT token formats. + // + // Note: The default claims 'scope' and 'permission' are always treated as space-delimited + // regardless of this setting. + // +protoc-gen-crd:list-value-validation:MinLength=1 + // +kubebuilder:validation:MaxItems=64 + repeated string space_delimited_claims = 14; + // $hide_from_docs - // Next available field number: 14 + // Next available field number: 15 } // This message specifies a header location to extract JWT token. diff --git a/tests/testdata/reqauth-invalid.yaml b/tests/testdata/reqauth-invalid.yaml index f45a96e9cc..e0daed3a24 100644 --- a/tests/testdata/reqauth-invalid.yaml +++ b/tests/testdata/reqauth-invalid.yaml @@ -210,3 +210,14 @@ spec: - issuer: example timeout: "apple" --- +_err: 'spaceDelimitedClaims[0] in body should be at least 1 chars long' +apiVersion: security.istio.io/v1 +kind: RequestAuthentication +metadata: + name: invalid-space-delimited-claims +spec: + jwtRules: + - issuer: example + spaceDelimitedClaims: + - "" +--- diff --git a/tests/testdata/reqauth-valid.yaml b/tests/testdata/reqauth-valid.yaml index 14bd7571eb..58128db5d2 100644 --- a/tests/testdata/reqauth-valid.yaml +++ b/tests/testdata/reqauth-valid.yaml @@ -20,3 +20,6 @@ spec: header: def timeout: 5s outputPayloadToHeader: header + spaceDelimitedClaims: + - "custom_scope" + - "provider.login.scope"
+

List of JWT claim names that should be treated as space-delimited strings. +These claims will be split on whitespace and each individual value will be available +for matching in authorization policies. This extends the default behavior that only +treats ‘scope’ and ‘permission’ claims as space-delimited.

+

Example usage for custom claims:

+
spaceDelimitedClaims:
+- "custom_scope"
+- "provider.login.scope"
+- "roles"
+
+

This allows authorization policies to match individual values within space-separated +claim strings, maintaining compatibility with existing JWT token formats.

+

Note: The default claims ‘scope’ and ‘permission’ are always treated as space-delimited +regardless of this setting.

+