mirror of https://github.com/k3s-io/k3s
269 lines
10 KiB
Go
269 lines
10 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"
|
||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||
|
"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"
|
||
|
)
|
||
|
|
||
|
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 ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) 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))...)
|
||
|
}
|
||
|
return allErrors
|
||
|
}
|
||
|
|
||
|
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) 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))...)
|
||
|
}
|
||
|
return allErrors
|
||
|
}
|
||
|
|
||
|
func validateWebhook(hook *admissionregistration.Webhook, 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))...)
|
||
|
}
|
||
|
if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
|
||
|
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
|
||
|
}
|
||
|
if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
|
||
|
allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
|
||
|
}
|
||
|
|
||
|
if hook.NamespaceSelector != nil {
|
||
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
|
||
|
}
|
||
|
|
||
|
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)...)
|
||
|
}
|
||
|
return allErrors
|
||
|
}
|
||
|
|
||
|
var supportedFailurePolicies = sets.NewString(
|
||
|
string(admissionregistration.Ignore),
|
||
|
string(admissionregistration.Fail),
|
||
|
)
|
||
|
|
||
|
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),
|
||
|
)
|
||
|
|
||
|
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 ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||
|
return ValidateValidatingWebhookConfiguration(newC)
|
||
|
}
|
||
|
|
||
|
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||
|
return ValidateMutatingWebhookConfiguration(newC)
|
||
|
}
|