...

Source file src/k8s.io/kubernetes/pkg/apis/rbac/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/rbac/validation

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"k8s.io/apimachinery/pkg/api/validation/path"
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
    23  	"k8s.io/apimachinery/pkg/util/validation/field"
    24  	"k8s.io/kubernetes/pkg/apis/core/validation"
    25  	"k8s.io/kubernetes/pkg/apis/rbac"
    26  )
    27  
    28  // ValidateRBACName is exported to allow types outside of the RBAC API group to reuse this validation logic
    29  // Minimal validation of names for roles and bindings. Identical to the validation for Openshift. See:
    30  // * https://github.com/kubernetes/kubernetes/blob/60db507b279ce45bd16ea3db49bf181f2aeb3c3d/pkg/api/validation/name.go
    31  // * https://github.com/openshift/origin/blob/388478c40e751c4295dcb9a44dd69e5ac65d0e3b/pkg/api/helpers.go
    32  func ValidateRBACName(name string, prefix bool) []string {
    33  	return path.IsValidPathSegmentName(name)
    34  }
    35  
    36  func ValidateRole(role *rbac.Role) field.ErrorList {
    37  	allErrs := field.ErrorList{}
    38  	allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...)
    39  
    40  	for i, rule := range role.Rules {
    41  		if err := ValidatePolicyRule(rule, true, field.NewPath("rules").Index(i)); err != nil {
    42  			allErrs = append(allErrs, err...)
    43  		}
    44  	}
    45  	if len(allErrs) != 0 {
    46  		return allErrs
    47  	}
    48  	return nil
    49  }
    50  
    51  func ValidateRoleUpdate(role *rbac.Role, oldRole *rbac.Role) field.ErrorList {
    52  	allErrs := ValidateRole(role)
    53  	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
    54  
    55  	return allErrs
    56  }
    57  
    58  type ClusterRoleValidationOptions struct {
    59  	AllowInvalidLabelValueInSelector bool
    60  }
    61  
    62  func ValidateClusterRole(role *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList {
    63  	allErrs := field.ErrorList{}
    64  	allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
    65  
    66  	for i, rule := range role.Rules {
    67  		if err := ValidatePolicyRule(rule, false, field.NewPath("rules").Index(i)); err != nil {
    68  			allErrs = append(allErrs, err...)
    69  		}
    70  	}
    71  
    72  	labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector}
    73  
    74  	if role.AggregationRule != nil {
    75  		if len(role.AggregationRule.ClusterRoleSelectors) == 0 {
    76  			allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil"))
    77  		}
    78  		for i, selector := range role.AggregationRule.ClusterRoleSelectors {
    79  			fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i)
    80  			allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, labelSelectorValidationOptions, fieldPath)...)
    81  
    82  			selector, err := metav1.LabelSelectorAsSelector(&selector)
    83  			if err != nil {
    84  				allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector."))
    85  			}
    86  		}
    87  	}
    88  
    89  	if len(allErrs) != 0 {
    90  		return allErrs
    91  	}
    92  	return nil
    93  }
    94  
    95  func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList {
    96  	allErrs := ValidateClusterRole(role, opts)
    97  	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
    98  
    99  	return allErrs
   100  }
   101  
   102  // ValidatePolicyRule is exported to allow types outside of the RBAC API group to embed a rbac.PolicyRule and reuse this validation logic
   103  func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList {
   104  	allErrs := field.ErrorList{}
   105  	if len(rule.Verbs) == 0 {
   106  		allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value"))
   107  	}
   108  
   109  	if len(rule.NonResourceURLs) > 0 {
   110  		if isNamespaced {
   111  			allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "namespaced rules cannot apply to non-resource URLs"))
   112  		}
   113  		if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 || len(rule.ResourceNames) > 0 {
   114  			allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs"))
   115  		}
   116  		return allErrs
   117  	}
   118  
   119  	if len(rule.APIGroups) == 0 {
   120  		allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group"))
   121  	}
   122  	if len(rule.Resources) == 0 {
   123  		allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource"))
   124  	}
   125  	return allErrs
   126  }
   127  
   128  func ValidateRoleBinding(roleBinding *rbac.RoleBinding) field.ErrorList {
   129  	allErrs := field.ErrorList{}
   130  	allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...)
   131  
   132  	// TODO allow multiple API groups.  For now, restrict to one, but I can envision other experimental roles in other groups taking
   133  	// advantage of the binding infrastructure
   134  	if roleBinding.RoleRef.APIGroup != rbac.GroupName {
   135  		allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName}))
   136  	}
   137  
   138  	switch roleBinding.RoleRef.Kind {
   139  	case "Role", "ClusterRole":
   140  	default:
   141  		allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"Role", "ClusterRole"}))
   142  
   143  	}
   144  
   145  	if len(roleBinding.RoleRef.Name) == 0 {
   146  		allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), ""))
   147  	} else {
   148  		for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) {
   149  			allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg))
   150  		}
   151  	}
   152  
   153  	subjectsPath := field.NewPath("subjects")
   154  	for i, subject := range roleBinding.Subjects {
   155  		allErrs = append(allErrs, ValidateRoleBindingSubject(subject, true, subjectsPath.Index(i))...)
   156  	}
   157  
   158  	return allErrs
   159  }
   160  
   161  func ValidateRoleBindingUpdate(roleBinding *rbac.RoleBinding, oldRoleBinding *rbac.RoleBinding) field.ErrorList {
   162  	allErrs := ValidateRoleBinding(roleBinding)
   163  	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...)
   164  
   165  	if oldRoleBinding.RoleRef != roleBinding.RoleRef {
   166  		allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef"))
   167  	}
   168  
   169  	return allErrs
   170  }
   171  
   172  func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList {
   173  	allErrs := field.ErrorList{}
   174  	allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
   175  
   176  	// TODO allow multiple API groups.  For now, restrict to one, but I can envision other experimental roles in other groups taking
   177  	// advantage of the binding infrastructure
   178  	if roleBinding.RoleRef.APIGroup != rbac.GroupName {
   179  		allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName}))
   180  	}
   181  
   182  	switch roleBinding.RoleRef.Kind {
   183  	case "ClusterRole":
   184  	default:
   185  		allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"}))
   186  
   187  	}
   188  
   189  	if len(roleBinding.RoleRef.Name) == 0 {
   190  		allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), ""))
   191  	} else {
   192  		for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) {
   193  			allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg))
   194  		}
   195  	}
   196  
   197  	subjectsPath := field.NewPath("subjects")
   198  	for i, subject := range roleBinding.Subjects {
   199  		allErrs = append(allErrs, ValidateRoleBindingSubject(subject, false, subjectsPath.Index(i))...)
   200  	}
   201  
   202  	return allErrs
   203  }
   204  
   205  func ValidateClusterRoleBindingUpdate(roleBinding *rbac.ClusterRoleBinding, oldRoleBinding *rbac.ClusterRoleBinding) field.ErrorList {
   206  	allErrs := ValidateClusterRoleBinding(roleBinding)
   207  	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...)
   208  
   209  	if oldRoleBinding.RoleRef != roleBinding.RoleRef {
   210  		allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef"))
   211  	}
   212  
   213  	return allErrs
   214  }
   215  
   216  // ValidateRoleBindingSubject is exported to allow types outside of the RBAC API group to embed a rbac.Subject and reuse this validation logic
   217  func ValidateRoleBindingSubject(subject rbac.Subject, isNamespaced bool, fldPath *field.Path) field.ErrorList {
   218  	allErrs := field.ErrorList{}
   219  
   220  	if len(subject.Name) == 0 {
   221  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
   222  	}
   223  
   224  	switch subject.Kind {
   225  	case rbac.ServiceAccountKind:
   226  		if len(subject.Name) > 0 {
   227  			for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) {
   228  				allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg))
   229  			}
   230  		}
   231  		if len(subject.APIGroup) > 0 {
   232  			allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{""}))
   233  		}
   234  		if !isNamespaced && len(subject.Namespace) == 0 {
   235  			allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
   236  		}
   237  
   238  	case rbac.UserKind:
   239  		// TODO(ericchiang): What other restrictions on user name are there?
   240  		if subject.APIGroup != rbac.GroupName {
   241  			allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName}))
   242  		}
   243  
   244  	case rbac.GroupKind:
   245  		// TODO(ericchiang): What other restrictions on group name are there?
   246  		if subject.APIGroup != rbac.GroupName {
   247  			allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName}))
   248  		}
   249  
   250  	default:
   251  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, []string{rbac.ServiceAccountKind, rbac.UserKind, rbac.GroupKind}))
   252  	}
   253  
   254  	return allErrs
   255  }
   256  

View as plain text