add field and label selectors to authorization attributes
Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
committed by
Jordan Liggitt
parent
f5e5bef2e0
commit
92e3445e9d
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user