mirror of https://github.com/k3s-io/k3s
Add AdmissionReviewVersions to admissionregistration and default it
parent
9eebfe7a04
commit
f7dff4725f
|
@ -43,6 +43,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||
i := int32(30)
|
||||
obj.TimeoutSeconds = &i
|
||||
}
|
||||
obj.AdmissionReviewVersions = []string{"v1beta1"}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,6 +239,15 @@ type Webhook struct {
|
|||
// The timeout value must be between 1 and 30 seconds.
|
||||
// +optional
|
||||
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
|
||||
|
|
|
@ -44,6 +44,10 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) {
|
|||
obj.TimeoutSeconds = new(int32)
|
||||
*obj.TimeoutSeconds = 30
|
||||
}
|
||||
|
||||
if len(obj.AdmissionReviewVersions) == 0 {
|
||||
obj.AdmissionReviewVersions = []string{admissionregistrationv1beta1.SchemeGroupVersion.Version}
|
||||
}
|
||||
}
|
||||
|
||||
func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) {
|
||||
|
|
|
@ -24,9 +24,11 @@ import (
|
|||
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
|
||||
)
|
||||
|
||||
func hasWildcard(slice []string) bool {
|
||||
|
@ -150,18 +152,71 @@ func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSu
|
|||
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 {
|
||||
return validateValidatingWebhookConfiguration(e, true)
|
||||
}
|
||||
|
||||
func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
|
||||
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
for i, hook := range e.Webhooks {
|
||||
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
|
||||
}
|
||||
|
||||
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"))
|
||||
for i, hook := range e.Webhooks {
|
||||
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
|
||||
}
|
||||
|
@ -247,10 +302,28 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi
|
|||
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 {
|
||||
return ValidateValidatingWebhookConfiguration(newC)
|
||||
return validateValidatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
|
||||
}
|
||||
|
||||
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||||
return ValidateMutatingWebhookConfiguration(newC)
|
||||
return validateMutatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks))
|
||||
}
|
||||
|
|
|
@ -28,7 +28,14 @@ func strPtr(s string) *string { return &s }
|
|||
|
||||
func int32Ptr(i int32) *int32 { return &i }
|
||||
|
||||
func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *admissionregistration.ValidatingWebhookConfiguration {
|
||||
func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration {
|
||||
// If the test case did not specify an AdmissionReviewVersions, default it so the test passes as
|
||||
// this field will be defaulted in production code.
|
||||
for i := range hooks {
|
||||
if defaultAdmissionReviewVersions && len(hooks[i].AdmissionReviewVersions) == 0 {
|
||||
hooks[i].AdmissionReviewVersions = []string{"v1beta1"}
|
||||
}
|
||||
}
|
||||
return &admissionregistration.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
|
@ -48,10 +55,64 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
config *admissionregistration.ValidatingWebhookConfiguration
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "should fail on bad AdmissionReviewVersion value",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"0v"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `Invalid value: "0v": a DNS-1035 label`,
|
||||
},
|
||||
{
|
||||
name: "should pass on valid AdmissionReviewVersion",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"v1beta1", "invalid-version"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "should fail on invalid AdmissionReviewVersion",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"invalidVersion"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `Invalid value: []string{"invalidVersion"}`,
|
||||
},
|
||||
{
|
||||
name: "should fail on duplicate AdmissionReviewVersion",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"v1beta1", "v1beta1"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `Invalid value: "v1beta1": duplicate version`,
|
||||
},
|
||||
{
|
||||
name: "all Webhooks must have a fully qualified name",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -64,13 +125,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
Name: "",
|
||||
ClientConfig: validClientConfig,
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "Operations must not be empty or nil",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Rules: []admissionregistration.RuleWithOperations{
|
||||
|
@ -92,13 +152,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`,
|
||||
},
|
||||
{
|
||||
name: "\"\" is NOT a valid operation",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Rules: []admissionregistration.RuleWithOperations{
|
||||
|
@ -112,13 +171,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `Unsupported value: ""`,
|
||||
},
|
||||
{
|
||||
name: "operation must be either create/update/delete/connect",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Rules: []admissionregistration.RuleWithOperations{
|
||||
|
@ -132,13 +190,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `Unsupported value: "PATCH"`,
|
||||
},
|
||||
{
|
||||
name: "wildcard operation cannot be mixed with other strings",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Rules: []admissionregistration.RuleWithOperations{
|
||||
|
@ -152,13 +209,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `if '*' is present, must not specify other operations`,
|
||||
},
|
||||
{
|
||||
name: `resource "*" can co-exist with resources that have subresources`,
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -173,12 +229,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
},
|
||||
{
|
||||
name: `resource "*" cannot mix with resources that don't have subresources`,
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -193,13 +248,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `if '*' is present, must not specify other resources without subresources`,
|
||||
},
|
||||
{
|
||||
name: "resource a/* cannot mix with a/x",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -214,13 +268,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
|
||||
},
|
||||
{
|
||||
name: "resource a/* can mix with a",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -235,12 +288,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
},
|
||||
{
|
||||
name: "resource */a cannot mix with x/a",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -255,13 +307,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
|
||||
},
|
||||
{
|
||||
name: "resource */* cannot mix with other resources",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -276,13 +327,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
|
||||
},
|
||||
{
|
||||
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -291,13 +341,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
return &r
|
||||
}(),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
|
||||
},
|
||||
{
|
||||
name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -306,24 +355,22 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
return &r
|
||||
}(),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun", "Some", "Unknown"`,
|
||||
},
|
||||
{
|
||||
name: "both service and URL missing",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `exactly one of`,
|
||||
},
|
||||
{
|
||||
name: "both service and URL provided",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -334,104 +381,96 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
URL: strPtr("example.com/k8s/webhook"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`,
|
||||
},
|
||||
{
|
||||
name: "blank URL",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr(""),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `[0].clientConfig.url: Invalid value: "": host must be provided`,
|
||||
},
|
||||
{
|
||||
name: "wrong scheme",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("http://example.com"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `https`,
|
||||
},
|
||||
{
|
||||
name: "missing host",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("https:///fancy/webhook"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `host must be provided`,
|
||||
},
|
||||
{
|
||||
name: "fragment",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("https://example.com/#bookmark"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `"bookmark": fragments are not permitted`,
|
||||
},
|
||||
{
|
||||
name: "query",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("https://example.com?arg=value"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `"arg=value": query parameters are not permitted`,
|
||||
},
|
||||
{
|
||||
name: "user",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("https://harry.potter@example.com/"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `"harry.potter": user information is not permitted`,
|
||||
},
|
||||
{
|
||||
name: "just totally wrong",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `host must be provided`,
|
||||
},
|
||||
{
|
||||
name: "path must start with slash",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -442,13 +481,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`,
|
||||
},
|
||||
{
|
||||
name: "path accepts slash",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -459,13 +497,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "path accepts no trailing slash",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -476,13 +513,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "path fails //",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -493,13 +529,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`,
|
||||
},
|
||||
{
|
||||
name: "path no empty step",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -510,12 +545,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
|
||||
}, {
|
||||
name: "path no empty step 2",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -526,13 +560,12 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`,
|
||||
},
|
||||
{
|
||||
name: "path no non-subdomain",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
|
@ -543,49 +576,45 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`,
|
||||
},
|
||||
{
|
||||
name: "timeout seconds cannot be greater than 30",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
TimeoutSeconds: int32Ptr(31),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`,
|
||||
},
|
||||
{
|
||||
name: "timeout seconds cannot be smaller than 1",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
TimeoutSeconds: int32Ptr(0),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`,
|
||||
},
|
||||
{
|
||||
name: "timeout seconds must be positive",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
TimeoutSeconds: int32Ptr(-1),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`,
|
||||
},
|
||||
{
|
||||
name: "valid timeout seconds",
|
||||
config: newValidatingWebhookConfiguration(
|
||||
[]admissionregistration.Webhook{
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
|
@ -601,7 +630,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
ClientConfig: validClientConfig,
|
||||
TimeoutSeconds: int32Ptr(30),
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -621,3 +650,102 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
|
||||
validClientConfig := admissionregistration.WebhookClientConfig{
|
||||
URL: strPtr("https://example.com"),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
oldconfig *admissionregistration.ValidatingWebhookConfiguration
|
||||
config *admissionregistration.ValidatingWebhookConfiguration
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "should pass on valid new AdmissionReviewVersion",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
},
|
||||
}, true),
|
||||
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "should pass on invalid AdmissionReviewVersion with invalid previous versions",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"},
|
||||
},
|
||||
}, true),
|
||||
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"invalid-v0"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "should fail on invalid AdmissionReviewVersion with valid previous versions",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"invalid-v1"},
|
||||
},
|
||||
}, true),
|
||||
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `Invalid value: []string{"invalid-v1"}`,
|
||||
},
|
||||
{
|
||||
name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
AdmissionReviewVersions: []string{"invalid-v1"},
|
||||
},
|
||||
}, true),
|
||||
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
},
|
||||
}, false),
|
||||
expectedError: `Invalid value: []string{"invalid-v1"}`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
errs := ValidateValidatingWebhookConfigurationUpdate(test.config, test.oldconfig)
|
||||
err := errs.ToAggregate()
|
||||
if err != nil {
|
||||
if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
|
||||
t.Errorf("expected to contain %s, got %s", e, a)
|
||||
}
|
||||
} else {
|
||||
if test.expectedError != "" {
|
||||
t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,7 @@ func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
|
|||
|
||||
// ValidateUpdate is the default update validation for an end user.
|
||||
func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
validationErrorList := validation.ValidateMutatingWebhookConfiguration(obj.(*admissionregistration.MutatingWebhookConfiguration))
|
||||
updateErrorList := validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
|
||||
return append(validationErrorList, updateErrorList...)
|
||||
return validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
|
||||
}
|
||||
|
||||
// AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should
|
||||
|
|
|
@ -63,8 +63,7 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte
|
|||
|
||||
// Validate validates a new validatingWebhookConfiguration.
|
||||
func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
ic := obj.(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
return validation.ValidateValidatingWebhookConfiguration(ic)
|
||||
return validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration))
|
||||
}
|
||||
|
||||
// Canonicalize normalizes the object after validation.
|
||||
|
@ -78,9 +77,7 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
|
|||
|
||||
// ValidateUpdate is the default update validation for an end user.
|
||||
func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration))
|
||||
updateErrorList := validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
|
||||
return append(validationErrorList, updateErrorList...)
|
||||
return validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
|
||||
}
|
||||
|
||||
// AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should
|
||||
|
|
|
@ -248,6 +248,17 @@ type Webhook struct {
|
|||
// Default to 30 seconds.
|
||||
// +optional
|
||||
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
|
||||
|
|
|
@ -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
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
|
|
|
@ -212,6 +212,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create},
|
||||
}},
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
},
|
||||
|
@ -222,6 +223,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("allow"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"},
|
||||
|
@ -233,6 +235,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("disallow"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ErrorContains: "without explanation",
|
||||
},
|
||||
|
@ -243,6 +246,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("disallowReason"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
|
||||
ErrorContains: "you shall not pass",
|
||||
|
@ -260,6 +264,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Operator: metav1.LabelSelectorOpIn,
|
||||
}},
|
||||
},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
|
||||
ExpectAllow: true,
|
||||
|
@ -277,6 +282,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
}},
|
||||
},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
},
|
||||
|
@ -288,18 +294,21 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
|
||||
ExpectAllow: true,
|
||||
|
@ -311,16 +320,19 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("internalErr"),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
Rules: matchEverythingRules,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
Rules: matchEverythingRules,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
Rules: matchEverythingRules,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: false,
|
||||
},
|
||||
|
@ -332,18 +344,21 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyFail,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyFail,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfgSVC("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyFail,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: false,
|
||||
},
|
||||
|
@ -354,6 +369,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgURL("allow"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"},
|
||||
|
@ -365,6 +381,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgURL("disallow"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ErrorContains: "without explanation",
|
||||
}, {
|
||||
|
@ -375,6 +392,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
FailurePolicy: &policyIgnore,
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
},
|
||||
|
@ -386,6 +404,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
FailurePolicy: &policyFail,
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ErrorContains: "Webhook response was absent",
|
||||
},
|
||||
|
@ -399,6 +418,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
}},
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsSome,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ExpectAllow: true,
|
||||
|
@ -411,6 +431,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsUnknown,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ErrorContains: "does not support dry run",
|
||||
|
@ -423,6 +444,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsNone,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ExpectAllow: true,
|
||||
|
@ -436,6 +458,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsSome,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ErrorContains: "does not support dry run",
|
||||
|
@ -448,6 +471,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsNoneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ExpectAllow: true,
|
||||
|
@ -460,6 +484,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgURL("invalidAnnotation"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
},
|
||||
|
@ -480,6 +505,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("removeLabel"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
AdditionalLabels: map[string]string{"remove": "me"},
|
||||
|
@ -493,6 +519,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("addLabel"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectLabels: map[string]string{"pod.name": "my-pod", "added": "test"},
|
||||
|
@ -504,6 +531,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("addLabel"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsCRD: true,
|
||||
ExpectAllow: true,
|
||||
|
@ -516,6 +544,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("removeLabel"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsCRD: true,
|
||||
ExpectAllow: true,
|
||||
|
@ -530,6 +559,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
ClientConfig: ccfgSVC("invalidMutation"),
|
||||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ErrorContains: "invalid character",
|
||||
},
|
||||
|
@ -541,6 +571,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
|
|||
Rules: matchEverythingRules,
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
SideEffects: &sideEffectsUnknown,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
IsDryRun: true,
|
||||
ErrorContains: "does not support dry run",
|
||||
|
@ -572,6 +603,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
|
|||
Rules: newMatchEverythingRules(),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectCacheMiss: true,
|
||||
|
@ -584,6 +616,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
|
|||
Rules: newMatchEverythingRules(),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectCacheMiss: true,
|
||||
|
@ -596,6 +629,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
|
|||
Rules: newMatchEverythingRules(),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectCacheMiss: false,
|
||||
|
@ -608,6 +642,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
|
|||
Rules: newMatchEverythingRules(),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectCacheMiss: true,
|
||||
|
@ -620,6 +655,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
|
|||
Rules: newMatchEverythingRules(),
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
FailurePolicy: &policyIgnore,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
ExpectAllow: true,
|
||||
ExpectCacheMiss: false,
|
||||
|
|
|
@ -40,3 +40,13 @@ func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
|
|||
}
|
||||
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,
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
|
|
Loading…
Reference in New Issue