add field and label selectors to authorization attributes

Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
David Eads
2024-05-23 15:12:26 -04:00
committed by Jordan Liggitt
parent f5e5bef2e0
commit 92e3445e9d
25 changed files with 2388 additions and 226 deletions

View File

@@ -17,12 +17,21 @@ limitations under the License.
package util
import (
"errors"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
)
@@ -37,6 +46,10 @@ func TestResourceAttributesFrom(t *testing.T) {
"Subresource",
"Name",
// Fields we read and parse in ResourceAttributesFrom
"FieldSelector",
"LabelSelector",
// Fields we copy in NonResourceAttributesFrom
"Path",
"Verb",
@@ -60,6 +73,12 @@ func TestResourceAttributesFrom(t *testing.T) {
"Name",
"ResourceRequest",
// Fields we compute and set in ResourceAttributesFrom
"FieldSelectorRequirements",
"FieldSelectorParsingErr",
"LabelSelectorRequirements",
"LabelSelectorParsingErr",
// Fields we set in NonResourceAttributesFrom
"User",
"ResourceRequest",
@@ -75,13 +94,22 @@ func TestResourceAttributesFrom(t *testing.T) {
}
func TestAuthorizationAttributesFrom(t *testing.T) {
mustRequirement := func(key string, op selection.Operator, vals []string) labels.Requirement {
ret, err := labels.NewRequirement(key, op, vals)
if err != nil {
panic(err)
}
return *ret
}
type args struct {
spec authorizationapi.SubjectAccessReviewSpec
}
tests := []struct {
name string
args args
want authorizer.AttributesRecord
name string
args args
want authorizer.AttributesRecord
enableAuthorizationSelector bool
}{
{
name: "nonresource",
@@ -162,11 +190,461 @@ func TestAuthorizationAttributesFrom(t *testing.T) {
ResourceRequest: true,
},
},
{
name: "field: ignore when featuregate off",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
RawSelector: "foo=bar",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
},
},
{
name: "field: raw selector",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
RawSelector: "foo=bar",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorRequirements: fields.Requirements{
{Operator: "=", Field: "foo", Value: "bar"},
},
},
enableAuthorizationSelector: true,
},
{
name: "field: raw selector error",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
RawSelector: "&foo",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: errors.New("invalid selector: '&foo'; can't understand '&foo'"),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "In",
Values: []string{"apple"},
},
{
Key: "two",
Operator: "NotIn",
Values: []string{"banana"},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorRequirements: fields.Requirements{
{Operator: "=", Field: "one", Value: "apple"},
{Operator: "!=", Field: "two", Value: "banana"},
},
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements too many values",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "In",
Values: []string{"apple", "other"},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("fieldSelectors do not yet support multiple values")}),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements missing in value",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "In",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("fieldSelectors in must have one value")}),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements missing notin value",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "NotIn",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("fieldSelectors not in must have one value")}),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements exists",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "Exists",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("fieldSelectors do not yet support Exists")}),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements DoesNotExist",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "DoesNotExist",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("fieldSelectors do not yet support DoesNotExist")}),
},
enableAuthorizationSelector: true,
},
{
name: "field: requirements bad operator",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
FieldSelector: &authorizationapi.FieldSelectorAttributes{
Requirements: []metav1.FieldSelectorRequirement{
{
Key: "one",
Operator: "bad",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
FieldSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("\"bad\" is not a valid field selector operator")}),
},
enableAuthorizationSelector: true,
},
{
name: "label: ignore when featuregate off",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
RawSelector: "foo=bar",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
},
},
{
name: "label: raw selector",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
RawSelector: "foo=bar",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorRequirements: labels.Requirements{
mustRequirement("foo", "=", []string{"bar"}),
},
},
enableAuthorizationSelector: true,
},
{
name: "label: raw selector error",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
RawSelector: "&foo",
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorParsingErr: errors.New("unable to parse requirement: <nil>: Invalid value: \"&foo\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"),
},
enableAuthorizationSelector: true,
},
{
name: "label: requirements",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
Requirements: []metav1.LabelSelectorRequirement{
{
Key: "one",
Operator: "In",
Values: []string{"apple"},
},
{
Key: "two",
Operator: "NotIn",
Values: []string{"banana"},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorRequirements: labels.Requirements{
mustRequirement("one", "in", []string{"apple"}),
mustRequirement("two", "notin", []string{"banana"}),
},
},
enableAuthorizationSelector: true,
},
{
name: "label: requirements multiple values",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
Requirements: []metav1.LabelSelectorRequirement{
{
Key: "one",
Operator: "In",
Values: []string{"apple", "other"},
},
{
Key: "two",
Operator: "NotIn",
Values: []string{"carrot", "donut"},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorRequirements: labels.Requirements{
mustRequirement("one", "in", []string{"apple", "other"}),
mustRequirement("two", "notin", []string{"carrot", "donut"}),
},
},
enableAuthorizationSelector: true,
},
{
name: "label: requirements exists",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
Requirements: []metav1.LabelSelectorRequirement{
{
Key: "one",
Operator: "Exists",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorRequirements: labels.Requirements{
mustRequirement("one", "exists", nil),
},
},
enableAuthorizationSelector: true,
},
{
name: "label: requirements DoesNotExist",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
Requirements: []metav1.LabelSelectorRequirement{
{
Key: "one",
Operator: "DoesNotExist",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorRequirements: labels.Requirements{
mustRequirement("one", "!", nil),
},
},
enableAuthorizationSelector: true,
},
{
name: "label: requirements bad operator",
args: args{
spec: authorizationapi.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
LabelSelector: &authorizationapi.LabelSelectorAttributes{
Requirements: []metav1.LabelSelectorRequirement{
{
Key: "one",
Operator: "bad",
Values: []string{},
},
},
},
},
},
},
want: authorizer.AttributesRecord{
User: &user.DefaultInfo{},
ResourceRequest: true,
APIVersion: "*",
LabelSelectorParsingErr: utilerrors.NewAggregate([]error{errors.New("\"bad\" is not a valid label selector operator")}),
},
enableAuthorizationSelector: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.enableAuthorizationSelector {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true)
}
if got := AuthorizationAttributesFrom(tt.args.spec); !reflect.DeepEqual(got, tt.want) {
t.Errorf("AuthorizationAttributesFrom() = %v, want %v", got, tt.want)
if got.LabelSelectorParsingErr != nil {
t.Logf("labelSelectorErr=%q", got.LabelSelectorParsingErr)
}
t.Errorf("AuthorizationAttributesFrom(), got:\n%#v\nwant:\n%#v", got, tt.want)
}
})
}