123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- /*
- Copyright 2017 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 validation
- import (
- "fmt"
- "strings"
- genericvalidation "k8s.io/apimachinery/pkg/api/validation"
- metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
- "k8s.io/apimachinery/pkg/util/sets"
- 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 {
- for _, s := range slice {
- if s == "*" {
- return true
- }
- }
- return false
- }
- func validateResources(resources []string, fldPath *field.Path) field.ErrorList {
- var allErrors field.ErrorList
- if len(resources) == 0 {
- allErrors = append(allErrors, field.Required(fldPath, ""))
- }
- // */x
- resourcesWithWildcardSubresoures := sets.String{}
- // x/*
- subResourcesWithWildcardResource := sets.String{}
- // */*
- hasDoubleWildcard := false
- // *
- hasSingleWildcard := false
- // x
- hasResourceWithoutSubresource := false
- for i, resSub := range resources {
- if resSub == "" {
- allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
- continue
- }
- if resSub == "*/*" {
- hasDoubleWildcard = true
- }
- if resSub == "*" {
- hasSingleWildcard = true
- }
- parts := strings.SplitN(resSub, "/", 2)
- if len(parts) == 1 {
- hasResourceWithoutSubresource = resSub != "*"
- continue
- }
- res, sub := parts[0], parts[1]
- if _, ok := resourcesWithWildcardSubresoures[res]; ok {
- allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub)))
- }
- if _, ok := subResourcesWithWildcardResource[sub]; ok {
- allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub)))
- }
- if sub == "*" {
- resourcesWithWildcardSubresoures[res] = struct{}{}
- }
- if res == "*" {
- subResourcesWithWildcardResource[sub] = struct{}{}
- }
- }
- if len(resources) > 1 && hasDoubleWildcard {
- allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources"))
- }
- if hasSingleWildcard && hasResourceWithoutSubresource {
- allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources"))
- }
- return allErrors
- }
- func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList {
- var allErrors field.ErrorList
- if len(resources) == 0 {
- allErrors = append(allErrors, field.Required(fldPath, ""))
- }
- for i, resource := range resources {
- if resource == "" {
- allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
- }
- if strings.Contains(resource, "/") {
- allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources"))
- }
- }
- if len(resources) > 1 && hasWildcard(resources) {
- allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources"))
- }
- return allErrors
- }
- var validScopes = sets.NewString(
- string(admissionregistration.ClusterScope),
- string(admissionregistration.NamespacedScope),
- string(admissionregistration.AllScopes),
- )
- func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList {
- var allErrors field.ErrorList
- if len(rule.APIGroups) == 0 {
- allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), ""))
- }
- if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) {
- allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups"))
- }
- // Note: group could be empty, e.g., the legacy "v1" API
- if len(rule.APIVersions) == 0 {
- allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), ""))
- }
- if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) {
- allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions"))
- }
- for i, version := range rule.APIVersions {
- if version == "" {
- allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), ""))
- }
- }
- if allowSubResource {
- allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...)
- } else {
- allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...)
- }
- if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List()))
- }
- 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, validateValidatingWebhook(&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, validateMutatingWebhook(&hook, field.NewPath("webhooks").Index(i))...)
- allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
- }
- return allErrors
- }
- func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, fldPath *field.Path) field.ErrorList {
- var allErrors field.ErrorList
- // hook.Name must be fully qualified
- allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
- for i, rule := range hook.Rules {
- allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
- }
- if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
- }
- if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
- }
- if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
- }
- if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
- allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
- }
- if hook.NamespaceSelector != nil {
- allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
- }
- if hook.ObjectSelector != nil {
- allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
- }
- cc := hook.ClientConfig
- switch {
- case (cc.URL == nil) == (cc.Service == nil):
- allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
- case cc.URL != nil:
- allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
- case cc.Service != nil:
- allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
- }
- return allErrors
- }
- func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, fldPath *field.Path) field.ErrorList {
- var allErrors field.ErrorList
- // hook.Name must be fully qualified
- allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
- for i, rule := range hook.Rules {
- allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
- }
- if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
- }
- if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
- }
- if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
- }
- if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
- allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
- }
- if hook.NamespaceSelector != nil {
- allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
- }
- if hook.ObjectSelector != nil {
- allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
- }
- if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List()))
- }
- cc := hook.ClientConfig
- switch {
- case (cc.URL == nil) == (cc.Service == nil):
- allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
- case cc.URL != nil:
- allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
- case cc.Service != nil:
- allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
- }
- return allErrors
- }
- var supportedFailurePolicies = sets.NewString(
- string(admissionregistration.Ignore),
- string(admissionregistration.Fail),
- )
- var supportedMatchPolicies = sets.NewString(
- string(admissionregistration.Exact),
- string(admissionregistration.Equivalent),
- )
- var supportedSideEffectClasses = sets.NewString(
- string(admissionregistration.SideEffectClassUnknown),
- string(admissionregistration.SideEffectClassNone),
- string(admissionregistration.SideEffectClassSome),
- string(admissionregistration.SideEffectClassNoneOnDryRun),
- )
- var supportedOperations = sets.NewString(
- string(admissionregistration.OperationAll),
- string(admissionregistration.Create),
- string(admissionregistration.Update),
- string(admissionregistration.Delete),
- string(admissionregistration.Connect),
- )
- var supportedReinvocationPolicies = sets.NewString(
- string(admissionregistration.NeverReinvocationPolicy),
- string(admissionregistration.IfNeededReinvocationPolicy),
- )
- func hasWildcardOperation(operations []admissionregistration.OperationType) bool {
- for _, o := range operations {
- if o == admissionregistration.OperationAll {
- return true
- }
- }
- return false
- }
- func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList {
- var allErrors field.ErrorList
- if len(ruleWithOperations.Operations) == 0 {
- allErrors = append(allErrors, field.Required(fldPath.Child("operations"), ""))
- }
- if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) {
- allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations"))
- }
- for i, operation := range ruleWithOperations.Operations {
- if !supportedOperations.Has(string(operation)) {
- allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List()))
- }
- }
- allowSubResource := true
- allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...)
- return allErrors
- }
- // mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
- // admission review version this apiserver accepts.
- func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) 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
- }
- // validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
- // admission review version this apiserver accepts.
- func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) 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, validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
- }
- func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
- return validateMutatingWebhookConfiguration(newC, mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
- }
|