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 }