diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c70bb9df..3fe74ccf 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright 2020 Red Hat, Inc. @@ -123,7 +122,8 @@ func (in *AuthConfigSpec) DeepCopyInto(out *AuthConfigSpec) { if val == nil { (*out)[key] = nil } else { - in, out := &val, &outVal + inVal := (*in)[key] + in, out := &inVal, &outVal *out = make(JSONPatternExpressions, len(*in)) copy(*out, *in) } diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 3647917e..d578097a 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright 2020 Red Hat, Inc. @@ -137,7 +136,8 @@ func (in *AuthConfigSpec) DeepCopyInto(out *AuthConfigSpec) { if val == nil { (*out)[key] = nil } else { - in, out := &val, &outVal + inVal := (*in)[key] + in, out := &inVal, &outVal *out = make(PatternExpressions, len(*in)) copy(*out, *in) } @@ -481,6 +481,36 @@ func (in *CallbackSpec) DeepCopy() *CallbackSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CelExpression) DeepCopyInto(out *CelExpression) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CelExpression. +func (in *CelExpression) DeepCopy() *CelExpression { + if in == nil { + return nil + } + out := new(CelExpression) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CelPredicate) DeepCopyInto(out *CelPredicate) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CelPredicate. +func (in *CelPredicate) DeepCopy() *CelPredicate { + if in == nil { + return nil + } + out := new(CelPredicate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CommonEvaluatorSpec) DeepCopyInto(out *CommonEvaluatorSpec) { *out = *in @@ -998,6 +1028,7 @@ func (in *PatternExpressionOrRef) DeepCopyInto(out *PatternExpressionOrRef) { *out = *in out.PatternExpression = in.PatternExpression out.PatternRef = in.PatternRef + out.CelPredicate = in.CelPredicate if in.All != nil { in, out := &in.All, &out.All *out = make([]UnstructuredPatternExpressionOrRef, len(*in)) @@ -1084,6 +1115,7 @@ func (in *PatternRef) DeepCopy() *PatternRef { func (in *PlainAuthResponseSpec) DeepCopyInto(out *PlainAuthResponseSpec) { *out = *in in.Value.DeepCopyInto(&out.Value) + out.Expression = in.Expression } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlainAuthResponseSpec. @@ -1287,6 +1319,7 @@ func (in *UserInfoMetadataSpec) DeepCopy() *UserInfoMetadataSpec { func (in *ValueOrSelector) DeepCopyInto(out *ValueOrSelector) { *out = *in in.Value.DeepCopyInto(&out.Value) + out.Expression = in.Expression } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueOrSelector. diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index cca5ff1f..5b4c1202 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "github.com/kuadrant/authorino/pkg/expressions/cel" "sort" "sync" @@ -193,10 +194,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf }, true)) } + predicates, err := buildPredicates(authConfig, identity.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedIdentity := &evaluators.IdentityConfig{ Name: identityCfgName, Priority: identity.Priority, - Conditions: buildJSONExpression(authConfig, identity.Conditions, jsonexp.All), + Conditions: predicates, ExtendedProperties: extendedProperties, Metrics: identity.Metrics, } @@ -288,10 +293,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf interfacedMetadataConfigs := make([]auth.AuthConfigEvaluator, 0) for name, metadata := range authConfig.Spec.Metadata { + predicates, err := buildPredicates(authConfig, metadata.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedMetadata := &evaluators.MetadataConfig{ Name: name, Priority: metadata.Priority, - Conditions: buildJSONExpression(authConfig, metadata.Conditions, jsonexp.All), + Conditions: predicates, Metrics: metadata.Metrics, } @@ -357,10 +366,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf authzIndex := 0 for authzName, authorization := range authConfig.Spec.Authorization { + predicates, err := buildPredicates(authConfig, authorization.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedAuthorization := &evaluators.AuthorizationConfig{ Name: authzName, Priority: authorization.Priority, - Conditions: buildJSONExpression(authConfig, authorization.Conditions, jsonexp.All), + Conditions: predicates, Metrics: authorization.Metrics, } @@ -415,8 +428,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf // json case api.PatternMatchingAuthorization: + rules, err := buildPredicates(authConfig, authorization.PatternMatching.Patterns, jsonexp.All) + if err != nil { + return nil, err + } translatedAuthorization.JSON = &authorization_evaluators.JSONPatternMatching{ - Rules: buildJSONExpression(authConfig, authorization.PatternMatching.Patterns, jsonexp.All), + Rules: rules, } case api.KubernetesSubjectAccessReviewAuthorization: @@ -477,10 +494,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf if responseConfig := authConfig.Spec.Response; responseConfig != nil { for responseName, headerSuccessResponse := range responseConfig.Success.Headers { + predicates, err := buildPredicates(authConfig, headerSuccessResponse.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedResponse := evaluators.NewResponseConfig( responseName, headerSuccessResponse.Priority, - buildJSONExpression(authConfig, headerSuccessResponse.Conditions, jsonexp.All), + predicates, "httpHeader", headerSuccessResponse.Key, headerSuccessResponse.Metrics, @@ -495,10 +516,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf } for responseName, successResponse := range responseConfig.Success.DynamicMetadata { + predicates, err := buildPredicates(authConfig, successResponse.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedResponse := evaluators.NewResponseConfig( responseName, successResponse.Priority, - buildJSONExpression(authConfig, successResponse.Conditions, jsonexp.All), + predicates, "envoyDynamicMetadata", successResponse.Key, successResponse.Metrics, @@ -516,10 +541,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf interfacedCallbackConfigs := make([]auth.AuthConfigEvaluator, 0) for callbackName, callback := range authConfig.Spec.Callbacks { + predicates, err := buildPredicates(authConfig, callback.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedCallback := &evaluators.CallbackConfig{ Name: callbackName, Priority: callback.Priority, - Conditions: buildJSONExpression(authConfig, callback.Conditions, jsonexp.All), + Conditions: predicates, Metrics: callback.Metrics, } @@ -539,8 +568,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf interfacedCallbackConfigs = append(interfacedCallbackConfigs, translatedCallback) } + predicates, err := buildPredicates(authConfig, authConfig.Spec.Conditions, jsonexp.All) + if err != nil { + return nil, err + } translatedAuthConfig := &evaluators.AuthConfig{ - Conditions: buildJSONExpression(authConfig, authConfig.Spec.Conditions, jsonexp.All), + Conditions: predicates, IdentityConfigs: interfacedIdentityConfigs, MetadataConfigs: interfacedMetadataConfigs, AuthorizationConfigs: interfacedAuthorizationConfigs, @@ -875,18 +908,26 @@ func findIdentityConfigByName(identityConfigs []evaluators.IdentityConfig, name return nil, fmt.Errorf("missing identity config %v", name) } -func buildJSONExpression(authConfig *api.AuthConfig, patterns []api.PatternExpressionOrRef, op func(...jsonexp.Expression) jsonexp.Expression) jsonexp.Expression { +func buildPredicates(authConfig *api.AuthConfig, patterns []api.PatternExpressionOrRef, op func(...jsonexp.Expression) jsonexp.Expression) (jsonexp.Expression, error) { var expression []jsonexp.Expression for _, pattern := range patterns { // patterns or refs - expression = append(expression, buildJSONExpressionPatterns(authConfig, pattern)...) + expressions, err := buildJSONExpressionPatterns(authConfig, pattern) + if err != nil { + return nil, err + } + expression = append(expression, expressions...) // all if len(pattern.All) > 0 { p := make([]api.PatternExpressionOrRef, len(pattern.All)) for i, ptn := range pattern.All { p[i] = ptn.PatternExpressionOrRef } - expression = append(expression, buildJSONExpression(authConfig, p, jsonexp.All)) + predicates, err := buildPredicates(authConfig, p, jsonexp.All) + if err != nil { + return nil, err + } + expression = append(expression, predicates) } // any if len(pattern.Any) > 0 { @@ -894,25 +935,35 @@ func buildJSONExpression(authConfig *api.AuthConfig, patterns []api.PatternExpre for i, ptn := range pattern.Any { p[i] = ptn.PatternExpressionOrRef } - expression = append(expression, buildJSONExpression(authConfig, p, jsonexp.Any)) + predicates, err := buildPredicates(authConfig, p, jsonexp.Any) + if err != nil { + return nil, err + } + expression = append(expression, predicates) } } - return op(expression...) + return op(expression...), nil } -func buildJSONExpressionPatterns(authConfig *api.AuthConfig, pattern api.PatternExpressionOrRef) []jsonexp.Expression { +func buildJSONExpressionPatterns(authConfig *api.AuthConfig, pattern api.PatternExpressionOrRef) ([]jsonexp.Expression, error) { expressionsToAdd := api.PatternExpressions{} + expressions := make([]jsonexp.Expression, len(expressionsToAdd)) if expressionsByRef, found := authConfig.Spec.NamedPatterns[pattern.PatternRef.Name]; found { expressionsToAdd = append(expressionsToAdd, expressionsByRef...) } else if pattern.PatternExpression.Operator != "" { expressionsToAdd = append(expressionsToAdd, pattern.PatternExpression) + } else if pattern.Predicate != "" { + if predicate, err := cel.NewPredicate(pattern.Predicate); err != nil { + return nil, err + } else { + expressions = append(expressions, predicate) + } } - expressions := make([]jsonexp.Expression, len(expressionsToAdd)) - for i, expression := range expressionsToAdd { - expressions[i] = buildJSONExpressionPattern(expression) + for _, expression := range expressionsToAdd { + expressions = append(expressions, buildJSONExpressionPattern(expression)) } - return expressions + return expressions, nil } func buildJSONExpressionPattern(expression api.PatternExpression) jsonexp.Expression { diff --git a/controllers/auth_config_controller_test.go b/controllers/auth_config_controller_test.go index 84e58631..331a0a23 100644 --- a/controllers/auth_config_controller_test.go +++ b/controllers/auth_config_controller_test.go @@ -95,10 +95,8 @@ func newTestAuthConfig(authConfigLabels map[string]string) api.AuthConfig { PatternMatching: &api.PatternMatchingAuthorizationSpec{ Patterns: []api.PatternExpressionOrRef{ { - PatternExpression: api.PatternExpression{ - Selector: "context.identity.role", - Operator: "eq", - Value: "admin", + CelPredicate: api.CelPredicate{ + Predicate: "context.identity.role == 'admin'", }, }, }, diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 84a61661..1db19475 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2320,6 +2320,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2373,6 +2375,8 @@ spec: defaults: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2453,6 +2457,8 @@ spec: overrides: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2524,6 +2530,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -2615,6 +2623,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2653,6 +2663,8 @@ spec: API group of the resource. Use '*' for all API groups. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2668,6 +2680,8 @@ spec: Resource name Omit it to check for authorization on all resources of the specified kind. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2682,6 +2696,8 @@ spec: description: Namespace where the user must have permissions on the resource. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2697,6 +2713,8 @@ spec: Resource kind Use '*' for all resource kinds. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2710,6 +2728,8 @@ spec: subresource: description: Subresource kind properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2725,6 +2745,8 @@ spec: Verb to check for authorization on the resource. Use '*' for all verbs. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2741,6 +2763,8 @@ spec: User to check for authorization in the Kubernetes RBAC. Omit it to check for group authorization only. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2780,6 +2804,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2793,6 +2819,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2852,6 +2880,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3003,6 +3033,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3041,6 +3073,8 @@ spec: description: The name of the permission (or relation) on which to execute the check. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3057,6 +3091,8 @@ spec: properties: kind: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3069,6 +3105,8 @@ spec: type: object name: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3103,6 +3141,8 @@ spec: properties: kind: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3115,6 +3155,8 @@ spec: type: object name: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3164,6 +3206,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3195,6 +3239,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3222,6 +3268,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3235,6 +3283,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3294,6 +3344,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3446,6 +3498,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3486,6 +3540,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3513,6 +3569,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3526,6 +3584,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3585,6 +3645,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3774,6 +3836,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3847,6 +3911,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3874,6 +3940,8 @@ spec: properties: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3901,6 +3969,8 @@ spec: plain: description: Plain text content properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3953,6 +4023,8 @@ spec: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3972,6 +4044,8 @@ spec: customClaims: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4046,6 +4120,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4073,6 +4149,8 @@ spec: properties: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4100,6 +4178,8 @@ spec: plain: description: Plain text content properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4152,6 +4232,8 @@ spec: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -4171,6 +4253,8 @@ spec: customClaims: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4242,6 +4326,8 @@ spec: description: HTTP response body to override the default denial body. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4262,6 +4348,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4278,6 +4366,8 @@ spec: message: description: HTTP message to override the default denial message. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4299,6 +4389,8 @@ spec: description: HTTP response body to override the default denial body. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4319,6 +4411,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4335,6 +4429,8 @@ spec: message: description: HTTP message to override the default denial message. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4382,6 +4478,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). diff --git a/install/manifests.yaml b/install/manifests.yaml index 6c13cf5e..60491e6d 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2600,6 +2600,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2653,6 +2655,8 @@ spec: defaults: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2733,6 +2737,8 @@ spec: overrides: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2824,6 +2830,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -2932,6 +2940,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2970,6 +2980,8 @@ spec: API group of the resource. Use '*' for all API groups. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2985,6 +2997,8 @@ spec: Resource name Omit it to check for authorization on all resources of the specified kind. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -2999,6 +3013,8 @@ spec: description: Namespace where the user must have permissions on the resource. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3014,6 +3030,8 @@ spec: Resource kind Use '*' for all resource kinds. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3027,6 +3045,8 @@ spec: subresource: description: Subresource kind properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3042,6 +3062,8 @@ spec: Verb to check for authorization on the resource. Use '*' for all verbs. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3058,6 +3080,8 @@ spec: User to check for authorization in the Kubernetes RBAC. Omit it to check for group authorization only. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3097,6 +3121,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3110,6 +3136,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3169,6 +3197,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3340,6 +3370,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3378,6 +3410,8 @@ spec: description: The name of the permission (or relation) on which to execute the check. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3394,6 +3428,8 @@ spec: properties: kind: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3406,6 +3442,8 @@ spec: type: object name: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3440,6 +3478,8 @@ spec: properties: kind: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3452,6 +3492,8 @@ spec: type: object name: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3521,6 +3563,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3552,6 +3596,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3579,6 +3625,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3592,6 +3640,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3651,6 +3701,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3803,6 +3855,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -3856,6 +3910,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3883,6 +3939,8 @@ spec: Supersedes 'bodyParameters'; use either one or the other. Use it with method=POST; for GET requests, set parameters as query string in the 'endpoint' (placeholders can be used). properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3896,6 +3954,8 @@ spec: bodyParameters: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -3955,6 +4015,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4164,6 +4226,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -4250,6 +4314,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4277,6 +4343,8 @@ spec: properties: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4304,6 +4372,8 @@ spec: plain: description: Plain text content properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4376,6 +4446,8 @@ spec: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -4395,6 +4467,8 @@ spec: customClaims: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4482,6 +4556,8 @@ spec: Key used to store the entry in the cache. The resolved key must be unique within the scope of this particular config. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4509,6 +4585,8 @@ spec: properties: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4536,6 +4614,8 @@ spec: plain: description: Plain text content properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4608,6 +4688,8 @@ spec: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -4627,6 +4709,8 @@ spec: customClaims: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4698,6 +4782,8 @@ spec: description: HTTP response body to override the default denial body. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4718,6 +4804,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4734,6 +4822,8 @@ spec: message: description: HTTP message to override the default denial message. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4755,6 +4845,8 @@ spec: description: HTTP response body to override the default denial body. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4775,6 +4867,8 @@ spec: headers: additionalProperties: properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4791,6 +4885,8 @@ spec: message: description: HTTP message to override the default denial message. properties: + expression: + type: string selector: description: |- Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!"). @@ -4858,6 +4954,8 @@ spec: patternRef: description: Reference to a named set of pattern expressions type: string + predicate: + type: string selector: description: |- Path selector to fetch content from the authorization JSON (e.g. 'request.method'). @@ -5029,6 +5127,80 @@ kind: ClusterRole metadata: name: authorino-manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - configmaps/status + verbs: + - delete + - get + - patch + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create - apiGroups: - authorino.kuadrant.io resources: @@ -5049,6 +5221,12 @@ rules: - get - patch - update +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create - apiGroups: - coordination.k8s.io resources: @@ -5066,3 +5244,69 @@ rules: - get - list - watch +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos/finalizers + verbs: + - update +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos/status + verbs: + - get + - patch + - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - create + - get + - list + - update + - watch diff --git a/install/rbac/role.yaml b/install/rbac/role.yaml index 69520e9e..2328df39 100644 --- a/install/rbac/role.yaml +++ b/install/rbac/role.yaml @@ -4,6 +4,80 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - configmaps/status + verbs: + - delete + - get + - patch + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create - apiGroups: - authorino.kuadrant.io resources: @@ -24,6 +98,12 @@ rules: - get - patch - update +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create - apiGroups: - coordination.k8s.io resources: @@ -41,3 +121,69 @@ rules: - get - list - watch +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos/finalizers + verbs: + - update +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos/status + verbs: + - get + - patch + - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - create + - get + - list + - update + - watch diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go index 47f81cc1..c5e709c5 100644 --- a/pkg/evaluators/response/dynamic_cel.go +++ b/pkg/evaluators/response/dynamic_cel.go @@ -2,25 +2,16 @@ package response import ( "context" - "strings" - - "github.com/golang/protobuf/jsonpb" - "github.com/google/cel-go/cel" - "github.com/google/cel-go/checker/decls" + interpreter "github.com/google/cel-go/cel" "github.com/kuadrant/authorino/pkg/auth" - "github.com/kuadrant/authorino/pkg/expressions" - "google.golang.org/protobuf/types/known/structpb" + "github.com/kuadrant/authorino/pkg/expressions/cel" ) -const rootBinding = "auth" - func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { cel_exp := DynamicCEL{} - if program, err := expressions.CelCompile(expression, cel.Declarations( - decls.NewConst(rootBinding, decls.NewObjectType("google.protobuf.Struct"), nil), - )); err != nil { + if program, err := cel.Compile(expression, false); err != nil { return nil, err } else { cel_exp.program = program @@ -30,26 +21,20 @@ func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { } type DynamicCEL struct { - program cel.Program + program interpreter.Program } func (c *DynamicCEL) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { - - auth_json := pipeline.GetAuthorizationJSON() - data := structpb.Struct{} - if err := jsonpb.Unmarshal(strings.NewReader(auth_json), &data); err != nil { + input, err := cel.AuthJsonToCel(pipeline.GetAuthorizationJSON()) + if err != nil { return nil, err } - - value := data.GetFields()["auth"] - result, _, err := c.program.Eval(map[string]interface{}{ - rootBinding: value, - }) + result, _, err := c.program.Eval(input) if err != nil { return nil, err } - if jsonVal, err := expressions.CelValueToJSON(result); err != nil { + if jsonVal, err := cel.ValueToJSON(result); err != nil { return nil, err } else { return jsonVal, nil diff --git a/pkg/expressions/cel.go b/pkg/expressions/cel.go deleted file mode 100644 index 71286573..00000000 --- a/pkg/expressions/cel.go +++ /dev/null @@ -1,47 +0,0 @@ -package expressions - -import ( - "reflect" - - "github.com/google/cel-go/cel" - "github.com/google/cel-go/common/types/ref" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" -) - -func CelCompile(expression string, opts ...cel.EnvOption) (cel.Program, error) { - env, env_err := cel.NewEnv(opts...) - if env_err != nil { - return nil, env_err - } - - ast, issues := env.Parse(expression) - if issues.Err() != nil { - return nil, issues.Err() - } - - checked, issues := env.Check(ast) - if issues.Err() != nil { - return nil, issues.Err() - } - - program, err := env.Program(checked) - if err != nil { - return nil, err - } - return program, nil -} - -func CelValueToJSON(val ref.Val) (string, error) { - v, err := val.ConvertToNative(reflect.TypeOf(&structpb.Value{})) - if err != nil { - return "", err - } - marshaller := protojson.MarshalOptions{Multiline: false} - bytes, err := marshaller.Marshal(v.(proto.Message)) - if err != nil { - return "", err - } - return string(bytes), nil -} diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go new file mode 100644 index 00000000..87d58e7d --- /dev/null +++ b/pkg/expressions/cel/expressions.go @@ -0,0 +1,102 @@ +package cel + +import ( + "fmt" + "github.com/golang/protobuf/jsonpb" + "github.com/google/cel-go/checker/decls" + "reflect" + "strings" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +const RootBinding = "auth" + +type Predicate struct { + program cel.Program + source string +} + +func NewPredicate(source string) (*Predicate, error) { + program, err := Compile(source, true) + if err != nil { + return nil, err + } + return &Predicate{ + program: program, + source: source, + }, nil +} + +func (p *Predicate) Matches(json string) (bool, error) { + input, err := AuthJsonToCel(json) + if err != nil { + return false, err + } + result, _, err := p.program.Eval(input) + if err != nil { + return false, err + } + return result.Value().(bool), nil +} + +func Compile(expression string, predicate bool, opts ...cel.EnvOption) (cel.Program, error) { + envOpts := append([]cel.EnvOption{cel.Declarations( + decls.NewConst(RootBinding, decls.NewObjectType("google.protobuf.Struct"), nil))}, opts...) + env, env_err := cel.NewEnv(envOpts...) + if env_err != nil { + return nil, env_err + } + + ast, issues := env.Parse(expression) + if issues.Err() != nil { + return nil, issues.Err() + } + + checked, issues := env.Check(ast) + if issues.Err() != nil { + return nil, issues.Err() + } + + if predicate { + if !reflect.DeepEqual(checked.OutputType(), cel.BoolType) && !reflect.DeepEqual(checked.OutputType(), cel.DynType) { + return nil, fmt.Errorf("type error: got %v, wanted %v output type", checked.OutputType(), cel.BoolType) + } + } + + program, err := env.Program(checked) + if err != nil { + return nil, err + } + return program, nil +} + +func ValueToJSON(val ref.Val) (string, error) { + v, err := val.ConvertToNative(reflect.TypeOf(&structpb.Value{})) + if err != nil { + return "", err + } + marshaller := protojson.MarshalOptions{Multiline: false} + bytes, err := marshaller.Marshal(v.(proto.Message)) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// todo this should eventually be sourced as proper proto from the pipeline +func AuthJsonToCel(json string) (map[string]interface{}, error) { + data := structpb.Struct{} + if err := jsonpb.Unmarshal(strings.NewReader(json), &data); err != nil { + return nil, err + } + value := data.GetFields()["auth"] + input := map[string]interface{}{ + RootBinding: value, + } + return input, nil +} diff --git a/pkg/expressions/cel/expressions_test.go b/pkg/expressions/cel/expressions_test.go new file mode 100644 index 00000000..39d4a1e0 --- /dev/null +++ b/pkg/expressions/cel/expressions_test.go @@ -0,0 +1,36 @@ +package cel + +import ( + "testing" + + mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" + "gotest.tools/assert" + + "github.com/golang/mock/gomock" +) + +func TestPredicate(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + predicate, err := NewPredicate(`{"prop1": "value1", "prop2": auth.identity.username}`) + assert.ErrorContains(t, err, "wanted bool output type") + + pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) + + predicate, err = NewPredicate(`false == true`) + assert.NilError(t, err) + + pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john","evil": false}}}`) + response, err := predicate.Matches(pipelineMock.GetAuthorizationJSON()) + assert.NilError(t, err) + assert.Equal(t, response, false) + + predicate, err = NewPredicate(`auth.identity.evil == false`) + assert.NilError(t, err) + + pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john","evil": false}}}`) + response, err = predicate.Matches(pipelineMock.GetAuthorizationJSON()) + assert.NilError(t, err) + assert.Equal(t, response, true) +} diff --git a/tests/v1beta2/authconfig.yaml b/tests/v1beta2/authconfig.yaml index 93114dc8..630eac33 100644 --- a/tests/v1beta2/authconfig.yaml +++ b/tests/v1beta2/authconfig.yaml @@ -74,9 +74,7 @@ spec: anonymous: {} priority: 1 when: - - selector: context.request.http.method - operator: eq - value: GET + - predicate: context.request.http.method == "GET" - selector: context.request.http.path operator: matches value: ^/$