2019-01-12 04:58:27 +00:00
/ *
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"
2019-04-07 17:07:55 +00:00
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/util/webhook"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
2019-04-07 17:07:55 +00:00
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
2019-01-12 04:58:27 +00:00
)
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/*
2019-04-07 17:07:55 +00:00
subResourcesWithWildcardResource := sets . String { }
2019-01-12 04:58:27 +00:00
// */*
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 ) ) )
}
2019-04-07 17:07:55 +00:00
if _ , ok := subResourcesWithWildcardResource [ sub ] ; ok {
2019-01-12 04:58:27 +00:00
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 == "*" {
2019-04-07 17:07:55 +00:00
subResourcesWithWildcardResource [ sub ] = struct { } { }
2019-01-12 04:58:27 +00:00
}
}
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
}
2019-04-07 17:07:55 +00:00
var validScopes = sets . NewString (
string ( admissionregistration . ClusterScope ) ,
string ( admissionregistration . NamespacedScope ) ,
string ( admissionregistration . AllScopes ) ,
)
2019-01-12 04:58:27 +00:00
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" ) ) ... )
}
2019-04-07 17:07:55 +00:00
if rule . Scope != nil && ! validScopes . Has ( string ( * rule . Scope ) ) {
allErrors = append ( allErrors , field . NotSupported ( fldPath . Child ( "scope" ) , * rule . Scope , validScopes . List ( ) ) )
}
2019-01-12 04:58:27 +00:00
return allErrors
}
2019-04-07 17:07:55 +00:00
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
2019-01-12 04:58:27 +00:00
}
func ValidateValidatingWebhookConfiguration ( e * admissionregistration . ValidatingWebhookConfiguration ) field . ErrorList {
2019-04-07 17:07:55 +00:00
return validateValidatingWebhookConfiguration ( e , true )
}
func validateValidatingWebhookConfiguration ( e * admissionregistration . ValidatingWebhookConfiguration , requireRecognizedVersion bool ) field . ErrorList {
2019-01-12 04:58:27 +00:00
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 ) ) ... )
2019-04-07 17:07:55 +00:00
allErrors = append ( allErrors , validateAdmissionReviewVersions ( hook . AdmissionReviewVersions , requireRecognizedVersion , field . NewPath ( "webhooks" ) . Index ( i ) . Child ( "admissionReviewVersions" ) ) ... )
2019-01-12 04:58:27 +00:00
}
return allErrors
}
func ValidateMutatingWebhookConfiguration ( e * admissionregistration . MutatingWebhookConfiguration ) field . ErrorList {
2019-04-07 17:07:55 +00:00
return validateMutatingWebhookConfiguration ( e , true )
}
func validateMutatingWebhookConfiguration ( e * admissionregistration . MutatingWebhookConfiguration , requireRecognizedVersion bool ) field . ErrorList {
2019-01-12 04:58:27 +00:00
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 ) ) ... )
2019-04-07 17:07:55 +00:00
allErrors = append ( allErrors , validateAdmissionReviewVersions ( hook . AdmissionReviewVersions , requireRecognizedVersion , field . NewPath ( "webhooks" ) . Index ( i ) . Child ( "admissionReviewVersions" ) ) ... )
2019-01-12 04:58:27 +00:00
}
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 ( ) ) )
}
2019-04-07 17:07:55 +00:00
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" ) )
}
2019-01-12 04:58:27 +00:00
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
}
2019-04-07 17:07:55 +00:00
// 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
}
2019-01-12 04:58:27 +00:00
func ValidateValidatingWebhookConfigurationUpdate ( newC , oldC * admissionregistration . ValidatingWebhookConfiguration ) field . ErrorList {
2019-04-07 17:07:55 +00:00
return validateValidatingWebhookConfiguration ( newC , hasAcceptedAdmissionReviewVersions ( oldC . Webhooks ) )
2019-01-12 04:58:27 +00:00
}
func ValidateMutatingWebhookConfigurationUpdate ( newC , oldC * admissionregistration . MutatingWebhookConfiguration ) field . ErrorList {
2019-04-07 17:07:55 +00:00
return validateMutatingWebhookConfiguration ( newC , hasAcceptedAdmissionReviewVersions ( oldC . Webhooks ) )
2019-01-12 04:58:27 +00:00
}