From 52c582ca77c775ee13300a999a29f8c4180750a2 Mon Sep 17 00:00:00 2001 From: Nabarun Pal Date: Thu, 14 Sep 2023 19:19:29 +0530 Subject: [PATCH] Bootstrap API Types for Structured Authorization Configuration Signed-off-by: Nabarun Pal --- .../apiserver/pkg/apis/apiserver/register.go | 1 + .../apiserver/pkg/apis/apiserver/types.go | 117 +++ .../pkg/apis/apiserver/v1alpha1/defaults.go | 36 + .../pkg/apis/apiserver/v1alpha1/register.go | 3 +- .../pkg/apis/apiserver/v1alpha1/types.go | 118 +++ .../v1alpha1/zz_generated.conversion.go | 174 ++++ .../v1alpha1/zz_generated.deepcopy.go | 115 +++ .../v1alpha1/zz_generated.defaults.go | 10 + .../apis/apiserver/validation/validation.go | 152 ++++ .../apiserver/validation/validation_test.go | 833 ++++++++++++++++++ .../apis/apiserver/zz_generated.deepcopy.go | 115 +++ 11 files changed, 1673 insertions(+), 1 deletion(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/defaults.go diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go index 7a5f6e5854f..d42852d93e6 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go @@ -44,6 +44,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &AdmissionConfiguration{}, &AuthenticationConfiguration{}, + &AuthorizationConfiguration{}, &EgressSelectorConfiguration{}, &TracingConfiguration{}, ) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go index c5ec9b4fe30..5d7bf39ecdf 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go @@ -198,3 +198,120 @@ type PrefixedClaimOrExpression struct { Claim string Prefix *string } + +// +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" + AuthorizationWebhookConnectionInfoTypeKubeConfig 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 AuthorizerType + + // Webhook defines the configuration for a Webhook authorizer + // Must be defined when Type=Webhook + Webhook *WebhookConfiguration +} + +type WebhookConfiguration struct { + // Name used to describe the webhook + // This is explicitly used in monitoring machinery for metrics + // Note: Names must be DNS1123 labels like `mywebhookname` or + // subdomains like `webhookname.example.domain` + // Required, with no default + Name string + // The duration to cache 'authorized' responses from the webhook + // authorizer. + // Same as setting `--authorization-webhook-cache-authorized-ttl` flag + // Default: 5m0s + AuthorizedTTL metav1.Duration + // The duration to cache 'unauthorized' responses from the webhook + // authorizer. + // Same as setting `--authorization-webhook-cache-unauthorized-ttl` flag + // Default: 30s + UnauthorizedTTL metav1.Duration + // Timeout for the webhook request + // Maximum allowed value is 30s. + // Required, no default value. + Timeout metav1.Duration + // 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 + // MatchConditionSubjectAccessReviewVersion specifies the SubjectAccessReview + // version the CEL expressions are evaluated against + // Valid values: v1 + // Required, no default value + MatchConditionSubjectAccessReviewVersion string + // 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 + + // ConnectionInfo defines how we talk to the webhook + ConnectionInfo WebhookConnectionInfo + + // 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 +} + +type WebhookConnectionInfo struct { + // Controls how the webhook should communicate with the server. + // Valid values: + // - KubeConfig: 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 + + // Path to KubeConfigFile for connection info + // Required, if connectionInfo.Type is KubeConfig + KubeConfigFile *string +} + +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 +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/defaults.go new file mode 100644 index 00000000000..a9af01fe76c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/defaults.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "time" + + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +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/v1alpha1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go index dc5d3be24bb..7d68ac0c62e 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go @@ -43,7 +43,7 @@ func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) + localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs) } // Adds the list of known types to the given scheme. @@ -54,6 +54,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { ) scheme.AddKnownTypes(ConfigSchemeGroupVersion, &AuthenticationConfiguration{}, + &AuthorizationConfiguration{}, &TracingConfiguration{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go index 0dd36c9bb5e..913c212ee27 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go @@ -268,3 +268,121 @@ type PrefixedClaimOrExpression struct { // +required Prefix *string `json:"prefix"` } + +// +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" + AuthorizationWebhookConnectionInfoTypeKubeConfig 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"` + + // 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 { + // Name used to describe the webhook + // This is explicitly used in monitoring machinery for metrics + // Note: Names must be DNS1123 labels like `mywebhookname` or + // subdomains like `webhookname.example.domain` + // Required, with no default + Name string `json:"name"` + // 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: + // - KubeConfig: 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/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go index 0fd3ae69cce..87b5346f5f8 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go @@ -66,6 +66,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_v1alpha1_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_v1alpha1_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_v1alpha1_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_v1alpha1_AuthorizerConfiguration(a.(*apiserver.AuthorizerConfiguration), b.(*AuthorizerConfiguration), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*ClaimMappings)(nil), (*apiserver.ClaimMappings)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_ClaimMappings_To_apiserver_ClaimMappings(a.(*ClaimMappings), b.(*apiserver.ClaimMappings), scope) }); err != nil { @@ -191,6 +211,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_v1alpha1_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_v1alpha1_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_v1alpha1_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_v1alpha1_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_v1alpha1_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_v1alpha1_WebhookMatchCondition(a.(*apiserver.WebhookMatchCondition), b.(*WebhookMatchCondition), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*EgressSelection)(nil), (*apiserver.EgressSelection)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(a.(*EgressSelection), b.(*apiserver.EgressSelection), scope) }); err != nil { @@ -263,6 +313,48 @@ func Convert_apiserver_AuthenticationConfiguration_To_v1alpha1_AuthenticationCon return autoConvert_apiserver_AuthenticationConfiguration_To_v1alpha1_AuthenticationConfiguration(in, out, s) } +func autoConvert_v1alpha1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in *AuthorizationConfiguration, out *apiserver.AuthorizationConfiguration, s conversion.Scope) error { + out.Authorizers = *(*[]apiserver.AuthorizerConfiguration)(unsafe.Pointer(&in.Authorizers)) + return nil +} + +// Convert_v1alpha1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in *AuthorizationConfiguration, out *apiserver.AuthorizationConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_AuthorizationConfiguration_To_apiserver_AuthorizationConfiguration(in, out, s) +} + +func autoConvert_apiserver_AuthorizationConfiguration_To_v1alpha1_AuthorizationConfiguration(in *apiserver.AuthorizationConfiguration, out *AuthorizationConfiguration, s conversion.Scope) error { + out.Authorizers = *(*[]AuthorizerConfiguration)(unsafe.Pointer(&in.Authorizers)) + return nil +} + +// Convert_apiserver_AuthorizationConfiguration_To_v1alpha1_AuthorizationConfiguration is an autogenerated conversion function. +func Convert_apiserver_AuthorizationConfiguration_To_v1alpha1_AuthorizationConfiguration(in *apiserver.AuthorizationConfiguration, out *AuthorizationConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_AuthorizationConfiguration_To_v1alpha1_AuthorizationConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in *AuthorizerConfiguration, out *apiserver.AuthorizerConfiguration, s conversion.Scope) error { + out.Type = apiserver.AuthorizerType(in.Type) + out.Webhook = (*apiserver.WebhookConfiguration)(unsafe.Pointer(in.Webhook)) + return nil +} + +// Convert_v1alpha1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in *AuthorizerConfiguration, out *apiserver.AuthorizerConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_AuthorizerConfiguration_To_apiserver_AuthorizerConfiguration(in, out, s) +} + +func autoConvert_apiserver_AuthorizerConfiguration_To_v1alpha1_AuthorizerConfiguration(in *apiserver.AuthorizerConfiguration, out *AuthorizerConfiguration, s conversion.Scope) error { + out.Type = string(in.Type) + out.Webhook = (*WebhookConfiguration)(unsafe.Pointer(in.Webhook)) + return nil +} + +// Convert_apiserver_AuthorizerConfiguration_To_v1alpha1_AuthorizerConfiguration is an autogenerated conversion function. +func Convert_apiserver_AuthorizerConfiguration_To_v1alpha1_AuthorizerConfiguration(in *apiserver.AuthorizerConfiguration, out *AuthorizerConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_AuthorizerConfiguration_To_v1alpha1_AuthorizerConfiguration(in, out, s) +} + func autoConvert_v1alpha1_ClaimMappings_To_apiserver_ClaimMappings(in *ClaimMappings, out *apiserver.ClaimMappings, s conversion.Scope) error { if err := Convert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpression(&in.Username, &out.Username, s); err != nil { return err @@ -583,3 +675,85 @@ func autoConvert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in *apiserver.U func Convert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in *apiserver.UDSTransport, out *UDSTransport, s conversion.Scope) error { return autoConvert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in, out, s) } + +func autoConvert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error { + out.Name = in.Name + 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_v1alpha1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(&in.ConnectionInfo, &out.ConnectionInfo, s); err != nil { + return err + } + out.MatchConditions = *(*[]apiserver.WebhookMatchCondition)(unsafe.Pointer(&in.MatchConditions)) + return nil +} + +// Convert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in, out, s) +} + +func autoConvert_apiserver_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *apiserver.WebhookConfiguration, out *WebhookConfiguration, s conversion.Scope) error { + out.Name = in.Name + 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_v1alpha1_WebhookConnectionInfo(&in.ConnectionInfo, &out.ConnectionInfo, s); err != nil { + return err + } + out.MatchConditions = *(*[]WebhookMatchCondition)(unsafe.Pointer(&in.MatchConditions)) + return nil +} + +// Convert_apiserver_WebhookConfiguration_To_v1alpha1_WebhookConfiguration is an autogenerated conversion function. +func Convert_apiserver_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *apiserver.WebhookConfiguration, out *WebhookConfiguration, s conversion.Scope) error { + return autoConvert_apiserver_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_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_v1alpha1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo is an autogenerated conversion function. +func Convert_v1alpha1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(in *WebhookConnectionInfo, out *apiserver.WebhookConnectionInfo, s conversion.Scope) error { + return autoConvert_v1alpha1_WebhookConnectionInfo_To_apiserver_WebhookConnectionInfo(in, out, s) +} + +func autoConvert_apiserver_WebhookConnectionInfo_To_v1alpha1_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_v1alpha1_WebhookConnectionInfo is an autogenerated conversion function. +func Convert_apiserver_WebhookConnectionInfo_To_v1alpha1_WebhookConnectionInfo(in *apiserver.WebhookConnectionInfo, out *WebhookConnectionInfo, s conversion.Scope) error { + return autoConvert_apiserver_WebhookConnectionInfo_To_v1alpha1_WebhookConnectionInfo(in, out, s) +} + +func autoConvert_v1alpha1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in *WebhookMatchCondition, out *apiserver.WebhookMatchCondition, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_v1alpha1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition is an autogenerated conversion function. +func Convert_v1alpha1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in *WebhookMatchCondition, out *apiserver.WebhookMatchCondition, s conversion.Scope) error { + return autoConvert_v1alpha1_WebhookMatchCondition_To_apiserver_WebhookMatchCondition(in, out, s) +} + +func autoConvert_apiserver_WebhookMatchCondition_To_v1alpha1_WebhookMatchCondition(in *apiserver.WebhookMatchCondition, out *WebhookMatchCondition, s conversion.Scope) error { + out.Expression = in.Expression + return nil +} + +// Convert_apiserver_WebhookMatchCondition_To_v1alpha1_WebhookMatchCondition is an autogenerated conversion function. +func Convert_apiserver_WebhookMatchCondition_To_v1alpha1_WebhookMatchCondition(in *apiserver.WebhookMatchCondition, out *WebhookMatchCondition, s conversion.Scope) error { + return autoConvert_apiserver_WebhookMatchCondition_To_v1alpha1_WebhookMatchCondition(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go index 328c5ddbbc5..5121d05e7d3 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go @@ -110,6 +110,59 @@ func (in *AuthenticationConfiguration) DeepCopyObject() runtime.Object { return nil } +// 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 *ClaimMappings) DeepCopyInto(out *ClaimMappings) { *out = *in @@ -383,3 +436,65 @@ func (in *UDSTransport) DeepCopy() *UDSTransport { 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/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go index 5070cb91b90..fc76be0fb8a 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go @@ -29,5 +29,15 @@ 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)) }) return nil } + +func SetObjectDefaults_AuthorizationConfiguration(in *AuthorizationConfiguration) { + for i := range in.Authorizers { + a := &in.Authorizers[i] + if a.Webhook != nil { + SetDefaults_WebhookConfiguration(a.Webhook) + } + } +} 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 90a5c8eb753..93089ac34bf 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 @@ -19,8 +19,16 @@ package validation import ( "fmt" "net/url" + "os" + "path/filepath" + "strings" + "time" + v1 "k8s.io/api/authorization/v1" + "k8s.io/api/authorization/v1beta1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" api "k8s.io/apiserver/pkg/apis/apiserver" "k8s.io/client-go/util/cert" @@ -202,3 +210,147 @@ func validateClaimMappings(m api.ClaimMappings, fldPath *field.Path) field.Error return allErrs } + +// ValidateAuthorizationConfiguration validates a given AuthorizationConfiguration. +func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.String, repeatableTypes sets.String) field.ErrorList { + allErrs := field.ErrorList{} + + if len(c.Authorizers) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("authorizers"), "at least one authorization mode must be defined")) + } + + seenAuthorizerTypes := sets.NewString() + seenWebhookNames := sets.NewString() + for i, a := range c.Authorizers { + fldPath := fldPath.Child("authorizers").Index(i) + aType := string(a.Type) + if aType == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "")) + continue + } + if !knownTypes.Has(aType) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), aType, knownTypes.List())) + continue + } + if seenAuthorizerTypes.Has(aType) && !repeatableTypes.Has(aType) { + allErrs = append(allErrs, field.Duplicate(fldPath.Child("type"), aType)) + continue + } + seenAuthorizerTypes.Insert(aType) + + switch a.Type { + case api.TypeWebhook: + if a.Webhook == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("webhook"), "required when type=Webhook")) + continue + } + allErrs = append(allErrs, ValidateWebhookConfiguration(fldPath, a.Webhook, seenWebhookNames)...) + default: + if a.Webhook != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("webhook"), "non-null", "may only be specified when type=Webhook")) + } + } + } + + return allErrs +} + +func ValidateWebhookConfiguration(fldPath *field.Path, c *api.WebhookConfiguration, seenNames sets.String) field.ErrorList { + allErrs := field.ErrorList{} + if len(c.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } else if seenNames.Has(c.Name) { + allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), c.Name)) + } else if errs := utilvalidation.IsDNS1123Subdomain(c.Name); len(errs) != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), c.Name, fmt.Sprintf("webhook name is invalid: %s", strings.Join(errs, ", ")))) + } + seenNames.Insert(c.Name) + + if c.Timeout.Duration == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("timeout"), "")) + } else if c.Timeout.Duration > 30*time.Second || c.Timeout.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("timeout"), c.Timeout.Duration.String(), "must be > 0s and <= 30s")) + } + + if c.AuthorizedTTL.Duration == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("authorizedTTL"), "")) + } else if c.AuthorizedTTL.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("authorizedTTL"), c.AuthorizedTTL.Duration.String(), "must be > 0s")) + } + + if c.UnauthorizedTTL.Duration == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("unauthorizedTTL"), "")) + } else if c.UnauthorizedTTL.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("unauthorizedTTL"), c.UnauthorizedTTL.Duration.String(), "must be > 0s")) + } + + switch c.SubjectAccessReviewVersion { + case "": + allErrs = append(allErrs, field.Required(fldPath.Child("subjectAccessReviewVersion"), "")) + case "v1": + _ = &v1.SubjectAccessReview{} + case "v1beta1": + _ = &v1beta1.SubjectAccessReview{} + default: + allErrs = append(allErrs, field.NotSupported(fldPath.Child("subjectAccessReviewVersion"), c.SubjectAccessReviewVersion, []string{"v1", "v1beta1"})) + } + + switch c.MatchConditionSubjectAccessReviewVersion { + case "": + allErrs = append(allErrs, field.Required(fldPath.Child("matchConditionSubjectAccessReviewVersion"), "")) + case "v1": + _ = &v1.SubjectAccessReview{} + default: + allErrs = append(allErrs, field.NotSupported(fldPath.Child("matchConditionSubjectAccessReviewVersion"), c.MatchConditionSubjectAccessReviewVersion, []string{"v1"})) + } + + switch c.FailurePolicy { + case "": + allErrs = append(allErrs, field.Required(fldPath.Child("failurePolicy"), "")) + case api.FailurePolicyNoOpinion, api.FailurePolicyDeny: + default: + allErrs = append(allErrs, field.NotSupported(fldPath.Child("failurePolicy"), c.FailurePolicy, []string{"NoOpinion", "Deny"})) + } + + switch c.ConnectionInfo.Type { + case "": + allErrs = append(allErrs, field.Required(fldPath.Child("connectionInfo", "type"), "")) + case api.AuthorizationWebhookConnectionInfoTypeInCluster: + if c.ConnectionInfo.KubeConfigFile != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "can only be set when type=KubeConfigFile")) + } + case api.AuthorizationWebhookConnectionInfoTypeKubeConfig: + if c.ConnectionInfo.KubeConfigFile == nil || *c.ConnectionInfo.KubeConfigFile == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("connectionInfo", "kubeConfigFile"), "")) + } else if !filepath.IsAbs(*c.ConnectionInfo.KubeConfigFile) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "must be an absolute path")) + } else if info, err := os.Stat(*c.ConnectionInfo.KubeConfigFile); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, fmt.Sprintf("error loading file: %v", err))) + } else if !info.Mode().IsRegular() { + allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "must be a regular file")) + } + default: + allErrs = append(allErrs, field.NotSupported(fldPath.Child("connectionInfo", "type"), c.ConnectionInfo, []string{"InClusterConfig", "KubeConfigFile"})) + } + + // TODO: Remove this check and ensure that correct validations below for MatchConditions are added + // for i, condition := range c.MatchConditions { + // fldPath := fldPath.Child("matchConditions").Index(i).Child("expression") + // if len(strings.TrimSpace(condition.Expression)) == 0 { + // allErrs = append(allErrs, field.Required(fldPath, "")) + // } else { + // allErrs = append(allErrs, ValidateWebhookMatchCondition(fldPath, sampleSAR, condition.Expression)...) + // } + // } + if len(c.MatchConditions) != 0 { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("matchConditions"), c.MatchConditions, []string{})) + } + + return allErrs +} + +func ValidateWebhookMatchCondition(fldPath *field.Path, sampleSAR runtime.Object, expression string) field.ErrorList { + allErrs := field.ErrorList{} + // TODO: typecheck CEL expression + return allErrs +} 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 7931a458e5f..072a34a400b 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 @@ -21,11 +21,15 @@ import ( "crypto/elliptic" "crypto/rand" "encoding/pem" + "os" "testing" + "time" "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" api "k8s.io/apiserver/pkg/apis/apiserver" certutil "k8s.io/client-go/util/cert" @@ -412,3 +416,832 @@ func errString(errs errors.Aggregate) string { } return "" } + +type ( + test struct { + name string + configuration api.AuthorizationConfiguration + expectedErrList field.ErrorList + knownTypes sets.String + repeatableTypes sets.String + } +) + +func TestValidateAuthorizationConfiguration(t *testing.T) { + badKubeConfigFile := "../some/relative/path/kubeconfig" + + tempKubeConfigFile, err := os.CreateTemp("/tmp", "kubeconfig") + if err != nil { + t.Fatalf("failed to set up temp file: %v", err) + } + tempKubeConfigFilePath := tempKubeConfigFile.Name() + defer os.Remove(tempKubeConfigFilePath) + + tests := []test{ + { + name: "atleast one authorizer should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{}, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("authorizers"), "at least one authorization mode must be defined")}, + knownTypes: sets.NewString(), + repeatableTypes: sets.NewString(), + }, + { + name: "type is required if an authorizer is defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + {}, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("type"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "bare minimum configuration with Webhook", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "bare minimum configuration with multiple webhooks", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "second-webhook", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "configuration with unknown types", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Foo", + }, + }, + }, + expectedErrList: field.ErrorList{field.NotSupported(field.NewPath("type"), "Foo", []string{"..."})}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "configuration with not repeatable types", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Foo", + }, + { + Type: "Foo", + }, + }, + }, + expectedErrList: field.ErrorList{field.Duplicate(field.NewPath("type"), "Foo")}, + knownTypes: sets.NewString([]string{string("Foo"), string("Webhook")}...), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "when type=Webhook, webhook needs to be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("webhook"), "required when type=Webhook")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "when type!=Webhook, webhooks needs to be nil", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Foo", + Webhook: &api.WebhookConfiguration{}, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("webhook"), "non-null", "may only be specified when type=Webhook")}, + knownTypes: sets.NewString(string("Foo")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "webhook name should be of non-zero length", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("name"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "webhook names should be unique", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "name-1", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "name-1", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Duplicate(field.NewPath("name"), "name-1")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "webhook names should be DNS1123 labels", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "mywebhookname", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "webhook names should be DNS1123 subdomains", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "webhookname.example.domain", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "webhook names should not be invalid DNS1123 labels or subdomains", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "WEBHOOKNAME.example.domain", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("name"), "WEBHOOKNAME.example.domain", "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "timeout should be specified", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("timeout"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + // + { + name: "timeout shouldn't be zero", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 0 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("timeout"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "timeout shouldn't be negative", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: -30 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("timeout"), time.Duration(-30*time.Second).String(), "must be > 0s and <= 30s")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "timeout shouldn't be greater than 30seconds", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 60 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("timeout"), time.Duration(60*time.Second).String(), "must be > 0s and <= 30s")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "authorizedTTL should be defined ", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("authorizedTTL"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "authorizedTTL shouldn't be negative", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: -30 * time.Second}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("authorizedTTL"), time.Duration(-30*time.Second).String(), "must be > 0s")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "unauthorizedTTL should be defined ", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("unauthorizedTTL"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "unauthorizedTTL shouldn't be negative", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + FailurePolicy: "NoOpinion", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: -30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("unauthorizedTTL"), time.Duration(-30*time.Second).String(), "must be > 0s")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "SAR should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + MatchConditionSubjectAccessReviewVersion: "v1", + FailurePolicy: "NoOpinion", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("subjectAccessReviewVersion"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "SAR should be one of v1 and v1beta1", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v2beta1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.NotSupported(field.NewPath("subjectAccessReviewVersion"), "v2beta1", []string{"v1", "v1beta1"})}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "MatchConditionSAR should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("matchConditionSubjectAccessReviewVersion"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "MatchConditionSAR must not be anything other than v1", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1beta1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.NotSupported(field.NewPath("matchConditionSubjectAccessReviewVersion"), "v1beta1", []string{"v1"})}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "failurePolicy should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("failurePolicy"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "failurePolicy should be one of \"NoOpinion\" or \"Deny\"", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "AlwaysAllow", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.NotSupported(field.NewPath("failurePolicy"), "AlwaysAllow", []string{"NoOpinion", "Deny"})}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "connectionInfo should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("connectionInfo"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "connectionInfo should be one of InClusterConfig or KubeConfigFile", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "ExternalClusterConfig", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{ + field.NotSupported(field.NewPath("connectionInfo"), api.WebhookConnectionInfo{Type: "ExternalClusterConfig"}, []string{"InClusterConfig", "KubeConfigFile"}), + }, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "if connectionInfo=InClusterConfig, then kubeConfigFile should be nil", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "InClusterConfig", + KubeConfigFile: new(string), + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{ + field.Invalid(field.NewPath("connectionInfo", "kubeConfigFile"), "", "can only be set when type=KubeConfigFile"), + }, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "if connectionInfo=KubeConfigFile, then KubeConfigFile should be defined", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "KubeConfigFile", + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Required(field.NewPath("kubeConfigFile"), "")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "if connectionInfo=KubeConfigFile, then KubeConfigFile should be defined, must be an absolute path, should exist, shouldn't be a symlink", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "KubeConfigFile", + KubeConfigFile: &badKubeConfigFile, + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{field.Invalid(field.NewPath("kubeConfigFile"), badKubeConfigFile, "must be an absolute path")}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + { + name: "if connectionInfo=KubeConfigFile, an existent file needs to be passed", + configuration: api.AuthorizationConfiguration{ + Authorizers: []api.AuthorizerConfiguration{ + { + Type: "Webhook", + Webhook: &api.WebhookConfiguration{ + Name: "default", + Timeout: metav1.Duration{Duration: 5 * time.Second}, + AuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute}, + UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, + FailurePolicy: "NoOpinion", + SubjectAccessReviewVersion: "v1", + MatchConditionSubjectAccessReviewVersion: "v1", + ConnectionInfo: api.WebhookConnectionInfo{ + Type: "KubeConfigFile", + KubeConfigFile: &tempKubeConfigFilePath, + }, + }, + }, + }, + }, + expectedErrList: field.ErrorList{}, + knownTypes: sets.NewString(string("Webhook")), + repeatableTypes: sets.NewString(string("Webhook")), + }, + + // TODO: When the CEL expression validator is implemented, add a few test cases to typecheck the expression + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + errList := ValidateAuthorizationConfiguration(nil, &test.configuration, test.knownTypes, test.repeatableTypes) + if len(errList) != len(test.expectedErrList) { + t.Errorf("expected %d errs, got %d, errors %v", len(test.expectedErrList), len(errList), errList) + } + + for i, expected := range test.expectedErrList { + if expected.Type.String() != errList[i].Type.String() { + t.Errorf("expected err type %s, got %s", + expected.Type.String(), + errList[i].Type.String()) + } + if expected.BadValue != errList[i].BadValue { + t.Errorf("expected bad value '%s', got '%s'", + expected.BadValue, + errList[i].BadValue) + } + } + }) + + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go index e1320e63163..87b41f7ef6b 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go @@ -110,6 +110,59 @@ func (in *AuthenticationConfiguration) DeepCopyObject() runtime.Object { return nil } +// 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 *ClaimMappings) DeepCopyInto(out *ClaimMappings) { *out = *in @@ -383,3 +436,65 @@ func (in *UDSTransport) DeepCopy() *UDSTransport { 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 +}