k8s.io/apiserver@v0.31.1/pkg/authorization/cel/compile_test.go (about)

     1  /*
     2  Copyright 2023 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 cel
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  
    25  	v1 "k8s.io/api/authorization/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	apiservercel "k8s.io/apiserver/pkg/cel"
    28  	"k8s.io/apiserver/pkg/cel/environment"
    29  	genericfeatures "k8s.io/apiserver/pkg/features"
    30  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    31  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    32  )
    33  
    34  func TestCompileCELExpression(t *testing.T) {
    35  	cases := []struct {
    36  		name          string
    37  		expression    string
    38  		expectedError string
    39  
    40  		authorizeWithSelectorsEnabled bool
    41  	}{
    42  		{
    43  			name:       "SubjectAccessReviewSpec user comparison",
    44  			expression: "request.user == 'bob'",
    45  		},
    46  		{
    47  			name:          "undefined fields",
    48  			expression:    "request.time == 'now'",
    49  			expectedError: "undefined field",
    50  		},
    51  		{
    52  			name:          "Syntax errors",
    53  			expression:    "request++'",
    54  			expectedError: "Syntax error",
    55  		},
    56  		{
    57  			name:          "bad return type",
    58  			expression:    "request.user",
    59  			expectedError: "must evaluate to bool",
    60  		},
    61  		{
    62  			name:          "undeclared reference",
    63  			expression:    "x.user",
    64  			expectedError: "undeclared reference",
    65  		},
    66  		{
    67  			name:                          "fieldSelector not enabled",
    68  			expression:                    "request.resourceAttributes.fieldSelector.rawSelector == 'foo'",
    69  			authorizeWithSelectorsEnabled: false,
    70  			expectedError:                 `undefined field 'fieldSelector'`,
    71  		},
    72  		{
    73  			name:                          "fieldSelector rawSelector",
    74  			expression:                    "request.resourceAttributes.fieldSelector.rawSelector == 'foo'",
    75  			authorizeWithSelectorsEnabled: true,
    76  		},
    77  		{
    78  			name:                          "fieldSelector requirement",
    79  			expression:                    "request.resourceAttributes.fieldSelector.requirements.exists(r, r.key == 'foo' && r.operator == 'In' && ('bar' in r.values))",
    80  			authorizeWithSelectorsEnabled: true,
    81  		},
    82  		{
    83  			name:                          "labelSelector not enabled",
    84  			expression:                    "request.resourceAttributes.labelSelector.rawSelector == 'foo'",
    85  			authorizeWithSelectorsEnabled: false,
    86  			expectedError:                 `undefined field 'labelSelector'`,
    87  		},
    88  		{
    89  			name:                          "labelSelector rawSelector",
    90  			expression:                    "request.resourceAttributes.labelSelector.rawSelector == 'foo'",
    91  			authorizeWithSelectorsEnabled: true,
    92  		},
    93  		{
    94  			name:                          "labelSelector requirement",
    95  			expression:                    "request.resourceAttributes.labelSelector.requirements.exists(r, r.key == 'foo' && r.operator == 'In' && ('bar' in r.values))",
    96  			authorizeWithSelectorsEnabled: true,
    97  		},
    98  	}
    99  
   100  	for _, tc := range cases {
   101  		t.Run(tc.name, func(t *testing.T) {
   102  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, tc.authorizeWithSelectorsEnabled)
   103  			compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
   104  			_, err := compiler.CompileCELExpression(&SubjectAccessReviewMatchCondition{
   105  				Expression: tc.expression,
   106  			})
   107  			if len(tc.expectedError) > 0 && (err == nil || !strings.Contains(err.Error(), tc.expectedError)) {
   108  				t.Fatalf("expected error: %s compiling expression %s, got: %v", tc.expectedError, tc.expression, err)
   109  			}
   110  			if len(tc.expectedError) == 0 && err != nil {
   111  				t.Fatalf("unexpected error %v compiling expression %s", err, tc.expression)
   112  			}
   113  		})
   114  	}
   115  }
   116  
   117  func TestBuildRequestType(t *testing.T) {
   118  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true)
   119  	f := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
   120  		return apiservercel.NewDeclField(name, declType, required, nil, nil)
   121  	}
   122  	fs := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
   123  		result := make(map[string]*apiservercel.DeclField, len(fields))
   124  		for _, f := range fields {
   125  			result[f.Name] = f
   126  		}
   127  		return result
   128  	}
   129  
   130  	requestDeclType := buildRequestType(f, fs)
   131  	requestType := reflect.TypeOf(v1.SubjectAccessReviewSpec{})
   132  	if len(requestDeclType.Fields) != requestType.NumField() {
   133  		t.Fatalf("expected %d fields for SubjectAccessReviewSpec, got %d", requestType.NumField(), len(requestDeclType.Fields))
   134  	}
   135  	resourceAttributesDeclType := buildResourceAttributesType(f, fs)
   136  	resourceAttributeType := reflect.TypeOf(v1.ResourceAttributes{})
   137  	if len(resourceAttributesDeclType.Fields) != resourceAttributeType.NumField() {
   138  		t.Fatalf("expected %d fields for ResourceAttributes, got %d", resourceAttributeType.NumField(), len(resourceAttributesDeclType.Fields))
   139  	}
   140  	nonResourceAttributesDeclType := buildNonResourceAttributesType(f, fs)
   141  	nonResourceAttributeType := reflect.TypeOf(v1.NonResourceAttributes{})
   142  	if len(nonResourceAttributesDeclType.Fields) != nonResourceAttributeType.NumField() {
   143  		t.Fatalf("expected %d fields for NonResourceAttributes, got %d", nonResourceAttributeType.NumField(), len(nonResourceAttributesDeclType.Fields))
   144  	}
   145  	if err := compareFieldsForType(t, requestType, requestDeclType, f, fs); err != nil {
   146  		t.Error(err)
   147  	}
   148  }
   149  
   150  func compareFieldsForType(t *testing.T, nativeType reflect.Type, declType *apiservercel.DeclType, field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) error {
   151  	for i := 0; i < nativeType.NumField(); i++ {
   152  		nativeField := nativeType.Field(i)
   153  		jsonTagParts := strings.Split(nativeField.Tag.Get("json"), ",")
   154  		if len(jsonTagParts) < 1 {
   155  			t.Fatal("expected json tag to be present")
   156  		}
   157  		fieldName := jsonTagParts[0]
   158  
   159  		declField, ok := declType.Fields[fieldName]
   160  		if !ok {
   161  			t.Fatalf("expected field %q to be present", nativeField.Name)
   162  		}
   163  		declFieldType := nativeTypeToCELType(t, nativeField.Type, field, fields)
   164  		if declFieldType == nil {
   165  			return fmt.Errorf("no field type found for %s %v", nativeField.Name, nativeField.Type)
   166  		}
   167  		if declFieldType.CelType().Equal(declField.Type.CelType()).Value() != true {
   168  			return fmt.Errorf("expected native field %q to have type %v, got %v", nativeField.Name, nativeField.Type, declField.Type)
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  func nativeTypeToCELType(t *testing.T, nativeType reflect.Type, field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
   175  	switch nativeType {
   176  	case reflect.TypeOf(""), reflect.TypeOf(metav1.LabelSelectorOperator("")), reflect.TypeOf(metav1.FieldSelectorOperator("")):
   177  		return apiservercel.StringType
   178  	case reflect.TypeOf([]string{}):
   179  		return apiservercel.NewListType(apiservercel.StringType, -1)
   180  	case reflect.TypeOf(map[string]v1.ExtraValue{}):
   181  		return apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1)
   182  	case reflect.TypeOf(&v1.ResourceAttributes{}):
   183  		resourceAttributesDeclType := buildResourceAttributesType(field, fields)
   184  		if err := compareFieldsForType(t, reflect.TypeOf(v1.ResourceAttributes{}), resourceAttributesDeclType, field, fields); err != nil {
   185  			t.Error(err)
   186  			return nil
   187  		}
   188  		return resourceAttributesDeclType
   189  	case reflect.TypeOf(&v1.NonResourceAttributes{}):
   190  		nonResourceAttributesDeclType := buildNonResourceAttributesType(field, fields)
   191  		if err := compareFieldsForType(t, reflect.TypeOf(v1.NonResourceAttributes{}), nonResourceAttributesDeclType, field, fields); err != nil {
   192  			t.Error(err)
   193  			return nil
   194  		}
   195  		return nonResourceAttributesDeclType
   196  	case reflect.TypeOf(&v1.FieldSelectorAttributes{}):
   197  		selectorAttributesDeclType := buildFieldSelectorType(field, fields)
   198  		if err := compareFieldsForType(t, reflect.TypeOf(v1.FieldSelectorAttributes{}), selectorAttributesDeclType, field, fields); err != nil {
   199  			t.Error(err)
   200  			return nil
   201  		}
   202  		return selectorAttributesDeclType
   203  	case reflect.TypeOf(&v1.LabelSelectorAttributes{}):
   204  		selectorAttributesDeclType := buildLabelSelectorType(field, fields)
   205  		if err := compareFieldsForType(t, reflect.TypeOf(v1.LabelSelectorAttributes{}), selectorAttributesDeclType, field, fields); err != nil {
   206  			t.Error(err)
   207  			return nil
   208  		}
   209  		return selectorAttributesDeclType
   210  	case reflect.TypeOf([]metav1.FieldSelectorRequirement{}):
   211  		requirementType := buildSelectorRequirementType(field, fields)
   212  		if err := compareFieldsForType(t, reflect.TypeOf(metav1.FieldSelectorRequirement{}), requirementType, field, fields); err != nil {
   213  			t.Error(err)
   214  			return nil
   215  		}
   216  		return apiservercel.NewListType(requirementType, -1)
   217  	case reflect.TypeOf([]metav1.LabelSelectorRequirement{}):
   218  		requirementType := buildSelectorRequirementType(field, fields)
   219  		if err := compareFieldsForType(t, reflect.TypeOf(metav1.LabelSelectorRequirement{}), requirementType, field, fields); err != nil {
   220  			t.Error(err)
   221  			return nil
   222  		}
   223  		return apiservercel.NewListType(requirementType, -1)
   224  	default:
   225  		t.Fatalf("unsupported type %v", nativeType)
   226  	}
   227  	return nil
   228  }