k8s.io/apiserver@v0.31.1/pkg/authentication/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  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	authenticationv1 "k8s.io/api/authentication/v1"
    25  	apiservercel "k8s.io/apiserver/pkg/cel"
    26  	"k8s.io/apiserver/pkg/cel/environment"
    27  )
    28  
    29  func TestCompileClaimsExpression(t *testing.T) {
    30  	testCases := []struct {
    31  		name                string
    32  		expressionAccessors []ExpressionAccessor
    33  	}{
    34  		{
    35  			name: "valid ClaimMappingCondition",
    36  			expressionAccessors: []ExpressionAccessor{
    37  				&ClaimMappingExpression{
    38  					Expression: "claims.foo",
    39  				},
    40  			},
    41  		},
    42  		{
    43  			name: "valid ClaimValidationCondition",
    44  			expressionAccessors: []ExpressionAccessor{
    45  				&ClaimValidationCondition{
    46  					Expression: "claims.foo == 'bar'",
    47  				},
    48  			},
    49  		},
    50  		{
    51  			name: "valid ExtraMapppingCondition",
    52  			expressionAccessors: []ExpressionAccessor{
    53  				&ExtraMappingExpression{
    54  					Expression: "claims.foo",
    55  				},
    56  			},
    57  		},
    58  	}
    59  
    60  	compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
    61  
    62  	for _, tc := range testCases {
    63  		t.Run(tc.name, func(t *testing.T) {
    64  			for _, expressionAccessor := range tc.expressionAccessors {
    65  				_, err := compiler.CompileClaimsExpression(expressionAccessor)
    66  				if err != nil {
    67  					t.Errorf("unexpected error: %v", err)
    68  				}
    69  			}
    70  		})
    71  	}
    72  }
    73  
    74  func TestCompileUserExpression(t *testing.T) {
    75  	testCases := []struct {
    76  		name                string
    77  		expressionAccessors []ExpressionAccessor
    78  	}{
    79  		{
    80  			name: "valid UserValidationCondition",
    81  			expressionAccessors: []ExpressionAccessor{
    82  				&ExtraMappingExpression{
    83  					Expression: "user.username == 'bar'",
    84  				},
    85  			},
    86  		},
    87  	}
    88  
    89  	compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
    90  
    91  	for _, tc := range testCases {
    92  		t.Run(tc.name, func(t *testing.T) {
    93  			for _, expressionAccessor := range tc.expressionAccessors {
    94  				_, err := compiler.CompileUserExpression(expressionAccessor)
    95  				if err != nil {
    96  					t.Errorf("unexpected error: %v", err)
    97  				}
    98  			}
    99  		})
   100  	}
   101  }
   102  
   103  func TestCompileClaimsExpressionError(t *testing.T) {
   104  	testCases := []struct {
   105  		name                string
   106  		expressionAccessors []ExpressionAccessor
   107  		wantErr             string
   108  	}{
   109  		{
   110  			name: "invalid ClaimValidationCondition",
   111  			expressionAccessors: []ExpressionAccessor{
   112  				&ClaimValidationCondition{
   113  					Expression: "claims.foo",
   114  				},
   115  			},
   116  			wantErr: "must evaluate to bool",
   117  		},
   118  		{
   119  			name: "UserValidationCondition with wrong env",
   120  			expressionAccessors: []ExpressionAccessor{
   121  				&UserValidationCondition{
   122  					Expression: "user.username == 'foo'",
   123  				},
   124  			},
   125  			wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'user' (in container '')`,
   126  		},
   127  		{
   128  			name: "invalid ClaimMappingCondition",
   129  			expressionAccessors: []ExpressionAccessor{
   130  				&ClaimMappingExpression{
   131  					Expression: "claims + 1",
   132  				},
   133  			},
   134  			wantErr: `compilation failed: ERROR: <input>:1:8: found no matching overload for '_+_' applied to '(map(string, any), int)'`,
   135  		},
   136  	}
   137  
   138  	compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
   139  
   140  	for _, tc := range testCases {
   141  		t.Run(tc.name, func(t *testing.T) {
   142  			for _, expressionAccessor := range tc.expressionAccessors {
   143  				_, err := compiler.CompileClaimsExpression(expressionAccessor)
   144  				if err == nil {
   145  					t.Errorf("expected error but got nil")
   146  				}
   147  				if !strings.Contains(err.Error(), tc.wantErr) {
   148  					t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error())
   149  				}
   150  			}
   151  		})
   152  	}
   153  }
   154  
   155  func TestCompileUserExpressionError(t *testing.T) {
   156  	testCases := []struct {
   157  		name                string
   158  		expressionAccessors []ExpressionAccessor
   159  		wantErr             string
   160  	}{
   161  		{
   162  			name: "invalid UserValidationCondition",
   163  			expressionAccessors: []ExpressionAccessor{
   164  				&UserValidationCondition{
   165  					Expression: "user.username",
   166  				},
   167  			},
   168  			wantErr: "must evaluate to bool",
   169  		},
   170  		{
   171  			name: "ClamMappingCondition with wrong env",
   172  			expressionAccessors: []ExpressionAccessor{
   173  				&ClaimMappingExpression{
   174  					Expression: "claims.foo",
   175  				},
   176  			},
   177  			wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
   178  		},
   179  		{
   180  			name: "ExtraMappingCondition with wrong env",
   181  			expressionAccessors: []ExpressionAccessor{
   182  				&ExtraMappingExpression{
   183  					Expression: "claims.foo",
   184  				},
   185  			},
   186  			wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
   187  		},
   188  		{
   189  			name: "ClaimValidationCondition with wrong env",
   190  			expressionAccessors: []ExpressionAccessor{
   191  				&ClaimValidationCondition{
   192  					Expression: "claims.foo == 'bar'",
   193  				},
   194  			},
   195  			wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
   196  		},
   197  		{
   198  			name: "UserValidationCondition expression with unknown field",
   199  			expressionAccessors: []ExpressionAccessor{
   200  				&UserValidationCondition{
   201  					Expression: "user.unknown == 'foo'",
   202  				},
   203  			},
   204  			wantErr: `compilation failed: ERROR: <input>:1:5: undefined field 'unknown'`,
   205  		},
   206  	}
   207  
   208  	compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
   209  
   210  	for _, tc := range testCases {
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			for _, expressionAccessor := range tc.expressionAccessors {
   213  				_, err := compiler.CompileUserExpression(expressionAccessor)
   214  				if err == nil {
   215  					t.Errorf("expected error but got nil")
   216  				}
   217  				if !strings.Contains(err.Error(), tc.wantErr) {
   218  					t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error())
   219  				}
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestBuildUserType(t *testing.T) {
   226  	userDeclType := buildUserType()
   227  	userType := reflect.TypeOf(authenticationv1.UserInfo{})
   228  
   229  	if len(userDeclType.Fields) != userType.NumField() {
   230  		t.Errorf("expected %d fields, got %d", userType.NumField(), len(userDeclType.Fields))
   231  	}
   232  
   233  	for i := 0; i < userType.NumField(); i++ {
   234  		field := userType.Field(i)
   235  		jsonTagParts := strings.Split(field.Tag.Get("json"), ",")
   236  		if len(jsonTagParts) < 1 {
   237  			t.Fatal("expected json tag to be present")
   238  		}
   239  		fieldName := jsonTagParts[0]
   240  
   241  		declField, ok := userDeclType.Fields[fieldName]
   242  		if !ok {
   243  			t.Errorf("expected field %q to be present", field.Name)
   244  		}
   245  		if nativeTypeToCELType(t, field.Type).CelType().Equal(declField.Type.CelType()).Value() != true {
   246  			t.Errorf("expected field %q to have type %v, got %v", field.Name, field.Type, declField.Type)
   247  		}
   248  	}
   249  }
   250  
   251  func nativeTypeToCELType(t *testing.T, nativeType reflect.Type) *apiservercel.DeclType {
   252  	switch nativeType {
   253  	case reflect.TypeOf(""):
   254  		return apiservercel.StringType
   255  	case reflect.TypeOf([]string{}):
   256  		return apiservercel.NewListType(apiservercel.StringType, -1)
   257  	case reflect.TypeOf(map[string]authenticationv1.ExtraValue{}):
   258  		return apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, -1)
   259  	default:
   260  		t.Fatalf("unsupported type %v", nativeType)
   261  	}
   262  	return nil
   263  }