
Automatic merge from submit-queue (batch tested with PRs 51583, 51283, 51374, 51690, 51716) Unify initializer name validation Unify the validation rules on initializer names. Fix https://github.com/kubernetes/kubernetes/issues/51843. ```release-note Action required: validation rule on metadata.initializers.pending[x].name is tightened. The initializer name needs to contain at least three segments separated by dots. If you create objects with pending initializers, (i.e., not relying on apiserver adding pending initializers according to initializerconfiguration), you need to update the initializer name in existing objects and in configuration files to comply to the new validation rule. ```
227 lines
8.5 KiB
Go
227 lines
8.5 KiB
Go
/*
|
|
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"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
|
)
|
|
|
|
func ValidateInitializerConfiguration(ic *admissionregistration.InitializerConfiguration) field.ErrorList {
|
|
allErrors := genericvalidation.ValidateObjectMeta(&ic.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
|
for i, initializer := range ic.Initializers {
|
|
allErrors = append(allErrors, validateInitializer(&initializer, field.NewPath("initializers").Index(i))...)
|
|
}
|
|
return allErrors
|
|
}
|
|
|
|
func validateInitializer(initializer *admissionregistration.Initializer, fldPath *field.Path) field.ErrorList {
|
|
var allErrors field.ErrorList
|
|
// initlializer.Name must be fully qualified
|
|
allErrors = append(allErrors, validation.IsFullyQualifiedName(fldPath.Child("name"), initializer.Name)...)
|
|
|
|
for i, rule := range initializer.Rules {
|
|
notAllowSubresources := false
|
|
allErrors = append(allErrors, validateRule(&rule, fldPath.Child("rules").Index(i), notAllowSubresources)...)
|
|
}
|
|
return allErrors
|
|
}
|
|
|
|
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/*
|
|
subResoucesWithWildcardResource := 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 := subResoucesWithWildcardResource[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 == "*" {
|
|
subResoucesWithWildcardResource[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
|
|
}
|
|
|
|
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"))...)
|
|
}
|
|
return allErrors
|
|
}
|
|
|
|
func ValidateInitializerConfigurationUpdate(newIC, oldIC *admissionregistration.InitializerConfiguration) field.ErrorList {
|
|
return ValidateInitializerConfiguration(newIC)
|
|
}
|
|
|
|
func ValidateExternalAdmissionHookConfiguration(e *admissionregistration.ExternalAdmissionHookConfiguration) field.ErrorList {
|
|
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
|
for i, hook := range e.ExternalAdmissionHooks {
|
|
allErrors = append(allErrors, validateExternalAdmissionHook(&hook, field.NewPath("externalAdmissionHooks").Index(i))...)
|
|
}
|
|
return allErrors
|
|
}
|
|
|
|
func validateExternalAdmissionHook(hook *admissionregistration.ExternalAdmissionHook, fldPath *field.Path) field.ErrorList {
|
|
var allErrors field.ErrorList
|
|
// hook.Name must be fully qualified
|
|
allErrors = append(allErrors, validation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
|
|
|
|
for i, rule := range hook.Rules {
|
|
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
|
|
}
|
|
// TODO: relax the validation rule when admissionregistration is beta.
|
|
if hook.FailurePolicy != nil && *hook.FailurePolicy != admissionregistration.Ignore {
|
|
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, []string{string(admissionregistration.Ignore)}))
|
|
}
|
|
return allErrors
|
|
}
|
|
|
|
var supportedOperations = sets.NewString(
|
|
string(admissionregistration.OperationAll),
|
|
string(admissionregistration.Create),
|
|
string(admissionregistration.Update),
|
|
string(admissionregistration.Delete),
|
|
string(admissionregistration.Connect),
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
func ValidateExternalAdmissionHookConfigurationUpdate(newC, oldC *admissionregistration.ExternalAdmissionHookConfiguration) field.ErrorList {
|
|
return ValidateExternalAdmissionHookConfiguration(newC)
|
|
}
|