diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go index 0b781e718dd..bed40faac29 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go @@ -355,6 +355,11 @@ func validateClaimMappings(compiler authenticationcel.Compiler, state *validatio if mapping.Key != strings.ToLower(mapping.Key) { allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), mapping.Key, "key must be lowercase")) } + + if isKubernetesDomainPrefix(mapping.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), mapping.Key, "k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use")) + } + if seenExtraKeys.Has(mapping.Key) { allErrs = append(allErrs, field.Duplicate(fldPath.Child("key"), mapping.Key)) continue @@ -394,6 +399,24 @@ func validateClaimMappings(compiler authenticationcel.Compiler, state *validatio return allErrs } +func isKubernetesDomainPrefix(key string) bool { + domainPrefix := getDomainPrefix(key) + if domainPrefix == "kubernetes.io" || strings.HasSuffix(domainPrefix, ".kubernetes.io") { + return true + } + if domainPrefix == "k8s.io" || strings.HasSuffix(domainPrefix, ".k8s.io") { + return true + } + return false +} + +func getDomainPrefix(key string) string { + if parts := strings.SplitN(key, "/", 2); len(parts) == 2 { + return parts[0] + } + return "" +} + func usesEmailClaim(ast *celgo.Ast) bool { return hasSelectExp(ast.Expr(), "claims", "email") } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation_test.go index 61778590bc8..deba4617c1e 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation_test.go @@ -1363,6 +1363,69 @@ func TestValidateClaimMappings(t *testing.T) { structuredAuthnFeatureEnabled: true, want: `issuer.claimMappings.extra[0].key: Invalid value: "example.org/Foo": key must be lowercase`, }, + { + name: "extra mapping key prefix is k8.io", + in: api.ClaimMappings{ + Username: api.PrefixedClaimOrExpression{Expression: "claims.username"}, + Groups: api.PrefixedClaimOrExpression{Expression: "claims.groups"}, + Extra: []api.ExtraMapping{ + {Key: "k8s.io/foo", ValueExpression: "claims.extra"}, + }, + }, + structuredAuthnFeatureEnabled: true, + want: `issuer.claimMappings.extra[0].key: Invalid value: "k8s.io/foo": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use`, + }, + { + name: "extra mapping key prefix contains k8.io", + in: api.ClaimMappings{ + Username: api.PrefixedClaimOrExpression{Expression: "claims.username"}, + Groups: api.PrefixedClaimOrExpression{Expression: "claims.groups"}, + Extra: []api.ExtraMapping{ + {Key: "example.k8s.io/foo", ValueExpression: "claims.extra"}, + }, + }, + structuredAuthnFeatureEnabled: true, + want: `issuer.claimMappings.extra[0].key: Invalid value: "example.k8s.io/foo": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use`, + }, + { + name: "extra mapping key prefix is kubernetes.io", + in: api.ClaimMappings{ + Username: api.PrefixedClaimOrExpression{Expression: "claims.username"}, + Groups: api.PrefixedClaimOrExpression{Expression: "claims.groups"}, + Extra: []api.ExtraMapping{ + {Key: "kubernetes.io/foo", ValueExpression: "claims.extra"}, + }, + }, + structuredAuthnFeatureEnabled: true, + want: `issuer.claimMappings.extra[0].key: Invalid value: "kubernetes.io/foo": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use`, + }, + { + name: "extra mapping key prefix contains kubernetes.io", + in: api.ClaimMappings{ + Username: api.PrefixedClaimOrExpression{Expression: "claims.username"}, + Groups: api.PrefixedClaimOrExpression{Expression: "claims.groups"}, + Extra: []api.ExtraMapping{ + {Key: "example.kubernetes.io/foo", ValueExpression: "claims.extra"}, + }, + }, + structuredAuthnFeatureEnabled: true, + want: `issuer.claimMappings.extra[0].key: Invalid value: "example.kubernetes.io/foo": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use`, + }, + { + name: "extra mapping key prefix with ak8s.io, *.ak8s.io, bkubernetes.io, *.bkubernetes.io are still valid", + in: api.ClaimMappings{ + Username: api.PrefixedClaimOrExpression{Expression: "claims.username"}, + Groups: api.PrefixedClaimOrExpression{Expression: "claims.groups"}, + Extra: []api.ExtraMapping{ + {Key: "ak8s.io/foo", ValueExpression: "claims.extra"}, + {Key: "example.ak8s.io/foo", ValueExpression: "claims.extra"}, + {Key: "bkubernetes.io/foo", ValueExpression: "claims.extra"}, + {Key: "example.bkubernetes.io/foo", ValueExpression: "claims.extra"}, + }, + }, + structuredAuthnFeatureEnabled: true, + want: "", + }, { name: "valid claim mappings but uses email without verification", in: api.ClaimMappings{