Commit f7dff472 authored by Mehdy Bohlool's avatar Mehdy Bohlool

Add AdmissionReviewVersions to admissionregistration and default it

parent 9eebfe7a
...@@ -43,6 +43,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { ...@@ -43,6 +43,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
i := int32(30) i := int32(30)
obj.TimeoutSeconds = &i obj.TimeoutSeconds = &i
} }
obj.AdmissionReviewVersions = []string{"v1beta1"}
}, },
} }
} }
...@@ -239,6 +239,15 @@ type Webhook struct { ...@@ -239,6 +239,15 @@ type Webhook struct {
// The timeout value must be between 1 and 30 seconds. // The timeout value must be between 1 and 30 seconds.
// +optional // +optional
TimeoutSeconds *int32 TimeoutSeconds *int32
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If the webhook configuration has already been persisted with a version apiserver
// does not understand, calls to the webhook will fail and be subject to the failure policy.
// +optional
AdmissionReviewVersions []string
} }
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
......
...@@ -44,6 +44,10 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { ...@@ -44,6 +44,10 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) {
obj.TimeoutSeconds = new(int32) obj.TimeoutSeconds = new(int32)
*obj.TimeoutSeconds = 30 *obj.TimeoutSeconds = 30
} }
if len(obj.AdmissionReviewVersions) == 0 {
obj.AdmissionReviewVersions = []string{admissionregistrationv1beta1.SchemeGroupVersion.Version}
}
} }
func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) { func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) {
......
...@@ -24,9 +24,11 @@ import ( ...@@ -24,9 +24,11 @@ import (
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
) )
func hasWildcard(slice []string) bool { func hasWildcard(slice []string) bool {
...@@ -150,18 +152,71 @@ func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSu ...@@ -150,18 +152,71 @@ func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSu
return allErrors return allErrors
} }
var AcceptedAdmissionReviewVersions = []string{v1beta1.SchemeGroupVersion.Version}
func isAcceptedAdmissionReviewVersion(v string) bool {
for _, version := range AcceptedAdmissionReviewVersions {
if v == version {
return true
}
}
return false
}
func validateAdmissionReviewVersions(versions []string, requireRecognizedVersion bool, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
// Currently only v1beta1 accepted in AdmissionReviewVersions
if len(versions) < 1 {
allErrors = append(allErrors, field.Required(fldPath, ""))
} else {
seen := map[string]bool{}
hasAcceptedVersion := false
for i, v := range versions {
if seen[v] {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version"))
continue
}
seen[v] = true
for _, errString := range utilvalidation.IsDNS1035Label(v) {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString))
}
if isAcceptedAdmissionReviewVersion(v) {
hasAcceptedVersion = true
}
}
if requireRecognizedVersion && !hasAcceptedVersion {
allErrors = append(allErrors, field.Invalid(
fldPath, versions,
fmt.Sprintf("none of the versions accepted by this server. accepted version(s) are %v",
strings.Join(AcceptedAdmissionReviewVersions, ", "))))
}
}
return allErrors
}
func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return validateValidatingWebhookConfiguration(e, true)
}
func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
} }
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return validateMutatingWebhookConfiguration(e, true)
}
func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
} }
...@@ -247,10 +302,28 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi ...@@ -247,10 +302,28 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi
return allErrors return allErrors
} }
// hasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
// admission review version this apiserver accepts.
func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook) bool {
for _, hook := range webhooks {
hasRecognizedVersion := false
for _, version := range hook.AdmissionReviewVersions {
if isAcceptedAdmissionReviewVersion(version) {
hasRecognizedVersion = true
break
}
}
if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
return false
}
}
return true
}
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return ValidateValidatingWebhookConfiguration(newC) return validateValidatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return ValidateMutatingWebhookConfiguration(newC) return validateMutatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }
...@@ -78,9 +78,7 @@ func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { ...@@ -78,9 +78,7 @@ func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateMutatingWebhookConfiguration(obj.(*admissionregistration.MutatingWebhookConfiguration)) return validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
updateErrorList := validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...)
} }
// AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should // AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should
......
...@@ -63,8 +63,7 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte ...@@ -63,8 +63,7 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte
// Validate validates a new validatingWebhookConfiguration. // Validate validates a new validatingWebhookConfiguration.
func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) return validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration))
return validation.ValidateValidatingWebhookConfiguration(ic)
} }
// Canonicalize normalizes the object after validation. // Canonicalize normalizes the object after validation.
...@@ -78,9 +77,7 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { ...@@ -78,9 +77,7 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration)) return validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
updateErrorList := validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...)
} }
// AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should // AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should
......
...@@ -248,6 +248,17 @@ type Webhook struct { ...@@ -248,6 +248,17 @@ type Webhook struct {
// Default to 30 seconds. // Default to 30 seconds.
// +optional // +optional
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"`
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If a persisted webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail
// and be subject to the failure policy.
// Default to `['v1beta1']`.
// +optional
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"`
} }
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
......
...@@ -94,6 +94,12 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta ...@@ -94,6 +94,12 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
} }
} }
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
}
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr) request := request.CreateAdmissionReview(attr)
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
......
...@@ -40,3 +40,13 @@ func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig { ...@@ -40,3 +40,13 @@ func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
} }
return ret return ret
} }
// HasAdmissionReviewVersion check whether a version is accepted by a given webhook.
func HasAdmissionReviewVersion(a string, w *v1beta1.Webhook) bool {
for _, b := range w.AdmissionReviewVersions {
if b == a {
return true
}
}
return false
}
...@@ -108,6 +108,12 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, ...@@ -108,6 +108,12 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook,
} }
} }
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
}
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr) request := request.CreateAdmissionReview(attr)
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment