Skip to content

Commit

Permalink
add field and label selectors to authorization attributes
Browse files Browse the repository at this point in the history
Co-authored-by: Jordan Liggitt <liggitt@google.com>

Kubernetes-commit: 92e3445e9d7a587ddb56b3ff4b1445244fbf9abd
  • Loading branch information
deads2k authored and k8s-publishing-bot committed May 23, 2024
1 parent ce76a8f commit cc2ba35
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 2 deletions.
41 changes: 40 additions & 1 deletion pkg/apis/meta/v1/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import (
type LabelSelectorValidationOptions struct {
// Allow invalid label value in selector
AllowInvalidLabelValueInSelector bool

// Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check
// can be performed, as in a *SubjectAccessReview
AllowUnknownOperatorInRequirement bool
}

// LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression.
Expand Down Expand Up @@ -79,7 +83,9 @@ func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts L
allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
}
default:
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
if !opts.AllowUnknownOperatorInRequirement {
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
}
}
allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
if !opts.AllowInvalidLabelValueInSelector {
Expand Down Expand Up @@ -113,6 +119,39 @@ func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorLi
return allErrs
}

// FieldSelectorValidationOptions is a struct that can be passed to ValidateFieldSelectorRequirement to record the validate options
type FieldSelectorValidationOptions struct {
// Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check
// can be performed, as in a *SubjectAccessReview
AllowUnknownOperatorInRequirement bool
}

// ValidateLabelSelectorRequirement validates the requirement according to the opts and returns any validation errors.
func ValidateFieldSelectorRequirement(requirement metav1.FieldSelectorRequirement, opts FieldSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if len(requirement.Key) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("key"), "must be specified"))
}

switch requirement.Operator {
case metav1.FieldSelectorOpIn, metav1.FieldSelectorOpNotIn:
if len(requirement.Values) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
}
case metav1.FieldSelectorOpExists, metav1.FieldSelectorOpDoesNotExist:
if len(requirement.Values) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
}
default:
if !opts.AllowUnknownOperatorInRequirement {
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), requirement.Operator, "not a valid selector operator"))
}
}

return allErrs
}

func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
allErrs := field.ErrorList{}
//lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/meta/v1/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ func TestLabelSelectorMatchExpression(t *testing.T) {
}}
for index, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector"))
allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, field.NewPath("labelSelector"))
if len(allErrs) != testCase.wantErrorNumber {
t.Errorf("case[%d]: expected failure", index)
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/labels/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ var (
// Requirements is AND of all requirements.
type Requirements []Requirement

func (r Requirements) String() string {
var sb strings.Builder

for i, requirement := range r {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(requirement.String())
}

return sb.String()
}

// Selector represents a label selector.
type Selector interface {
// Matches returns true if this selector matches the given set of labels.
Expand Down Expand Up @@ -285,6 +298,13 @@ func (r *Requirement) Values() sets.String {
return ret
}

// ValuesUnsorted returns a copy of requirement values as passed to NewRequirement without sorting.
func (r *Requirement) ValuesUnsorted() []string {
ret := make([]string, 0, len(r.strValues))
ret = append(ret, r.strValues...)
return ret
}

// Equal checks the equality of requirement.
func (r Requirement) Equal(x Requirement) bool {
if r.key != x.key {
Expand Down

0 comments on commit cc2ba35

Please sign in to comment.