2019-01-12 04:58:27 +00:00
/ *
Copyright 2016 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 (
"k8s.io/apimachinery/pkg/api/validation/path"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// ValidateRBACName is exported to allow types outside of the RBAC API group to reuse this validation logic
// Minimal validation of names for roles and bindings. Identical to the validation for Openshift. See:
2021-07-02 08:43:15 +00:00
// * https://github.com/kubernetes/kubernetes/blob/60db507b279ce45bd16ea3db49bf181f2aeb3c3d/pkg/api/validation/name.go
// * https://github.com/openshift/origin/blob/388478c40e751c4295dcb9a44dd69e5ac65d0e3b/pkg/api/helpers.go
2019-01-12 04:58:27 +00:00
func ValidateRBACName ( name string , prefix bool ) [ ] string {
return path . IsValidPathSegmentName ( name )
}
func ValidateRole ( role * rbac . Role ) field . ErrorList {
allErrs := field . ErrorList { }
allErrs = append ( allErrs , validation . ValidateObjectMeta ( & role . ObjectMeta , true , ValidateRBACName , field . NewPath ( "metadata" ) ) ... )
for i , rule := range role . Rules {
if err := ValidatePolicyRule ( rule , true , field . NewPath ( "rules" ) . Index ( i ) ) ; err != nil {
allErrs = append ( allErrs , err ... )
}
}
if len ( allErrs ) != 0 {
return allErrs
}
return nil
}
func ValidateRoleUpdate ( role * rbac . Role , oldRole * rbac . Role ) field . ErrorList {
allErrs := ValidateRole ( role )
allErrs = append ( allErrs , validation . ValidateObjectMetaUpdate ( & role . ObjectMeta , & oldRole . ObjectMeta , field . NewPath ( "metadata" ) ) ... )
return allErrs
}
func ValidateClusterRole ( role * rbac . ClusterRole ) field . ErrorList {
allErrs := field . ErrorList { }
allErrs = append ( allErrs , validation . ValidateObjectMeta ( & role . ObjectMeta , false , ValidateRBACName , field . NewPath ( "metadata" ) ) ... )
for i , rule := range role . Rules {
if err := ValidatePolicyRule ( rule , false , field . NewPath ( "rules" ) . Index ( i ) ) ; err != nil {
allErrs = append ( allErrs , err ... )
}
}
if role . AggregationRule != nil {
if len ( role . AggregationRule . ClusterRoleSelectors ) == 0 {
allErrs = append ( allErrs , field . Required ( field . NewPath ( "aggregationRule" , "clusterRoleSelectors" ) , "at least one clusterRoleSelector required if aggregationRule is non-nil" ) )
}
for i , selector := range role . AggregationRule . ClusterRoleSelectors {
fieldPath := field . NewPath ( "aggregationRule" , "clusterRoleSelectors" ) . Index ( i )
allErrs = append ( allErrs , unversionedvalidation . ValidateLabelSelector ( & selector , fieldPath ) ... )
selector , err := metav1 . LabelSelectorAsSelector ( & selector )
if err != nil {
allErrs = append ( allErrs , field . Invalid ( fieldPath , selector , "invalid label selector." ) )
}
}
}
if len ( allErrs ) != 0 {
return allErrs
}
return nil
}
func ValidateClusterRoleUpdate ( role * rbac . ClusterRole , oldRole * rbac . ClusterRole ) field . ErrorList {
allErrs := ValidateClusterRole ( role )
allErrs = append ( allErrs , validation . ValidateObjectMetaUpdate ( & role . ObjectMeta , & oldRole . ObjectMeta , field . NewPath ( "metadata" ) ) ... )
return allErrs
}
// ValidatePolicyRule is exported to allow types outside of the RBAC API group to embed a rbac.PolicyRule and reuse this validation logic
func ValidatePolicyRule ( rule rbac . PolicyRule , isNamespaced bool , fldPath * field . Path ) field . ErrorList {
allErrs := field . ErrorList { }
if len ( rule . Verbs ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "verbs" ) , "verbs must contain at least one value" ) )
}
if len ( rule . NonResourceURLs ) > 0 {
if isNamespaced {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "nonResourceURLs" ) , rule . NonResourceURLs , "namespaced rules cannot apply to non-resource URLs" ) )
}
if len ( rule . APIGroups ) > 0 || len ( rule . Resources ) > 0 || len ( rule . ResourceNames ) > 0 {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "nonResourceURLs" ) , rule . NonResourceURLs , "rules cannot apply to both regular resources and non-resource URLs" ) )
}
return allErrs
}
if len ( rule . APIGroups ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "apiGroups" ) , "resource rules must supply at least one api group" ) )
}
if len ( rule . Resources ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "resources" ) , "resource rules must supply at least one resource" ) )
}
return allErrs
}
func ValidateRoleBinding ( roleBinding * rbac . RoleBinding ) field . ErrorList {
allErrs := field . ErrorList { }
allErrs = append ( allErrs , validation . ValidateObjectMeta ( & roleBinding . ObjectMeta , true , ValidateRBACName , field . NewPath ( "metadata" ) ) ... )
// TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking
// advantage of the binding infrastructure
if roleBinding . RoleRef . APIGroup != rbac . GroupName {
allErrs = append ( allErrs , field . NotSupported ( field . NewPath ( "roleRef" , "apiGroup" ) , roleBinding . RoleRef . APIGroup , [ ] string { rbac . GroupName } ) )
}
switch roleBinding . RoleRef . Kind {
case "Role" , "ClusterRole" :
default :
allErrs = append ( allErrs , field . NotSupported ( field . NewPath ( "roleRef" , "kind" ) , roleBinding . RoleRef . Kind , [ ] string { "Role" , "ClusterRole" } ) )
}
if len ( roleBinding . RoleRef . Name ) == 0 {
allErrs = append ( allErrs , field . Required ( field . NewPath ( "roleRef" , "name" ) , "" ) )
} else {
for _ , msg := range ValidateRBACName ( roleBinding . RoleRef . Name , false ) {
allErrs = append ( allErrs , field . Invalid ( field . NewPath ( "roleRef" , "name" ) , roleBinding . RoleRef . Name , msg ) )
}
}
subjectsPath := field . NewPath ( "subjects" )
for i , subject := range roleBinding . Subjects {
allErrs = append ( allErrs , ValidateRoleBindingSubject ( subject , true , subjectsPath . Index ( i ) ) ... )
}
return allErrs
}
func ValidateRoleBindingUpdate ( roleBinding * rbac . RoleBinding , oldRoleBinding * rbac . RoleBinding ) field . ErrorList {
allErrs := ValidateRoleBinding ( roleBinding )
allErrs = append ( allErrs , validation . ValidateObjectMetaUpdate ( & roleBinding . ObjectMeta , & oldRoleBinding . ObjectMeta , field . NewPath ( "metadata" ) ) ... )
if oldRoleBinding . RoleRef != roleBinding . RoleRef {
allErrs = append ( allErrs , field . Invalid ( field . NewPath ( "roleRef" ) , roleBinding . RoleRef , "cannot change roleRef" ) )
}
return allErrs
}
func ValidateClusterRoleBinding ( roleBinding * rbac . ClusterRoleBinding ) field . ErrorList {
allErrs := field . ErrorList { }
allErrs = append ( allErrs , validation . ValidateObjectMeta ( & roleBinding . ObjectMeta , false , ValidateRBACName , field . NewPath ( "metadata" ) ) ... )
// TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking
// advantage of the binding infrastructure
if roleBinding . RoleRef . APIGroup != rbac . GroupName {
allErrs = append ( allErrs , field . NotSupported ( field . NewPath ( "roleRef" , "apiGroup" ) , roleBinding . RoleRef . APIGroup , [ ] string { rbac . GroupName } ) )
}
switch roleBinding . RoleRef . Kind {
case "ClusterRole" :
default :
allErrs = append ( allErrs , field . NotSupported ( field . NewPath ( "roleRef" , "kind" ) , roleBinding . RoleRef . Kind , [ ] string { "ClusterRole" } ) )
}
if len ( roleBinding . RoleRef . Name ) == 0 {
allErrs = append ( allErrs , field . Required ( field . NewPath ( "roleRef" , "name" ) , "" ) )
} else {
for _ , msg := range ValidateRBACName ( roleBinding . RoleRef . Name , false ) {
allErrs = append ( allErrs , field . Invalid ( field . NewPath ( "roleRef" , "name" ) , roleBinding . RoleRef . Name , msg ) )
}
}
subjectsPath := field . NewPath ( "subjects" )
for i , subject := range roleBinding . Subjects {
allErrs = append ( allErrs , ValidateRoleBindingSubject ( subject , false , subjectsPath . Index ( i ) ) ... )
}
return allErrs
}
func ValidateClusterRoleBindingUpdate ( roleBinding * rbac . ClusterRoleBinding , oldRoleBinding * rbac . ClusterRoleBinding ) field . ErrorList {
allErrs := ValidateClusterRoleBinding ( roleBinding )
allErrs = append ( allErrs , validation . ValidateObjectMetaUpdate ( & roleBinding . ObjectMeta , & oldRoleBinding . ObjectMeta , field . NewPath ( "metadata" ) ) ... )
if oldRoleBinding . RoleRef != roleBinding . RoleRef {
allErrs = append ( allErrs , field . Invalid ( field . NewPath ( "roleRef" ) , roleBinding . RoleRef , "cannot change roleRef" ) )
}
return allErrs
}
// ValidateRoleBindingSubject is exported to allow types outside of the RBAC API group to embed a rbac.Subject and reuse this validation logic
func ValidateRoleBindingSubject ( subject rbac . Subject , isNamespaced bool , fldPath * field . Path ) field . ErrorList {
allErrs := field . ErrorList { }
if len ( subject . Name ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "name" ) , "" ) )
}
switch subject . Kind {
case rbac . ServiceAccountKind :
if len ( subject . Name ) > 0 {
for _ , msg := range validation . ValidateServiceAccountName ( subject . Name , false ) {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "name" ) , subject . Name , msg ) )
}
}
if len ( subject . APIGroup ) > 0 {
allErrs = append ( allErrs , field . NotSupported ( fldPath . Child ( "apiGroup" ) , subject . APIGroup , [ ] string { "" } ) )
}
if ! isNamespaced && len ( subject . Namespace ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "namespace" ) , "" ) )
}
case rbac . UserKind :
// TODO(ericchiang): What other restrictions on user name are there?
if subject . APIGroup != rbac . GroupName {
allErrs = append ( allErrs , field . NotSupported ( fldPath . Child ( "apiGroup" ) , subject . APIGroup , [ ] string { rbac . GroupName } ) )
}
case rbac . GroupKind :
// TODO(ericchiang): What other restrictions on group name are there?
if subject . APIGroup != rbac . GroupName {
allErrs = append ( allErrs , field . NotSupported ( fldPath . Child ( "apiGroup" ) , subject . APIGroup , [ ] string { rbac . GroupName } ) )
}
default :
allErrs = append ( allErrs , field . NotSupported ( fldPath . Child ( "kind" ) , subject . Kind , [ ] string { rbac . ServiceAccountKind , rbac . UserKind , rbac . GroupKind } ) )
}
return allErrs
}