diff --git a/pkg/features/versioned_kube_features.go b/pkg/features/versioned_kube_features.go index 47f39308413..d7a3f63c14c 100644 --- a/pkg/features/versioned_kube_features.go +++ b/pkg/features/versioned_kube_features.go @@ -308,6 +308,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate genericfeatures.StructuredAuthorizationConfiguration: { {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, genericfeatures.UnauthenticatedHTTP2DOSMitigation: { diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go index c988b9957a1..1c5eb1d0dde 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go @@ -265,6 +265,45 @@ apiVersion: apiserver.config.k8s.io/v1beta1 kind: AuthorizationConfiguration authorizers: - type: Webhook +`), + expectConfig: &api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, + }, + }, + { + name: "v1 - json", + data: []byte(`{ +"apiVersion":"apiserver.config.k8s.io/v1", +"kind":"AuthorizationConfiguration", +"authorizers":[{"type":"Webhook"}]}`), + expectConfig: &api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, + }, + }, + { + name: "v1 - defaults", + data: []byte(`{ +"apiVersion":"apiserver.config.k8s.io/v1", +"kind":"AuthorizationConfiguration", +"authorizers":[{"type":"Webhook","name":"default","webhook":{}}]}`), + expectConfig: &api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{{ + Type: "Webhook", + Name: "default", + Webhook: &api.WebhookConfiguration{ + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + }, + }}, + }, + }, + { + name: "v1 - yaml", + data: []byte(` +apiVersion: apiserver.config.k8s.io/v1 +kind: AuthorizationConfiguration +authorizers: +- type: Webhook `), expectConfig: &api.AuthorizationConfiguration{ Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/defaults.go index b71b53c658b..46fb841a5f9 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/defaults.go @@ -48,3 +48,12 @@ func SetDefaults_KMSConfiguration(obj *KMSConfiguration) { obj.CacheSize = &defaultCacheSize } } + +func SetDefaults_WebhookConfiguration(obj *WebhookConfiguration) { + if obj.AuthorizedTTL.Duration == 0 { + obj.AuthorizedTTL.Duration = 5 * time.Minute + } + if obj.UnauthorizedTTL.Duration == 0 { + obj.UnauthorizedTTL.Duration = 30 * time.Second + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/register.go index 0de8db49711..7b1b51b629d 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/register.go @@ -47,6 +47,7 @@ func init() { func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &AdmissionConfiguration{}, + &AuthorizationConfiguration{}, &EncryptionConfiguration{}, ) // also register into the v1 group as EncryptionConfig (due to a docs bug) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/types.go index e139dceb9de..e72109364f7 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/types.go @@ -48,3 +48,122 @@ type AdmissionPluginConfiguration struct { // +optional Configuration *runtime.Unknown `json:"configuration"` } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type AuthorizationConfiguration struct { + metav1.TypeMeta + + // Authorizers is an ordered list of authorizers to + // authorize requests against. + // This is similar to the --authorization-modes kube-apiserver flag + // Must be at least one. + Authorizers []AuthorizerConfiguration `json:"authorizers"` +} + +const ( + TypeWebhook AuthorizerType = "Webhook" + FailurePolicyNoOpinion string = "NoOpinion" + FailurePolicyDeny string = "Deny" + AuthorizationWebhookConnectionInfoTypeKubeConfigFile string = "KubeConfigFile" + AuthorizationWebhookConnectionInfoTypeInCluster string = "InClusterConfig" +) + +type AuthorizerType string + +type AuthorizerConfiguration struct { + // Type refers to the type of the authorizer + // "Webhook" is supported in the generic API server + // Other API servers may support additional authorizer + // types like Node, RBAC, ABAC, etc. + Type string `json:"type"` + + // Name used to describe the webhook + // This is explicitly used in monitoring machinery for metrics + // Note: Names must be DNS1123 labels like `myauthorizername` or + // subdomains like `myauthorizer.example.domain` + // Required, with no default + Name string `json:"name"` + + // Webhook defines the configuration for a Webhook authorizer + // Must be defined when Type=Webhook + // Must not be defined when Type!=Webhook + Webhook *WebhookConfiguration `json:"webhook,omitempty"` +} + +type WebhookConfiguration struct { + // The duration to cache 'authorized' responses from the webhook + // authorizer. + // Same as setting `--authorization-webhook-cache-authorized-ttl` flag + // Default: 5m0s + AuthorizedTTL metav1.Duration `json:"authorizedTTL"` + // The duration to cache 'unauthorized' responses from the webhook + // authorizer. + // Same as setting `--authorization-webhook-cache-unauthorized-ttl` flag + // Default: 30s + UnauthorizedTTL metav1.Duration `json:"unauthorizedTTL"` + // Timeout for the webhook request + // Maximum allowed value is 30s. + // Required, no default value. + Timeout metav1.Duration `json:"timeout"` + // The API version of the authorization.k8s.io SubjectAccessReview to + // send to and expect from the webhook. + // Same as setting `--authorization-webhook-version` flag + // Valid values: v1beta1, v1 + // Required, no default value + SubjectAccessReviewVersion string `json:"subjectAccessReviewVersion"` + // MatchConditionSubjectAccessReviewVersion specifies the SubjectAccessReview + // version the CEL expressions are evaluated against + // Valid values: v1 + // Required, no default value + MatchConditionSubjectAccessReviewVersion string `json:"matchConditionSubjectAccessReviewVersion"` + // Controls the authorization decision when a webhook request fails to + // complete or returns a malformed response or errors evaluating + // matchConditions. + // Valid values: + // - NoOpinion: continue to subsequent authorizers to see if one of + // them allows the request + // - Deny: reject the request without consulting subsequent authorizers + // Required, with no default. + FailurePolicy string `json:"failurePolicy"` + + // ConnectionInfo defines how we talk to the webhook + ConnectionInfo WebhookConnectionInfo `json:"connectionInfo"` + + // matchConditions is a list of conditions that must be met for a request to be sent to this + // webhook. An empty list of matchConditions matches all requests. + // There are a maximum of 64 match conditions allowed. + // + // The exact matching logic is (in order): + // 1. If at least one matchCondition evaluates to FALSE, then the webhook is skipped. + // 2. If ALL matchConditions evaluate to TRUE, then the webhook is called. + // 3. If at least one matchCondition evaluates to an error (but none are FALSE): + // - If failurePolicy=Deny, then the webhook rejects the request + // - If failurePolicy=NoOpinion, then the error is ignored and the webhook is skipped + MatchConditions []WebhookMatchCondition `json:"matchConditions"` +} + +type WebhookConnectionInfo struct { + // Controls how the webhook should communicate with the server. + // Valid values: + // - KubeConfigFile: use the file specified in kubeConfigFile to locate the + // server. + // - InClusterConfig: use the in-cluster configuration to call the + // SubjectAccessReview API hosted by kube-apiserver. This mode is not + // allowed for kube-apiserver. + Type string `json:"type"` + + // Path to KubeConfigFile for connection info + // Required, if connectionInfo.Type is KubeConfig + KubeConfigFile *string `json:"kubeConfigFile"` +} + +type WebhookMatchCondition struct { + // expression represents the expression which will be evaluated by CEL. Must evaluate to bool. + // CEL expressions have access to the contents of the SubjectAccessReview in v1 version. + // If version specified by subjectAccessReviewVersion in the request variable is v1beta1, + // the contents would be converted to the v1 version before evaluating the CEL expression. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Expression string `json:"expression"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go index c0f218742a3..63083025a53 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go @@ -67,6 +67,26 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*AuthorizationConfiguration)(nil), (*apiserver.AuthorizationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(a.(*AuthorizationConfiguration), b.(*apiserver.AuthorizationConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*apiserver.AuthorizationConfiguration)(nil), (*AuthorizationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiserver_AuthorizationConfiguration_To_v1_AuthorizationConfiguration(a.(*apiserver.AuthorizationConfiguration), b.(*AuthorizationConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*AuthorizerConfiguration)(nil), (*apiserver.AuthorizerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(a.(*AuthorizerConfiguration), b.(*apiserver.AuthorizerConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*apiserver.AuthorizerConfiguration)(nil), (*AuthorizerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiserver_AuthorizerConfiguration_To_v1_AuthorizerConfiguration(a.(*apiserver.AuthorizerConfiguration), b.(*AuthorizerConfiguration), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*EncryptionConfiguration)(nil), (*apiserver.EncryptionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_EncryptionConfiguration_To_apiserver_EncryptionConfiguration(a.(*EncryptionConfiguration), b.(*apiserver.EncryptionConfiguration), scope) }); err != nil { @@ -137,6 +157,36 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*WebhookConfiguration)(nil), (*apiserver.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_WebhookConfiguration_To_apiserver_WebhookConfiguration(a.(*WebhookConfiguration), b.(*apiserver.WebhookConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*apiserver.WebhookConfiguration)(nil), (*WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiserver_WebhookConfiguration_To_v1_WebhookConfiguration(a.(*apiserver.WebhookConfiguration), b.(*WebhookConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*WebhookConnectionInfo)(nil), (*apiserver.WebhookConnectionInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(a.(*WebhookConnectionInfo), b.(*apiserver.WebhookConnectionInfo), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*apiserver.WebhookConnectionInfo)(nil), (*WebhookConnectionInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo(a.(*apiserver.WebhookConnectionInfo), b.(*WebhookConnectionInfo), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*WebhookMatchCondition)(nil), (*apiserver.WebhookMatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(a.(*WebhookMatchCondition), b.(*apiserver.WebhookMatchCondition), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*apiserver.WebhookMatchCondition)(nil), (*WebhookMatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiserver_WebhookMatchCondition_To_v1_WebhookMatchCondition(a.(*apiserver.WebhookMatchCondition), b.(*WebhookMatchCondition), scope) + }); err != nil { + return err + } return nil } @@ -204,6 +254,50 @@ func Convert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfigu return autoConvert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration(in, out, s) } +func autoConvert_v1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in *AuthorizationConfiguration, out *apiserver.AuthorizationConfiguration, s conversion.Scope) error { + out.Authorizers = *(*[]apiserver.AuthorizerConfiguration)(unsafe.Pointer(&in.Authorizers)) + return nil +} + +// Convert_v1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration is an autogenerated conversion function. +func Convert_v1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in *AuthorizationConfiguration, out *apiserver.AuthorizationConfiguration, s conversion.Scope) error { + return autoConvert_v1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in, out, s) +} + +func autoConvert_apiserver_AuthorizationConfiguration_To_v1_AuthorizationConfiguration(in *apiserver.AuthorizationConfiguration, out *AuthorizationConfiguration, s conversion.Scope) error { + out.Authorizers = *(*[]AuthorizerConfiguration)(unsafe.Pointer(&in.Authorizers)) + return nil +} + +// Convert_apiserver_AuthorizationConfiguration_To_v1_AuthorizationConfiguration is an autogenerated conversion function. +func Convert_apiserver_AuthorizationConfiguration_To_v1_AuthorizationConfiguration(in *apiserver.AuthorizationConfiguration, out *AuthorizationConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_AuthorizationConfiguration_To_v1_AuthorizationConfiguration(in, out, s) +} + +func autoConvert_v1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in *AuthorizerConfiguration, out *apiserver.AuthorizerConfiguration, s conversion.Scope) error { + out.Type = apiserver.AuthorizerType(in.Type) + out.Name = in.Name + out.Webhook = (*apiserver.WebhookConfiguration)(unsafe.Pointer(in.Webhook)) + return nil +} + +// Convert_v1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration is an autogenerated conversion function. +func Convert_v1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in *AuthorizerConfiguration, out *apiserver.AuthorizerConfiguration, s conversion.Scope) error { + return autoConvert_v1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in, out, s) +} + +func autoConvert_apiserver_AuthorizerConfiguration_To_v1_AuthorizerConfiguration(in *apiserver.AuthorizerConfiguration, out *AuthorizerConfiguration, s conversion.Scope) error { + out.Type = string(in.Type) + out.Name = in.Name + out.Webhook = (*WebhookConfiguration)(unsafe.Pointer(in.Webhook)) + return nil +} + +// Convert_apiserver_AuthorizerConfiguration_To_v1_AuthorizerConfiguration is an autogenerated conversion function. +func Convert_apiserver_AuthorizerConfiguration_To_v1_AuthorizerConfiguration(in *apiserver.AuthorizerConfiguration, out *AuthorizerConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_AuthorizerConfiguration_To_v1_AuthorizerConfiguration(in, out, s) +} + func autoConvert_v1_EncryptionConfiguration_To_apiserver_EncryptionConfiguration(in *EncryptionConfiguration, out *apiserver.EncryptionConfiguration, s conversion.Scope) error { out.Resources = *(*[]apiserver.ResourceConfiguration)(unsafe.Pointer(&in.Resources)) return nil @@ -361,3 +455,83 @@ func autoConvert_apiserver_SecretboxConfiguration_To_v1_SecretboxConfiguration(i func Convert_apiserver_SecretboxConfiguration_To_v1_SecretboxConfiguration(in *apiserver.SecretboxConfiguration, out *SecretboxConfiguration, s conversion.Scope) error { return autoConvert_apiserver_SecretboxConfiguration_To_v1_SecretboxConfiguration(in, out, s) } + +func autoConvert_v1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error { + out.AuthorizedTTL = in.AuthorizedTTL + out.UnauthorizedTTL = in.UnauthorizedTTL + out.Timeout = in.Timeout + out.SubjectAccessReviewVersion = in.SubjectAccessReviewVersion + out.MatchConditionSubjectAccessReviewVersion = in.MatchConditionSubjectAccessReviewVersion + out.FailurePolicy = in.FailurePolicy + if err := Convert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(&in.ConnectionInfo, &out.ConnectionInfo, s); err != nil { + return err + } + out.MatchConditions = *(*[]apiserver.WebhookMatchCondition)(unsafe.Pointer(&in.MatchConditions)) + return nil +} + +// Convert_v1_WebhookConfiguration_To_apiserver_WebhookConfiguration is an autogenerated conversion function. +func Convert_v1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error { + return autoConvert_v1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in, out, s) +} + +func autoConvert_apiserver_WebhookConfiguration_To_v1_WebhookConfiguration(in *apiserver.WebhookConfiguration, out *WebhookConfiguration, s conversion.Scope) error { + out.AuthorizedTTL = in.AuthorizedTTL + out.UnauthorizedTTL = in.UnauthorizedTTL + out.Timeout = in.Timeout + out.SubjectAccessReviewVersion = in.SubjectAccessReviewVersion + out.MatchConditionSubjectAccessReviewVersion = in.MatchConditionSubjectAccessReviewVersion + out.FailurePolicy = in.FailurePolicy + if err := Convert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo(&in.ConnectionInfo, &out.ConnectionInfo, s); err != nil { + return err + } + out.MatchConditions = *(*[]WebhookMatchCondition)(unsafe.Pointer(&in.MatchConditions)) + return nil +} + +// Convert_apiserver_WebhookConfiguration_To_v1_WebhookConfiguration is an autogenerated conversion function. +func Convert_apiserver_WebhookConfiguration_To_v1_WebhookConfiguration(in *apiserver.WebhookConfiguration, out *WebhookConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_WebhookConfiguration_To_v1_WebhookConfiguration(in, out, s) +} + +func autoConvert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(in *WebhookConnectionInfo, out *apiserver.WebhookConnectionInfo, s conversion.Scope) error { + out.Type = in.Type + out.KubeConfigFile = (*string)(unsafe.Pointer(in.KubeConfigFile)) + return nil +} + +// Convert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo is an autogenerated conversion function. +func Convert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(in *WebhookConnectionInfo, out *apiserver.WebhookConnectionInfo, s conversion.Scope) error { + return autoConvert_v1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(in, out, s) +} + +func autoConvert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo(in *apiserver.WebhookConnectionInfo, out *WebhookConnectionInfo, s conversion.Scope) error { + out.Type = in.Type + out.KubeConfigFile = (*string)(unsafe.Pointer(in.KubeConfigFile)) + return nil +} + +// Convert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo is an autogenerated conversion function. +func Convert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo(in *apiserver.WebhookConnectionInfo, out *WebhookConnectionInfo, s conversion.Scope) error { + return autoConvert_apiserver_WebhookConnectionInfo_To_v1_WebhookConnectionInfo(in, out, s) +} + +func autoConvert_v1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in *WebhookMatchCondition, out *apiserver.WebhookMatchCondition, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_v1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition is an autogenerated conversion function. +func Convert_v1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in *WebhookMatchCondition, out *apiserver.WebhookMatchCondition, s conversion.Scope) error { + return autoConvert_v1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in, out, s) +} + +func autoConvert_apiserver_WebhookMatchCondition_To_v1_WebhookMatchCondition(in *apiserver.WebhookMatchCondition, out *WebhookMatchCondition, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_apiserver_WebhookMatchCondition_To_v1_WebhookMatchCondition is an autogenerated conversion function. +func Convert_apiserver_WebhookMatchCondition_To_v1_WebhookMatchCondition(in *apiserver.WebhookMatchCondition, out *WebhookMatchCondition, s conversion.Scope) error { + return autoConvert_apiserver_WebhookMatchCondition_To_v1_WebhookMatchCondition(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go index cbdcaa5a06c..6afdbd3a2cc 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go @@ -100,6 +100,59 @@ func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationConfiguration) DeepCopyInto(out *AuthorizationConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Authorizers != nil { + in, out := &in.Authorizers, &out.Authorizers + *out = make([]AuthorizerConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationConfiguration. +func (in *AuthorizationConfiguration) DeepCopy() *AuthorizationConfiguration { + if in == nil { + return nil + } + out := new(AuthorizationConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthorizationConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizerConfiguration) DeepCopyInto(out *AuthorizerConfiguration) { + *out = *in + if in.Webhook != nil { + in, out := &in.Webhook, &out.Webhook + *out = new(WebhookConfiguration) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizerConfiguration. +func (in *AuthorizerConfiguration) DeepCopy() *AuthorizerConfiguration { + if in == nil { + return nil + } + out := new(AuthorizerConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) { *out = *in @@ -279,3 +332,65 @@ func (in *SecretboxConfiguration) DeepCopy() *SecretboxConfiguration { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { + *out = *in + out.AuthorizedTTL = in.AuthorizedTTL + out.UnauthorizedTTL = in.UnauthorizedTTL + out.Timeout = in.Timeout + in.ConnectionInfo.DeepCopyInto(&out.ConnectionInfo) + if in.MatchConditions != nil { + in, out := &in.MatchConditions, &out.MatchConditions + *out = make([]WebhookMatchCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConfiguration. +func (in *WebhookConfiguration) DeepCopy() *WebhookConfiguration { + if in == nil { + return nil + } + out := new(WebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookConnectionInfo) DeepCopyInto(out *WebhookConnectionInfo) { + *out = *in + if in.KubeConfigFile != nil { + in, out := &in.KubeConfigFile, &out.KubeConfigFile + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConnectionInfo. +func (in *WebhookConnectionInfo) DeepCopy() *WebhookConnectionInfo { + if in == nil { + return nil + } + out := new(WebhookConnectionInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookMatchCondition) DeepCopyInto(out *WebhookMatchCondition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookMatchCondition. +func (in *WebhookMatchCondition) DeepCopy() *WebhookMatchCondition { + if in == nil { + return nil + } + out := new(WebhookMatchCondition) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go index 82fec011102..4c8189b1317 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go @@ -29,10 +29,20 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&AuthorizationConfiguration{}, func(obj interface{}) { SetObjectDefaults_AuthorizationConfiguration(obj.(*AuthorizationConfiguration)) }) scheme.AddTypeDefaultingFunc(&EncryptionConfiguration{}, func(obj interface{}) { SetObjectDefaults_EncryptionConfiguration(obj.(*EncryptionConfiguration)) }) return nil } +func SetObjectDefaults_AuthorizationConfiguration(in *AuthorizationConfiguration) { + for i := range in.Authorizers { + a := &in.Authorizers[i] + if a.Webhook != nil { + SetDefaults_WebhookConfiguration(a.Webhook) + } + } +} + func SetObjectDefaults_EncryptionConfiguration(in *EncryptionConfiguration) { for i := range in.Resources { a := &in.Resources[i] 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 b2cbfd90548..2e39d375f1c 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 @@ -1686,8 +1686,6 @@ type ( ) func TestValidateAuthorizationConfiguration(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) - badKubeConfigFile := "../some/relative/path/kubeconfig" tempKubeConfigFile, err := os.CreateTemp("/tmp", "kubeconfig") @@ -2481,7 +2479,6 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { testCases := []struct { name string matchConditions []api.WebhookMatchCondition - featureEnabled bool expectedErr string }{ { @@ -2494,26 +2491,11 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { Expression: "request.user == 'admin'", }, }, - featureEnabled: true, - expectedErr: "", - }, - { - name: "should fail when match conditions are used without feature enabled", - matchConditions: []api.WebhookMatchCondition{ - { - Expression: "has(request.resourceAttributes) && request.resourceAttributes.namespace == 'kube-system'", - }, - { - Expression: "request.user == 'admin'", - }, - }, - featureEnabled: false, - expectedErr: `matchConditions: Invalid value: "": matchConditions are not supported when StructuredAuthorizationConfiguration feature gate is disabled`, + expectedErr: "", }, { name: "no matchConditions should not require feature enablement", matchConditions: []api.WebhookMatchCondition{}, - featureEnabled: false, expectedErr: "", }, { @@ -2523,8 +2505,7 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { Expression: " ", }, }, - featureEnabled: true, - expectedErr: "matchConditions[0].expression: Required value", + expectedErr: "matchConditions[0].expression: Required value", }, { name: "match conditions with duplicate expressions", @@ -2536,8 +2517,7 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { Expression: "request.user == 'admin'", }, }, - featureEnabled: true, - expectedErr: `matchConditions[1].expression: Duplicate value: "request.user == 'admin'"`, + expectedErr: `matchConditions[1].expression: Duplicate value: "request.user == 'admin'"`, }, { name: "match conditions with undeclared reference", @@ -2546,8 +2526,7 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { Expression: "test", }, }, - featureEnabled: true, - expectedErr: "matchConditions[0].expression: Invalid value: \"test\": compilation failed: ERROR: :1:1: undeclared reference to 'test' (in container '')\n | test\n | ^", + expectedErr: "matchConditions[0].expression: Invalid value: \"test\": compilation failed: ERROR: :1:1: undeclared reference to 'test' (in container '')\n | test\n | ^", }, { name: "match conditions with bad return type", @@ -2556,14 +2535,12 @@ func TestValidateAndCompileMatchConditions(t *testing.T) { Expression: "request.user = 'test'", }, }, - featureEnabled: true, - expectedErr: "matchConditions[0].expression: Invalid value: \"request.user = 'test'\": compilation failed: ERROR: :1:14: Syntax error: token recognition error at: '= '\n | request.user = 'test'\n | .............^\nERROR: :1:16: Syntax error: extraneous input ''test'' expecting \n | request.user = 'test'\n | ...............^", + expectedErr: "matchConditions[0].expression: Invalid value: \"request.user = 'test'\": compilation failed: ERROR: :1:14: Syntax error: token recognition error at: '= '\n | request.user = 'test'\n | .............^\nERROR: :1:16: Syntax error: extraneous input ''test'' expecting \n | request.user = 'test'\n | ...............^", }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, tt.featureEnabled) celMatcher, errList := ValidateAndCompileMatchConditions(authorizationcel.NewDefaultCompiler(), tt.matchConditions) if len(tt.expectedErr) == 0 && len(tt.matchConditions) > 0 && len(errList) == 0 && celMatcher == nil { t.Errorf("celMatcher should not be nil when there are matchCondition and no error returned") diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index c17f2849a62..f70644cd4d4 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -373,6 +373,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate StructuredAuthorizationConfiguration: { {Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, }, UnauthenticatedHTTP2DOSMitigation: { diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_v1_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_v1_test.go index ea058c9d5e7..7d22897c235 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_v1_test.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_v1_test.go @@ -590,7 +590,6 @@ func TestV1WebhookCache(t *testing.T) { t.Fatal(err) } defer s.Close() - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) expressions := []apiserver.WebhookMatchCondition{ { Expression: "has(request.resourceAttributes) && request.resourceAttributes.namespace == 'kittensandponies'", @@ -711,7 +710,6 @@ func TestStructuredAuthzConfigFeatureEnablement(t *testing.T) { expectedEvalErr bool expectedDecision authorizer.Decision expressions []apiserver.WebhookMatchCondition - featureEnabled bool selectorEnabled bool } aliceAttr := authorizer.AttributesRecord{ @@ -746,20 +744,6 @@ func TestStructuredAuthzConfigFeatureEnablement(t *testing.T) { expectedCompileErr: false, expectedDecision: authorizer.DecisionAllow, expressions: []apiserver.WebhookMatchCondition{}, - featureEnabled: false, - }, - { - name: "should fail when match conditions are used without feature enabled", - attr: aliceAttr, - allow: false, - expectedCompileErr: true, - expectedDecision: authorizer.DecisionNoOpinion, - expressions: []apiserver.WebhookMatchCondition{ - { - Expression: "request.user == 'alice'", - }, - }, - featureEnabled: false, }, { name: "feature enabled, match all against all expressions", @@ -793,14 +777,12 @@ func TestStructuredAuthzConfigFeatureEnablement(t *testing.T) { Expression: "request.resourceAttributes.labelSelector.?requirements.orValue([]).exists(r, r.key=='baz' && r.operator=='In' && ('qux' in r.values))", }, }, - featureEnabled: true, selectorEnabled: true, }, } for i, test := range tests { t.Run(test.name, func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, test.featureEnabled) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AuthorizeWithSelectors, test.selectorEnabled) // create new compiler because it depends on the feature gate @@ -837,7 +819,6 @@ func TestWebhookMetrics(t *testing.T) { t.Fatal(err) } defer s.Close() - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) aliceAttr := authorizer.AttributesRecord{ User: &user.DefaultInfo{ @@ -934,23 +915,13 @@ func TestWebhookMetrics(t *testing.T) { } } -func BenchmarkNoCELExpressionFeatureOff(b *testing.B) { - expressions := []apiserver.WebhookMatchCondition{} - b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, false) - }) - b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, false) - }) -} - func BenchmarkNoCELExpressionFeatureOn(b *testing.B) { expressions := []apiserver.WebhookMatchCondition{} b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithOneCELExpressions(b *testing.B) { @@ -960,10 +931,10 @@ func BenchmarkWithOneCELExpressions(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithOneCELExpressionsFalse(b *testing.B) { @@ -973,10 +944,10 @@ func BenchmarkWithOneCELExpressionsFalse(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithTwoCELExpressions(b *testing.B) { @@ -989,10 +960,10 @@ func BenchmarkWithTwoCELExpressions(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithTwoCELExpressionsFalse(b *testing.B) { @@ -1005,10 +976,10 @@ func BenchmarkWithTwoCELExpressionsFalse(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithManyCELExpressions(b *testing.B) { @@ -1039,10 +1010,10 @@ func BenchmarkWithManyCELExpressions(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } func BenchmarkWithManyCELExpressionsFalse(b *testing.B) { @@ -1073,14 +1044,14 @@ func BenchmarkWithManyCELExpressionsFalse(b *testing.B) { }, } b.Run("compile", func(b *testing.B) { - benchmarkNewWebhookAuthorizer(b, expressions, true) + benchmarkNewWebhookAuthorizer(b, expressions) }) b.Run("authorize", func(b *testing.B) { - benchmarkWebhookAuthorize(b, expressions, true) + benchmarkWebhookAuthorize(b, expressions) }) } -func benchmarkNewWebhookAuthorizer(b *testing.B, expressions []apiserver.WebhookMatchCondition, featureEnabled bool) { +func benchmarkNewWebhookAuthorizer(b *testing.B, expressions []apiserver.WebhookMatchCondition) { service := new(mockV1Service) service.statusCode = 200 service.Allow() @@ -1089,7 +1060,6 @@ func benchmarkNewWebhookAuthorizer(b *testing.B, expressions []apiserver.Webhook b.Fatal(err) } defer s.Close() - featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, featureEnabled) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -1102,7 +1072,7 @@ func benchmarkNewWebhookAuthorizer(b *testing.B, expressions []apiserver.Webhook b.StopTimer() } -func benchmarkWebhookAuthorize(b *testing.B, expressions []apiserver.WebhookMatchCondition, featureEnabled bool) { +func benchmarkWebhookAuthorize(b *testing.B, expressions []apiserver.WebhookMatchCondition) { attr := authorizer.AttributesRecord{ User: &user.DefaultInfo{ Name: "alice", @@ -1122,7 +1092,6 @@ func benchmarkWebhookAuthorize(b *testing.B, expressions []apiserver.WebhookMatc b.Fatal(err) } defer s.Close() - featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, featureEnabled) // Create an authorizer with or without expressions to compile wh, err := newV1Authorizer(s.URL, clientCert, clientKey, caCert, 0, noopAuthorizerMetrics(), authorizationcel.NewDefaultCompiler(), expressions, "") if err != nil { @@ -1142,7 +1111,6 @@ func benchmarkWebhookAuthorize(b *testing.B, expressions []apiserver.WebhookMatc // TestV1WebhookMatchConditions verifies cel expressions are compiled and evaluated correctly func TestV1WebhookMatchConditions(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) service := new(mockV1Service) service.statusCode = 200 service.Allow() diff --git a/test/featuregates_linter/test_data/versioned_feature_list.yaml b/test/featuregates_linter/test_data/versioned_feature_list.yaml index cd039bf5608..188d98a550a 100644 --- a/test/featuregates_linter/test_data/versioned_feature_list.yaml +++ b/test/featuregates_linter/test_data/versioned_feature_list.yaml @@ -1182,6 +1182,10 @@ lockToDefault: false preRelease: Beta version: "1.30" + - default: true + lockToDefault: true + preRelease: GA + version: "1.32" - name: SupplementalGroupsPolicy versionedSpecs: - default: false diff --git a/test/integration/auth/authz_config_test.go b/test/integration/auth/authz_config_test.go index d062786baf2..ef4ff057721 100644 --- a/test/integration/auth/authz_config_test.go +++ b/test/integration/auth/authz_config_test.go @@ -54,8 +54,6 @@ import ( ) func TestAuthzConfig(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) - dir := t.TempDir() configFileName := filepath.Join(dir, "config.yaml") if err := atomicWriteFile(configFileName, []byte(` @@ -126,7 +124,6 @@ authorizers: func TestMultiWebhookAuthzConfig(t *testing.T) { authzmetrics.ResetMetricsForTest() defer authzmetrics.ResetMetricsForTest() - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthorizationConfiguration, true) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AuthorizeWithSelectors, true) dir := t.TempDir() @@ -415,7 +412,7 @@ users: configFileName := filepath.Join(dir, "config.yaml") if err := atomicWriteFile(configFileName, []byte(` -apiVersion: apiserver.config.k8s.io/v1alpha1 +apiVersion: apiserver.config.k8s.io/v1beta1 kind: AuthorizationConfiguration authorizers: - type: Webhook @@ -803,7 +800,7 @@ authorizers: // write good config with different webhook if err := atomicWriteFile(configFileName, []byte(` -apiVersion: apiserver.config.k8s.io/v1beta1 +apiVersion: apiserver.config.k8s.io/v1 kind: AuthorizationConfiguration authorizers: - type: Webhook