k8s.io/apiserver@v0.31.1/pkg/cel/library/authz.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 library
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/fields"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	genericfeatures "k8s.io/apiserver/pkg/features"
    28  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    29  
    30  	"github.com/google/cel-go/cel"
    31  	"github.com/google/cel-go/common/types"
    32  	"github.com/google/cel-go/common/types/ref"
    33  
    34  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apiserver/pkg/authentication/serviceaccount"
    37  	"k8s.io/apiserver/pkg/authentication/user"
    38  	"k8s.io/apiserver/pkg/authorization/authorizer"
    39  )
    40  
    41  // Authz provides a CEL function library extension for performing authorization checks.
    42  // Note that authorization checks are only supported for CEL expression fields in the API
    43  // where an 'authorizer' variable is provided to the CEL expression. See the
    44  // documentation of API fields where CEL expressions are used to learn if the 'authorizer'
    45  // variable is provided.
    46  //
    47  // path
    48  //
    49  // Returns a PathCheck configured to check authorization for a non-resource request
    50  // path (e.g. /healthz). If path is an empty string, an error is returned.
    51  // Note that the leading '/' is not required.
    52  //
    53  //	<Authorizer>.path(<string>) <PathCheck>
    54  //
    55  // Examples:
    56  //
    57  //	authorizer.path('/healthz') // returns a PathCheck for the '/healthz' API path
    58  //	authorizer.path('') // results in "path must not be empty" error
    59  //	authorizer.path('  ') // results in "path must not be empty" error
    60  //
    61  // group
    62  //
    63  // Returns a GroupCheck configured to check authorization for the API resources for
    64  // a particular API group.
    65  // Note that authorization checks are only supported for CEL expression fields in the API
    66  // where an 'authorizer' variable is provided to the CEL expression. Check the
    67  // documentation of API fields where CEL expressions are used to learn if the 'authorizer'
    68  // variable is provided.
    69  //
    70  //	<Authorizer>.group(<string>) <GroupCheck>
    71  //
    72  // Examples:
    73  //
    74  //	authorizer.group('apps') // returns a GroupCheck for the 'apps' API group
    75  //	authorizer.group('') // returns a GroupCheck for the core API group
    76  //	authorizer.group('example.com') // returns a GroupCheck for the custom resources in the 'example.com' API group
    77  //
    78  // serviceAccount
    79  //
    80  // Returns an Authorizer configured to check authorization for the provided service account namespace and name.
    81  // If the name is not a valid DNS subdomain string (as defined by RFC 1123), an error is returned.
    82  // If the namespace is not a valid DNS label (as defined by RFC 1123), an error is returned.
    83  //
    84  //	<Authorizer>.serviceAccount(<string>, <string>) <Authorizer>
    85  //
    86  // Examples:
    87  //
    88  //	authorizer.serviceAccount('default', 'myserviceaccount') // returns an Authorizer for the service account with namespace 'default' and name 'myserviceaccount'
    89  //	authorizer.serviceAccount('not@a#valid!namespace', 'validname') // returns an error
    90  //	authorizer.serviceAccount('valid.example.com', 'invalid@*name') // returns an error
    91  //
    92  // resource
    93  //
    94  // Returns a ResourceCheck configured to check authorization for a particular API resource.
    95  // Note that the provided resource string should be a lower case plural name of a Kubernetes API resource.
    96  //
    97  //	<GroupCheck>.resource(<string>) <ResourceCheck>
    98  //
    99  // Examples:
   100  //
   101  //	authorizer.group('apps').resource('deployments') // returns a ResourceCheck for the 'deployments' resources in the 'apps' group.
   102  //	authorizer.group('').resource('pods') // returns a ResourceCheck for the 'pods' resources in the core group.
   103  //	authorizer.group('apps').resource('') // results in "resource must not be empty" error
   104  //	authorizer.group('apps').resource('  ') // results in "resource must not be empty" error
   105  //
   106  // subresource
   107  //
   108  // Returns a ResourceCheck configured to check authorization for a particular subresource of an API resource.
   109  // If subresource is set to "", the subresource field of this ResourceCheck is considered unset.
   110  //
   111  //	<ResourceCheck>.subresource(<string>) <ResourceCheck>
   112  //
   113  // Examples:
   114  //
   115  //	authorizer.group('').resource('pods').subresource('status') // returns a ResourceCheck the 'status' subresource of 'pods'
   116  //	authorizer.group('apps').resource('deployments').subresource('scale') // returns a ResourceCheck the 'scale' subresource of 'deployments'
   117  //	authorizer.group('example.com').resource('widgets').subresource('scale') // returns a ResourceCheck for the 'scale' subresource of the 'widgets' custom resource
   118  //	authorizer.group('example.com').resource('widgets').subresource('') // returns a ResourceCheck for the 'widgets' resource.
   119  //
   120  // namespace
   121  //
   122  // Returns a ResourceCheck configured to check authorization for a particular namespace.
   123  // For cluster scoped resources, namespace() does not need to be called; namespace defaults
   124  // to "", which is the correct namespace value to use to check cluster scoped resources.
   125  // If namespace is set to "", the ResourceCheck will check authorization for the cluster scope.
   126  //
   127  //	<ResourceCheck>.namespace(<string>) <ResourceCheck>
   128  //
   129  // Examples:
   130  //
   131  //	authorizer.group('apps').resource('deployments').namespace('test') // returns a ResourceCheck for 'deployments' in the 'test' namespace
   132  //	authorizer.group('').resource('pods').namespace('default') // returns a ResourceCheck for 'pods' in the 'default' namespace
   133  //	authorizer.group('').resource('widgets').namespace('') // returns a ResourceCheck for 'widgets' in the cluster scope
   134  //
   135  // name
   136  //
   137  // Returns a ResourceCheck configured to check authorization for a particular resource name.
   138  // If name is set to "", the name field of this ResourceCheck is considered unset.
   139  //
   140  //	<ResourceCheck>.name(<name>) <ResourceCheck>
   141  //
   142  // Examples:
   143  //
   144  //	authorizer.group('apps').resource('deployments').namespace('test').name('backend') // returns a ResourceCheck for the 'backend' 'deployments' resource in the 'test' namespace
   145  //	authorizer.group('apps').resource('deployments').namespace('test').name('') // returns a ResourceCheck for the 'deployments' resource in the 'test' namespace
   146  //
   147  // check
   148  //
   149  // For PathCheck, checks if the principal (user or service account) that sent the request is authorized for the HTTP request verb of the path.
   150  // For ResourceCheck, checks if the principal (user or service account) that sent the request is authorized for the API verb and the configured authorization checks of the ResourceCheck.
   151  // The check operation can be expensive, particularly in clusters using the webhook authorization mode.
   152  //
   153  //	<PathCheck>.check(<check>) <Decision>
   154  //	<ResourceCheck>.check(<check>) <Decision>
   155  //
   156  // Examples:
   157  //
   158  //	authorizer.group('').resource('pods').namespace('default').check('create') // Checks if the principal (user or service account) is authorized create pods in the 'default' namespace.
   159  //	authorizer.path('/healthz').check('get') // Checks if the principal (user or service account) is authorized to make HTTP GET requests to the /healthz API path.
   160  //
   161  // allowed
   162  //
   163  // Returns true if the authorizer's decision for the check is "allow".  Note that if the authorizer's decision is
   164  // "no opinion", that the 'allowed' function will return false.
   165  //
   166  //	<Decision>.allowed() <bool>
   167  //
   168  // Examples:
   169  //
   170  //	authorizer.group('').resource('pods').namespace('default').check('create').allowed() // Returns true if the principal (user or service account) is allowed create pods in the 'default' namespace.
   171  //	authorizer.path('/healthz').check('get').allowed()  // Returns true if the principal (user or service account) is allowed to make HTTP GET requests to the /healthz API path.
   172  //
   173  // reason
   174  //
   175  // Returns a string reason for the authorization decision
   176  //
   177  //	<Decision>.reason() <string>
   178  //
   179  // Examples:
   180  //
   181  //	authorizer.path('/healthz').check('GET').reason()
   182  //
   183  // errored
   184  //
   185  // Returns true if the authorization check resulted in an error.
   186  //
   187  //	<Decision>.errored() <bool>
   188  //
   189  // Examples:
   190  //
   191  //	authorizer.group('').resource('pods').namespace('default').check('create').errored() // Returns true if the authorization check resulted in an error
   192  //
   193  // error
   194  //
   195  // If the authorization check resulted in an error, returns the error. Otherwise, returns the empty string.
   196  //
   197  //	<Decision>.error() <string>
   198  //
   199  // Examples:
   200  //
   201  //	authorizer.group('').resource('pods').namespace('default').check('create').error()
   202  //
   203  // fieldSelector
   204  //
   205  // Takes a string field selector, parses it to field selector requirements, and includes it in the authorization check.
   206  // If the field selector does not parse successfully, no field selector requirements are included in the authorization check.
   207  // Added in Kubernetes 1.31+, Authz library version 1.
   208  //
   209  //	<ResourceCheck>.fieldSelector(<string>) <ResourceCheck>
   210  //
   211  // Examples:
   212  //
   213  //	authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed()
   214  //
   215  // labelSelector (added in v1, Kubernetes 1.31+)
   216  //
   217  // Takes a string label selector, parses it to label selector requirements, and includes it in the authorization check.
   218  // If the label selector does not parse successfully, no label selector requirements are included in the authorization check.
   219  // Added in Kubernetes 1.31+, Authz library version 1.
   220  //
   221  //	<ResourceCheck>.labelSelector(<string>) <ResourceCheck>
   222  //
   223  // Examples:
   224  //
   225  //	authorizer.group('').resource('pods').labelSelector('app=example').check('list').allowed()
   226  func Authz() cel.EnvOption {
   227  	return cel.Lib(authzLib)
   228  }
   229  
   230  var authzLib = &authz{}
   231  
   232  type authz struct{}
   233  
   234  func (*authz) LibraryName() string {
   235  	return "k8s.authz"
   236  }
   237  
   238  var authzLibraryDecls = map[string][]cel.FunctionOpt{
   239  	"path": {
   240  		cel.MemberOverload("authorizer_path", []*cel.Type{AuthorizerType, cel.StringType}, PathCheckType,
   241  			cel.BinaryBinding(authorizerPath))},
   242  	"group": {
   243  		cel.MemberOverload("authorizer_group", []*cel.Type{AuthorizerType, cel.StringType}, GroupCheckType,
   244  			cel.BinaryBinding(authorizerGroup))},
   245  	"serviceAccount": {
   246  		cel.MemberOverload("authorizer_serviceaccount", []*cel.Type{AuthorizerType, cel.StringType, cel.StringType}, AuthorizerType,
   247  			cel.FunctionBinding(authorizerServiceAccount))},
   248  	"resource": {
   249  		cel.MemberOverload("groupcheck_resource", []*cel.Type{GroupCheckType, cel.StringType}, ResourceCheckType,
   250  			cel.BinaryBinding(groupCheckResource))},
   251  	"subresource": {
   252  		cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
   253  			cel.BinaryBinding(resourceCheckSubresource))},
   254  	"namespace": {
   255  		cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
   256  			cel.BinaryBinding(resourceCheckNamespace))},
   257  	"name": {
   258  		cel.MemberOverload("resourcecheck_name", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
   259  			cel.BinaryBinding(resourceCheckName))},
   260  	"check": {
   261  		cel.MemberOverload("pathcheck_check", []*cel.Type{PathCheckType, cel.StringType}, DecisionType,
   262  			cel.BinaryBinding(pathCheckCheck)),
   263  		cel.MemberOverload("resourcecheck_check", []*cel.Type{ResourceCheckType, cel.StringType}, DecisionType,
   264  			cel.BinaryBinding(resourceCheckCheck))},
   265  	"errored": {
   266  		cel.MemberOverload("decision_errored", []*cel.Type{DecisionType}, cel.BoolType,
   267  			cel.UnaryBinding(decisionErrored))},
   268  	"error": {
   269  		cel.MemberOverload("decision_error", []*cel.Type{DecisionType}, cel.StringType,
   270  			cel.UnaryBinding(decisionError))},
   271  	"allowed": {
   272  		cel.MemberOverload("decision_allowed", []*cel.Type{DecisionType}, cel.BoolType,
   273  			cel.UnaryBinding(decisionAllowed))},
   274  	"reason": {
   275  		cel.MemberOverload("decision_reason", []*cel.Type{DecisionType}, cel.StringType,
   276  			cel.UnaryBinding(decisionReason))},
   277  }
   278  
   279  func (*authz) CompileOptions() []cel.EnvOption {
   280  	options := make([]cel.EnvOption, 0, len(authzLibraryDecls))
   281  	for name, overloads := range authzLibraryDecls {
   282  		options = append(options, cel.Function(name, overloads...))
   283  	}
   284  	return options
   285  }
   286  
   287  func (*authz) ProgramOptions() []cel.ProgramOption {
   288  	return []cel.ProgramOption{}
   289  }
   290  
   291  // AuthzSelectors provides a CEL function library extension for adding fieldSelector and
   292  // labelSelector filters to authorization checks. This requires the Authz library.
   293  // See documentation of the Authz library for use and availability of the authorizer variable.
   294  //
   295  // fieldSelector
   296  //
   297  // Takes a string field selector, parses it to field selector requirements, and includes it in the authorization check.
   298  // If the field selector does not parse successfully, no field selector requirements are included in the authorization check.
   299  // Added in Kubernetes 1.31+.
   300  //
   301  //	<ResourceCheck>.fieldSelector(<string>) <ResourceCheck>
   302  //
   303  // Examples:
   304  //
   305  //	authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed()
   306  //
   307  // labelSelector
   308  //
   309  // Takes a string label selector, parses it to label selector requirements, and includes it in the authorization check.
   310  // If the label selector does not parse successfully, no label selector requirements are included in the authorization check.
   311  // Added in Kubernetes 1.31+.
   312  //
   313  //	<ResourceCheck>.labelSelector(<string>) <ResourceCheck>
   314  //
   315  // Examples:
   316  //
   317  //	authorizer.group('').resource('pods').labelSelector('app=example').check('list').allowed()
   318  func AuthzSelectors() cel.EnvOption {
   319  	return cel.Lib(authzSelectorsLib)
   320  }
   321  
   322  var authzSelectorsLib = &authzSelectors{}
   323  
   324  type authzSelectors struct{}
   325  
   326  func (*authzSelectors) LibraryName() string {
   327  	return "k8s.authzSelectors"
   328  }
   329  
   330  var authzSelectorsLibraryDecls = map[string][]cel.FunctionOpt{
   331  	"fieldSelector": {
   332  		cel.MemberOverload("authorizer_fieldselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
   333  			cel.BinaryBinding(resourceCheckFieldSelector))},
   334  	"labelSelector": {
   335  		cel.MemberOverload("authorizer_labelselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
   336  			cel.BinaryBinding(resourceCheckLabelSelector))},
   337  }
   338  
   339  func (*authzSelectors) CompileOptions() []cel.EnvOption {
   340  	options := make([]cel.EnvOption, 0, len(authzSelectorsLibraryDecls))
   341  	for name, overloads := range authzSelectorsLibraryDecls {
   342  		options = append(options, cel.Function(name, overloads...))
   343  	}
   344  	return options
   345  }
   346  
   347  func (*authzSelectors) ProgramOptions() []cel.ProgramOption {
   348  	return []cel.ProgramOption{}
   349  }
   350  
   351  func authorizerPath(arg1, arg2 ref.Val) ref.Val {
   352  	authz, ok := arg1.(authorizerVal)
   353  	if !ok {
   354  		return types.MaybeNoSuchOverloadErr(arg1)
   355  	}
   356  
   357  	path, ok := arg2.Value().(string)
   358  	if !ok {
   359  		return types.MaybeNoSuchOverloadErr(arg1)
   360  	}
   361  
   362  	if len(strings.TrimSpace(path)) == 0 {
   363  		return types.NewErr("path must not be empty")
   364  	}
   365  
   366  	return authz.pathCheck(path)
   367  }
   368  
   369  func authorizerGroup(arg1, arg2 ref.Val) ref.Val {
   370  	authz, ok := arg1.(authorizerVal)
   371  	if !ok {
   372  		return types.MaybeNoSuchOverloadErr(arg1)
   373  	}
   374  
   375  	group, ok := arg2.Value().(string)
   376  	if !ok {
   377  		return types.MaybeNoSuchOverloadErr(arg1)
   378  	}
   379  
   380  	return authz.groupCheck(group)
   381  }
   382  
   383  func authorizerServiceAccount(args ...ref.Val) ref.Val {
   384  	argn := len(args)
   385  	if argn != 3 {
   386  		return types.NoSuchOverloadErr()
   387  	}
   388  
   389  	authz, ok := args[0].(authorizerVal)
   390  	if !ok {
   391  		return types.MaybeNoSuchOverloadErr(args[0])
   392  	}
   393  
   394  	namespace, ok := args[1].Value().(string)
   395  	if !ok {
   396  		return types.MaybeNoSuchOverloadErr(args[1])
   397  	}
   398  
   399  	name, ok := args[2].Value().(string)
   400  	if !ok {
   401  		return types.MaybeNoSuchOverloadErr(args[2])
   402  	}
   403  
   404  	if errors := apimachineryvalidation.ValidateServiceAccountName(name, false); len(errors) > 0 {
   405  		return types.NewErr("Invalid service account name")
   406  	}
   407  	if errors := apimachineryvalidation.ValidateNamespaceName(namespace, false); len(errors) > 0 {
   408  		return types.NewErr("Invalid service account namespace")
   409  	}
   410  	return authz.serviceAccount(namespace, name)
   411  }
   412  
   413  func groupCheckResource(arg1, arg2 ref.Val) ref.Val {
   414  	groupCheck, ok := arg1.(groupCheckVal)
   415  	if !ok {
   416  		return types.MaybeNoSuchOverloadErr(arg1)
   417  	}
   418  
   419  	resource, ok := arg2.Value().(string)
   420  	if !ok {
   421  		return types.MaybeNoSuchOverloadErr(arg1)
   422  	}
   423  
   424  	if len(strings.TrimSpace(resource)) == 0 {
   425  		return types.NewErr("resource must not be empty")
   426  	}
   427  	return groupCheck.resourceCheck(resource)
   428  }
   429  
   430  func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
   431  	resourceCheck, ok := arg1.(resourceCheckVal)
   432  	if !ok {
   433  		return types.MaybeNoSuchOverloadErr(arg1)
   434  	}
   435  
   436  	subresource, ok := arg2.Value().(string)
   437  	if !ok {
   438  		return types.MaybeNoSuchOverloadErr(arg1)
   439  	}
   440  
   441  	result := resourceCheck
   442  	result.subresource = subresource
   443  	return result
   444  }
   445  
   446  func resourceCheckFieldSelector(arg1, arg2 ref.Val) ref.Val {
   447  	resourceCheck, ok := arg1.(resourceCheckVal)
   448  	if !ok {
   449  		return types.MaybeNoSuchOverloadErr(arg1)
   450  	}
   451  
   452  	fieldSelector, ok := arg2.Value().(string)
   453  	if !ok {
   454  		return types.MaybeNoSuchOverloadErr(arg1)
   455  	}
   456  
   457  	result := resourceCheck
   458  	result.fieldSelector = fieldSelector
   459  	return result
   460  }
   461  
   462  func resourceCheckLabelSelector(arg1, arg2 ref.Val) ref.Val {
   463  	resourceCheck, ok := arg1.(resourceCheckVal)
   464  	if !ok {
   465  		return types.MaybeNoSuchOverloadErr(arg1)
   466  	}
   467  
   468  	labelSelector, ok := arg2.Value().(string)
   469  	if !ok {
   470  		return types.MaybeNoSuchOverloadErr(arg1)
   471  	}
   472  
   473  	result := resourceCheck
   474  	result.labelSelector = labelSelector
   475  	return result
   476  }
   477  
   478  func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
   479  	resourceCheck, ok := arg1.(resourceCheckVal)
   480  	if !ok {
   481  		return types.MaybeNoSuchOverloadErr(arg1)
   482  	}
   483  
   484  	namespace, ok := arg2.Value().(string)
   485  	if !ok {
   486  		return types.MaybeNoSuchOverloadErr(arg1)
   487  	}
   488  
   489  	result := resourceCheck
   490  	result.namespace = namespace
   491  	return result
   492  }
   493  
   494  func resourceCheckName(arg1, arg2 ref.Val) ref.Val {
   495  	resourceCheck, ok := arg1.(resourceCheckVal)
   496  	if !ok {
   497  		return types.MaybeNoSuchOverloadErr(arg1)
   498  	}
   499  
   500  	name, ok := arg2.Value().(string)
   501  	if !ok {
   502  		return types.MaybeNoSuchOverloadErr(arg1)
   503  	}
   504  
   505  	result := resourceCheck
   506  	result.name = name
   507  	return result
   508  }
   509  
   510  func pathCheckCheck(arg1, arg2 ref.Val) ref.Val {
   511  	pathCheck, ok := arg1.(pathCheckVal)
   512  	if !ok {
   513  		return types.MaybeNoSuchOverloadErr(arg1)
   514  	}
   515  
   516  	httpRequestVerb, ok := arg2.Value().(string)
   517  	if !ok {
   518  		return types.MaybeNoSuchOverloadErr(arg1)
   519  	}
   520  
   521  	return pathCheck.Authorize(context.TODO(), httpRequestVerb)
   522  }
   523  
   524  func resourceCheckCheck(arg1, arg2 ref.Val) ref.Val {
   525  	resourceCheck, ok := arg1.(resourceCheckVal)
   526  	if !ok {
   527  		return types.MaybeNoSuchOverloadErr(arg1)
   528  	}
   529  
   530  	apiVerb, ok := arg2.Value().(string)
   531  	if !ok {
   532  		return types.MaybeNoSuchOverloadErr(arg1)
   533  	}
   534  
   535  	return resourceCheck.Authorize(context.TODO(), apiVerb)
   536  }
   537  
   538  func decisionErrored(arg ref.Val) ref.Val {
   539  	decision, ok := arg.(decisionVal)
   540  	if !ok {
   541  		return types.MaybeNoSuchOverloadErr(arg)
   542  	}
   543  
   544  	return types.Bool(decision.err != nil)
   545  }
   546  
   547  func decisionError(arg ref.Val) ref.Val {
   548  	decision, ok := arg.(decisionVal)
   549  	if !ok {
   550  		return types.MaybeNoSuchOverloadErr(arg)
   551  	}
   552  
   553  	if decision.err == nil {
   554  		return types.String("")
   555  	}
   556  	return types.String(decision.err.Error())
   557  }
   558  
   559  func decisionAllowed(arg ref.Val) ref.Val {
   560  	decision, ok := arg.(decisionVal)
   561  	if !ok {
   562  		return types.MaybeNoSuchOverloadErr(arg)
   563  	}
   564  
   565  	return types.Bool(decision.authDecision == authorizer.DecisionAllow)
   566  }
   567  
   568  func decisionReason(arg ref.Val) ref.Val {
   569  	decision, ok := arg.(decisionVal)
   570  	if !ok {
   571  		return types.MaybeNoSuchOverloadErr(arg)
   572  	}
   573  
   574  	return types.String(decision.reason)
   575  }
   576  
   577  var (
   578  	AuthorizerType    = cel.ObjectType("kubernetes.authorization.Authorizer")
   579  	PathCheckType     = cel.ObjectType("kubernetes.authorization.PathCheck")
   580  	GroupCheckType    = cel.ObjectType("kubernetes.authorization.GroupCheck")
   581  	ResourceCheckType = cel.ObjectType("kubernetes.authorization.ResourceCheck")
   582  	DecisionType      = cel.ObjectType("kubernetes.authorization.Decision")
   583  )
   584  
   585  // Resource represents an API resource
   586  type Resource interface {
   587  	// GetName returns the name of the object as presented in the request.  On a CREATE operation, the client
   588  	// may omit name and rely on the server to generate the name.  If that is the case, this method will return
   589  	// the empty string
   590  	GetName() string
   591  	// GetNamespace is the namespace associated with the request (if any)
   592  	GetNamespace() string
   593  	// GetResource is the name of the resource being requested.  This is not the kind.  For example: pods
   594  	GetResource() schema.GroupVersionResource
   595  	// GetSubresource is the name of the subresource being requested.  This is a different resource, scoped to the parent resource, but it may have a different kind.
   596  	// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
   597  	// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
   598  	GetSubresource() string
   599  }
   600  
   601  func NewAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer) ref.Val {
   602  	return authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
   603  }
   604  
   605  func NewResourceAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer, requestResource Resource) ref.Val {
   606  	a := authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
   607  	resource := requestResource.GetResource()
   608  	g := a.groupCheck(resource.Group)
   609  	r := g.resourceCheck(resource.Resource)
   610  	r.subresource = requestResource.GetSubresource()
   611  	r.namespace = requestResource.GetNamespace()
   612  	r.name = requestResource.GetName()
   613  	return r
   614  }
   615  
   616  type authorizerVal struct {
   617  	receiverOnlyObjectVal
   618  	userInfo       user.Info
   619  	authAuthorizer authorizer.Authorizer
   620  }
   621  
   622  func (a authorizerVal) pathCheck(path string) pathCheckVal {
   623  	return pathCheckVal{receiverOnlyObjectVal: receiverOnlyVal(PathCheckType), authorizer: a, path: path}
   624  }
   625  
   626  func (a authorizerVal) groupCheck(group string) groupCheckVal {
   627  	return groupCheckVal{receiverOnlyObjectVal: receiverOnlyVal(GroupCheckType), authorizer: a, group: group}
   628  }
   629  
   630  func (a authorizerVal) serviceAccount(namespace, name string) authorizerVal {
   631  	sa := &serviceaccount.ServiceAccountInfo{Name: name, Namespace: namespace}
   632  	return authorizerVal{
   633  		receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType),
   634  		userInfo:              sa.UserInfo(),
   635  		authAuthorizer:        a.authAuthorizer,
   636  	}
   637  }
   638  
   639  type pathCheckVal struct {
   640  	receiverOnlyObjectVal
   641  	authorizer authorizerVal
   642  	path       string
   643  }
   644  
   645  func (a pathCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
   646  	attr := &authorizer.AttributesRecord{
   647  		Path: a.path,
   648  		Verb: verb,
   649  		User: a.authorizer.userInfo,
   650  	}
   651  
   652  	decision, reason, err := a.authorizer.authAuthorizer.Authorize(ctx, attr)
   653  	return newDecision(decision, err, reason)
   654  }
   655  
   656  type groupCheckVal struct {
   657  	receiverOnlyObjectVal
   658  	authorizer authorizerVal
   659  	group      string
   660  }
   661  
   662  func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
   663  	return resourceCheckVal{receiverOnlyObjectVal: receiverOnlyVal(ResourceCheckType), groupCheck: g, resource: resource}
   664  }
   665  
   666  type resourceCheckVal struct {
   667  	receiverOnlyObjectVal
   668  	groupCheck    groupCheckVal
   669  	resource      string
   670  	subresource   string
   671  	namespace     string
   672  	name          string
   673  	fieldSelector string
   674  	labelSelector string
   675  }
   676  
   677  func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
   678  	attr := &authorizer.AttributesRecord{
   679  		ResourceRequest: true,
   680  		APIGroup:        a.groupCheck.group,
   681  		APIVersion:      "*",
   682  		Resource:        a.resource,
   683  		Subresource:     a.subresource,
   684  		Namespace:       a.namespace,
   685  		Name:            a.name,
   686  		Verb:            verb,
   687  		User:            a.groupCheck.authorizer.userInfo,
   688  	}
   689  
   690  	if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) {
   691  		if len(a.fieldSelector) > 0 {
   692  			selector, err := fields.ParseSelector(a.fieldSelector)
   693  			if err != nil {
   694  				attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = nil, err
   695  			} else {
   696  				attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = selector.Requirements(), nil
   697  			}
   698  		}
   699  		if len(a.labelSelector) > 0 {
   700  			requirements, err := labels.ParseToRequirements(a.labelSelector)
   701  			if err != nil {
   702  				attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = nil, err
   703  			} else {
   704  				attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = requirements, nil
   705  			}
   706  		}
   707  	}
   708  
   709  	decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
   710  	return newDecision(decision, err, reason)
   711  }
   712  
   713  func newDecision(authDecision authorizer.Decision, err error, reason string) decisionVal {
   714  	return decisionVal{receiverOnlyObjectVal: receiverOnlyVal(DecisionType), authDecision: authDecision, err: err, reason: reason}
   715  }
   716  
   717  type decisionVal struct {
   718  	receiverOnlyObjectVal
   719  	err          error
   720  	authDecision authorizer.Decision
   721  	reason       string
   722  }
   723  
   724  // receiverOnlyObjectVal provides an implementation of ref.Val for
   725  // any object type that has receiver functions but does not expose any fields to
   726  // CEL.
   727  type receiverOnlyObjectVal struct {
   728  	typeValue *types.Type
   729  }
   730  
   731  // receiverOnlyVal returns a receiverOnlyObjectVal for the given type.
   732  func receiverOnlyVal(objectType *cel.Type) receiverOnlyObjectVal {
   733  	return receiverOnlyObjectVal{typeValue: types.NewTypeValue(objectType.String())}
   734  }
   735  
   736  // ConvertToNative implements ref.Val.ConvertToNative.
   737  func (a receiverOnlyObjectVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
   738  	return nil, fmt.Errorf("type conversion error from '%s' to '%v'", a.typeValue.String(), typeDesc)
   739  }
   740  
   741  // ConvertToType implements ref.Val.ConvertToType.
   742  func (a receiverOnlyObjectVal) ConvertToType(typeVal ref.Type) ref.Val {
   743  	switch typeVal {
   744  	case a.typeValue:
   745  		return a
   746  	case types.TypeType:
   747  		return a.typeValue
   748  	}
   749  	return types.NewErr("type conversion error from '%s' to '%s'", a.typeValue, typeVal)
   750  }
   751  
   752  // Equal implements ref.Val.Equal.
   753  func (a receiverOnlyObjectVal) Equal(other ref.Val) ref.Val {
   754  	o, ok := other.(receiverOnlyObjectVal)
   755  	if !ok {
   756  		return types.MaybeNoSuchOverloadErr(other)
   757  	}
   758  	return types.Bool(a == o)
   759  }
   760  
   761  // Type implements ref.Val.Type.
   762  func (a receiverOnlyObjectVal) Type() ref.Type {
   763  	return a.typeValue
   764  }
   765  
   766  // Value implements ref.Val.Value.
   767  func (a receiverOnlyObjectVal) Value() any {
   768  	return types.NoSuchOverloadErr()
   769  }