From 3d79881a7318c3044a25a474d261396671856fc7 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 14 Oct 2024 15:31:36 -0400 Subject: [PATCH 01/43] First seam: DynamicCEL Signed-off-by: Alex Snaps --- api/v1beta2/auth_config_types.go | 3 + controllers/auth_config_controller.go | 7 ++ go.mod | 3 + go.sum | 7 ++ pkg/evaluators/response.go | 1 + pkg/evaluators/response/dynamic_cel.go | 90 ++++++++++++++++++++ pkg/evaluators/response/dynamic_cel_test.go | 35 ++++++++ pkg/evaluators/response/dynamic_json_test.go | 4 + 8 files changed, 150 insertions(+) create mode 100644 pkg/evaluators/response/dynamic_cel.go create mode 100644 pkg/evaluators/response/dynamic_cel_test.go diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index c689e51b..17f812f3 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -51,6 +51,7 @@ const ( PlainAuthResponse JsonAuthResponse WristbandAuthResponse + CelAuthResponse // The following constants are used to identify the different methods of callback functions. UnknownCallbackMethod CallbackMethod = iota @@ -732,6 +733,8 @@ func (s *SuccessResponseSpec) GetMethod() AuthResponseMethod { type AuthResponseMethodSpec struct { // Plain text content Plain *PlainAuthResponseSpec `json:"plain,omitempty"` + // Cel Expression, where the result is outputted as JSON + Expression string `json:"expression,omitempty"` // JSON object // Specify it as the list of properties of the object, whose values can combine static values and values selected from the authorization JSON. Json *JsonAuthResponseSpec `json:"json,omitempty"` diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 6de5191f..bc591dc8 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -628,6 +628,13 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe translatedResponse.DynamicJSON = response_evaluators.NewDynamicJSONResponse(jsonProperties) + case api.CelAuthResponse: + if exp, err := response_evaluators.NewDynamicCelResponse(string(*successResponse.Expression)); err != nil { + return err + } else { + translatedResponse.DynamicCEL = exp + } + // plain case api.PlainAuthResponse: translatedResponse.Plain = &response_evaluators.Plain{ diff --git a/go.mod b/go.mod index 3917d46c..80ee0f7a 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/gogo/googleapis v1.4.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/mock v1.6.0 + github.com/google/cel-go v0.21.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/hashicorp/go-multierror v1.1.1 github.com/open-policy-agent/opa v0.68.0 @@ -43,6 +44,7 @@ require ( require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -59,6 +61,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect diff --git a/go.sum b/go.sum index 784a7ad9..d9ef5732 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI= github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= @@ -226,6 +228,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -506,6 +510,8 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -517,6 +523,7 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/pkg/evaluators/response.go b/pkg/evaluators/response.go index 6ee4b156..f96dca01 100644 --- a/pkg/evaluators/response.go +++ b/pkg/evaluators/response.go @@ -53,6 +53,7 @@ type ResponseConfig struct { Cache EvaluatorCache Wristband auth.WristbandIssuer `yaml:"wristband,omitempty"` + DynamicCEL *response.DynamicCEL `yaml:"json,omitempty"` DynamicJSON *response.DynamicJSON `yaml:"json,omitempty"` Plain *response.Plain `yaml:"plain,omitempty"` } diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go new file mode 100644 index 00000000..e4be22dc --- /dev/null +++ b/pkg/evaluators/response/dynamic_cel.go @@ -0,0 +1,90 @@ +package response + +import ( + "context" + "reflect" + "strings" + + "github.com/golang/protobuf/jsonpb" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/common/types/ref" + "github.com/kuadrant/authorino/pkg/auth" + "google.golang.org/protobuf/types/known/structpb" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +const rootBinding = "auth" + +func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { + + cel_exp := DynamicCEL{} + + env, err := cel.NewEnv(cel.Declarations( + decls.NewConst(rootBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + )) + if err != nil { + return nil, 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 + } + + cel_exp.program = program + + return &cel_exp, nil +} + +type DynamicCEL struct { + program cel.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 { + return nil, err + } + + value := data.GetFields()["auth"] + result, _, err := c.program.Eval(map[string]interface{}{ + rootBinding: value, + }) + if err != nil { + return nil, err + } + + if jsonVal, err := valueToJSON(result); err != nil { + return nil, err + } else { + return jsonVal, 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 +} diff --git a/pkg/evaluators/response/dynamic_cel_test.go b/pkg/evaluators/response/dynamic_cel_test.go new file mode 100644 index 00000000..1089d444 --- /dev/null +++ b/pkg/evaluators/response/dynamic_cel_test.go @@ -0,0 +1,35 @@ +package response + +import ( + "context" + "encoding/json" + "testing" + + mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" + "gotest.tools/assert" + + "github.com/golang/mock/gomock" +) + +func TestDynamicCELCall(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + celResponseEvaluator, _ := NewDynamicCelResponse(`{"prop1": "value1", "prop2": auth.identity.username}`) + + pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) + pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john","evil": false}}}`) + + response, err := celResponseEvaluator.Call(pipelineMock, context.TODO()) + assert.NilError(t, err) + + // We need to parse this response: https://protobuf.dev/reference/go/faq/#unstable-json + result := struct { + Prop1 string `json:"prop1"` + Prop2 string `json:"prop2"` + }{} + assert.NilError(t, json.Unmarshal([]byte(response.(string)), &result)) + + assert.Equal(t, result.Prop1, "value1") + assert.Equal(t, result.Prop2, "john") +} diff --git a/pkg/evaluators/response/dynamic_json_test.go b/pkg/evaluators/response/dynamic_json_test.go index 47d48b93..417f9adb 100644 --- a/pkg/evaluators/response/dynamic_json_test.go +++ b/pkg/evaluators/response/dynamic_json_test.go @@ -19,6 +19,10 @@ func TestDynamicJSONCall(t *testing.T) { jsonProperties := []json.JSONProperty{ {Name: "prop1", Value: json.JSONValue{Static: "value1"}}, {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, + {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, + {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, + {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, + {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, } jsonResponseEvaluator := NewDynamicJSONResponse(jsonProperties) From 79882b395c7540e42b0cc8852b38a559a2f47478 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 14 Oct 2024 16:03:21 -0400 Subject: [PATCH 02/43] Refactored Cel into own pkg, removed CelResponse Signed-off-by: Alex Snaps --- api/v1beta2/auth_config_types.go | 3 -- controllers/auth_config_controller.go | 7 --- pkg/evaluators/response.go | 1 - pkg/evaluators/response/dynamic_cel.go | 45 +++---------------- pkg/evaluators/response/dynamic_json_test.go | 4 -- pkg/expressions/cel.go | 47 ++++++++++++++++++++ 6 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 pkg/expressions/cel.go diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index 17f812f3..c689e51b 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -51,7 +51,6 @@ const ( PlainAuthResponse JsonAuthResponse WristbandAuthResponse - CelAuthResponse // The following constants are used to identify the different methods of callback functions. UnknownCallbackMethod CallbackMethod = iota @@ -733,8 +732,6 @@ func (s *SuccessResponseSpec) GetMethod() AuthResponseMethod { type AuthResponseMethodSpec struct { // Plain text content Plain *PlainAuthResponseSpec `json:"plain,omitempty"` - // Cel Expression, where the result is outputted as JSON - Expression string `json:"expression,omitempty"` // JSON object // Specify it as the list of properties of the object, whose values can combine static values and values selected from the authorization JSON. Json *JsonAuthResponseSpec `json:"json,omitempty"` diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index bc591dc8..6de5191f 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -628,13 +628,6 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe translatedResponse.DynamicJSON = response_evaluators.NewDynamicJSONResponse(jsonProperties) - case api.CelAuthResponse: - if exp, err := response_evaluators.NewDynamicCelResponse(string(*successResponse.Expression)); err != nil { - return err - } else { - translatedResponse.DynamicCEL = exp - } - // plain case api.PlainAuthResponse: translatedResponse.Plain = &response_evaluators.Plain{ diff --git a/pkg/evaluators/response.go b/pkg/evaluators/response.go index f96dca01..6ee4b156 100644 --- a/pkg/evaluators/response.go +++ b/pkg/evaluators/response.go @@ -53,7 +53,6 @@ type ResponseConfig struct { Cache EvaluatorCache Wristband auth.WristbandIssuer `yaml:"wristband,omitempty"` - DynamicCEL *response.DynamicCEL `yaml:"json,omitempty"` DynamicJSON *response.DynamicJSON `yaml:"json,omitempty"` Plain *response.Plain `yaml:"plain,omitempty"` } diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go index e4be22dc..47f81cc1 100644 --- a/pkg/evaluators/response/dynamic_cel.go +++ b/pkg/evaluators/response/dynamic_cel.go @@ -2,18 +2,14 @@ package response import ( "context" - "reflect" "strings" "github.com/golang/protobuf/jsonpb" "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" - "github.com/google/cel-go/common/types/ref" "github.com/kuadrant/authorino/pkg/auth" + "github.com/kuadrant/authorino/pkg/expressions" "google.golang.org/protobuf/types/known/structpb" - - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" ) const rootBinding = "auth" @@ -22,30 +18,14 @@ func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { cel_exp := DynamicCEL{} - env, err := cel.NewEnv(cel.Declarations( + if program, err := expressions.CelCompile(expression, cel.Declarations( decls.NewConst(rootBinding, decls.NewObjectType("google.protobuf.Struct"), nil), - )) - if err != nil { - return nil, 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 { + )); err != nil { return nil, err + } else { + cel_exp.program = program } - cel_exp.program = program - return &cel_exp, nil } @@ -69,22 +49,9 @@ func (c *DynamicCEL) Call(pipeline auth.AuthPipeline, ctx context.Context) (inte return nil, err } - if jsonVal, err := valueToJSON(result); err != nil { + if jsonVal, err := expressions.CelValueToJSON(result); err != nil { return nil, err } else { return jsonVal, 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 -} diff --git a/pkg/evaluators/response/dynamic_json_test.go b/pkg/evaluators/response/dynamic_json_test.go index 417f9adb..47d48b93 100644 --- a/pkg/evaluators/response/dynamic_json_test.go +++ b/pkg/evaluators/response/dynamic_json_test.go @@ -19,10 +19,6 @@ func TestDynamicJSONCall(t *testing.T) { jsonProperties := []json.JSONProperty{ {Name: "prop1", Value: json.JSONValue{Static: "value1"}}, {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, - {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, - {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, - {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, - {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, } jsonResponseEvaluator := NewDynamicJSONResponse(jsonProperties) diff --git a/pkg/expressions/cel.go b/pkg/expressions/cel.go new file mode 100644 index 00000000..71286573 --- /dev/null +++ b/pkg/expressions/cel.go @@ -0,0 +1,47 @@ +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 +} From 95d3bfac9a848aa0e1166a6ce616d043c662a980 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 14 Oct 2024 16:24:26 -0400 Subject: [PATCH 03/43] Adding the new seams Signed-off-by: Alex Snaps --- api/v1beta2/auth_config_types.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index c689e51b..fd4f9e25 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -163,13 +163,21 @@ type PatternExpression struct { Value string `json:"value,omitempty"` } +type CelExpression struct { + Expression string `json:"expression,omitempty"` +} + +type CelPredicate struct { + Predicate string `json:"predicate,omitempty"` +} + // +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches type PatternExpressionOperator string type PatternExpressionOrRef struct { PatternExpression `json:",omitempty"` PatternRef `json:",omitempty"` - + CelPredicate `json:",omitempty"` // A list of pattern expressions to be evaluated as a logical AND. All []UnstructuredPatternExpressionOrRef `json:"all,omitempty"` // A list of pattern expressions to be evaluated as a logical OR. @@ -198,6 +206,8 @@ type ValueOrSelector struct { // Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector,omitempty"` + + Expression CelExpression `json:",omitempty"` } type CommonEvaluatorSpec struct { From e138990356d69cf264a918053547a733b1d0859c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 07:15:01 -0400 Subject: [PATCH 04/43] Cel Predicates Signed-off-by: Alex Snaps --- api/v1beta2/zz_generated.deepcopy.go | 33 +++ controllers/auth_config_controller.go | 87 +++++-- controllers/auth_config_controller_test.go | 6 +- .../authorino.kuadrant.io_authconfigs.yaml | 98 +++++++ install/manifests.yaml | 244 ++++++++++++++++++ install/rbac/role.yaml | 146 +++++++++++ pkg/evaluators/response/dynamic_cel.go | 30 +-- pkg/expressions/cel.go | 47 ---- pkg/expressions/cel/expressions.go | 102 ++++++++ pkg/expressions/cel/expressions_test.go | 36 +++ tests/v1beta2/authconfig.yaml | 4 +- 11 files changed, 739 insertions(+), 94 deletions(-) delete mode 100644 pkg/expressions/cel.go create mode 100644 pkg/expressions/cel/expressions.go create mode 100644 pkg/expressions/cel/expressions_test.go diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 29171143..d578097a 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -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 6de5191f..4a65ac3d 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -29,6 +29,7 @@ import ( identity_evaluators "github.com/kuadrant/authorino/pkg/evaluators/identity" metadata_evaluators "github.com/kuadrant/authorino/pkg/evaluators/metadata" response_evaluators "github.com/kuadrant/authorino/pkg/evaluators/response" + "github.com/kuadrant/authorino/pkg/expressions/cel" "github.com/kuadrant/authorino/pkg/index" "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/jsonexp" @@ -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 8169a6d3..b7e97f3b 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 7c15cc4b..e96ee99d 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -150,6 +150,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}!"). @@ -203,6 +205,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}!"). @@ -283,6 +287,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}!"). @@ -354,6 +360,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'). @@ -445,6 +453,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}!"). @@ -483,6 +493,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}!"). @@ -498,6 +510,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}!"). @@ -512,6 +526,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}!"). @@ -527,6 +543,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}!"). @@ -540,6 +558,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}!"). @@ -555,6 +575,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}!"). @@ -571,6 +593,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}!"). @@ -610,6 +634,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}!"). @@ -623,6 +649,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}!"). @@ -682,6 +710,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}!"). @@ -833,6 +863,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'). @@ -871,6 +903,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}!"). @@ -887,6 +921,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}!"). @@ -899,6 +935,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}!"). @@ -933,6 +971,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}!"). @@ -945,6 +985,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}!"). @@ -994,6 +1036,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'). @@ -1025,6 +1069,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}!"). @@ -1052,6 +1098,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}!"). @@ -1065,6 +1113,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}!"). @@ -1124,6 +1174,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}!"). @@ -1276,6 +1328,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'). @@ -1316,6 +1370,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}!"). @@ -1343,6 +1399,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}!"). @@ -1356,6 +1414,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}!"). @@ -1415,6 +1475,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}!"). @@ -1604,6 +1666,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'). @@ -1677,6 +1741,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}!"). @@ -1704,6 +1770,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}!"). @@ -1731,6 +1799,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}!"). @@ -1783,6 +1853,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'). @@ -1802,6 +1874,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}!"). @@ -1876,6 +1950,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}!"). @@ -1903,6 +1979,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}!"). @@ -1930,6 +2008,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}!"). @@ -1982,6 +2062,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'). @@ -2001,6 +2083,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}!"). @@ -2072,6 +2156,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}!"). @@ -2092,6 +2178,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}!"). @@ -2108,6 +2196,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}!"). @@ -2129,6 +2219,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}!"). @@ -2149,6 +2241,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}!"). @@ -2165,6 +2259,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}!"). @@ -2212,6 +2308,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 3bc0f460..99b57c65 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2719,6 +2719,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}!"). @@ -2772,6 +2774,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}!"). @@ -2852,6 +2856,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}!"). @@ -2943,6 +2949,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'). @@ -3051,6 +3059,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}!"). @@ -3089,6 +3099,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}!"). @@ -3104,6 +3116,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}!"). @@ -3118,6 +3132,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}!"). @@ -3133,6 +3149,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}!"). @@ -3146,6 +3164,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}!"). @@ -3161,6 +3181,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}!"). @@ -3177,6 +3199,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}!"). @@ -3216,6 +3240,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}!"). @@ -3229,6 +3255,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}!"). @@ -3288,6 +3316,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}!"). @@ -3459,6 +3489,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'). @@ -3497,6 +3529,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}!"). @@ -3513,6 +3547,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}!"). @@ -3525,6 +3561,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}!"). @@ -3559,6 +3597,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}!"). @@ -3571,6 +3611,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}!"). @@ -3640,6 +3682,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'). @@ -3671,6 +3715,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}!"). @@ -3698,6 +3744,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}!"). @@ -3711,6 +3759,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}!"). @@ -3770,6 +3820,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}!"). @@ -3922,6 +3974,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'). @@ -3975,6 +4029,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}!"). @@ -4002,6 +4058,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}!"). @@ -4015,6 +4073,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}!"). @@ -4074,6 +4134,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}!"). @@ -4283,6 +4345,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'). @@ -4369,6 +4433,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}!"). @@ -4396,6 +4462,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}!"). @@ -4423,6 +4491,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}!"). @@ -4495,6 +4565,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'). @@ -4514,6 +4586,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}!"). @@ -4601,6 +4675,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}!"). @@ -4628,6 +4704,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}!"). @@ -4655,6 +4733,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}!"). @@ -4727,6 +4807,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'). @@ -4746,6 +4828,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}!"). @@ -4817,6 +4901,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}!"). @@ -4837,6 +4923,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}!"). @@ -4853,6 +4941,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}!"). @@ -4874,6 +4964,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}!"). @@ -4894,6 +4986,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}!"). @@ -4910,6 +5004,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}!"). @@ -4977,6 +5073,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'). @@ -5148,6 +5246,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: @@ -5168,6 +5340,12 @@ rules: - get - patch - update +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create - apiGroups: - coordination.k8s.io resources: @@ -5185,3 +5363,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..d538c5b7 100644 --- a/pkg/evaluators/response/dynamic_cel.go +++ b/pkg/evaluators/response/dynamic_cel.go @@ -2,25 +2,17 @@ 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 +22,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..0008ddc9 --- /dev/null +++ b/pkg/expressions/cel/expressions.go @@ -0,0 +1,102 @@ +package cel + +import ( + "fmt" + "reflect" + "strings" + + "github.com/golang/protobuf/jsonpb" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "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: ^/$ From 6087b3c490a2f22db155ede186a0e02a6523759b Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 08:11:46 -0400 Subject: [PATCH 05/43] Add `context` to Cel's context Signed-off-by: Alex Snaps --- pkg/expressions/cel/expressions.go | 13 +++++++++---- pkg/expressions/cel/expressions_test.go | 10 +++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 0008ddc9..9d7be3f6 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -14,7 +14,8 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -const RootBinding = "auth" +const RootAuthBinding = "auth" +const RootContextBinding = "context" type Predicate struct { program cel.Program @@ -46,7 +47,9 @@ func (p *Predicate) Matches(json string) (bool, error) { 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...) + decls.NewConst(RootAuthBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + decls.NewConst(RootContextBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + )}, opts...) env, env_err := cel.NewEnv(envOpts...) if env_err != nil { return nil, env_err @@ -94,9 +97,11 @@ func AuthJsonToCel(json string) (map[string]interface{}, error) { if err := jsonpb.Unmarshal(strings.NewReader(json), &data); err != nil { return nil, err } - value := data.GetFields()["auth"] + auth := data.GetFields()["auth"] + context := data.GetFields()["context"] input := map[string]interface{}{ - RootBinding: value, + RootAuthBinding: auth, + RootContextBinding: context, } return input, nil } diff --git a/pkg/expressions/cel/expressions_test.go b/pkg/expressions/cel/expressions_test.go index 39d4a1e0..e7f228ef 100644 --- a/pkg/expressions/cel/expressions_test.go +++ b/pkg/expressions/cel/expressions_test.go @@ -13,7 +13,7 @@ func TestPredicate(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - predicate, err := NewPredicate(`{"prop1": "value1", "prop2": auth.identity.username}`) + predicate, err := NewPredicate(`context`) assert.ErrorContains(t, err, "wanted bool output type") pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) @@ -33,4 +33,12 @@ func TestPredicate(t *testing.T) { response, err = predicate.Matches(pipelineMock.GetAuthorizationJSON()) assert.NilError(t, err) assert.Equal(t, response, true) + + predicate, err = NewPredicate(`context.request.http.method == "GET"`) + assert.NilError(t, err) + + pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http": {"method": "GET"}}}}`) + response, err = predicate.Matches(pipelineMock.GetAuthorizationJSON()) + assert.NilError(t, err) + assert.Equal(t, response, true) } From e469c1779d332047fef996cd76a3cc2ab334e326 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 08:59:39 -0400 Subject: [PATCH 06/43] Cel Expressions Signed-off-by: Alex Snaps --- pkg/evaluators/authorization.go | 2 +- pkg/evaluators/authorization/authzed.go | 34 ++++++++++--- .../authorization/kubernetes_authz.go | 50 +++++++++++++++---- pkg/evaluators/cache.go | 4 +- pkg/evaluators/identity.go | 8 ++- pkg/evaluators/identity/plain.go | 4 +- pkg/evaluators/identity_extension.go | 4 +- pkg/evaluators/identity_extension_test.go | 4 +- pkg/evaluators/metadata.go | 2 +- pkg/evaluators/metadata/generic_http.go | 22 ++++++-- pkg/evaluators/response.go | 2 +- pkg/evaluators/response/dynamic_json.go | 6 ++- pkg/evaluators/response/plain.go | 2 +- pkg/evaluators/response/wristband.go | 6 ++- pkg/expressions/cel/expressions.go | 23 +++++++++ pkg/expressions/types.go | 5 ++ pkg/json/json.go | 6 ++- pkg/json/json_test.go | 18 +++---- pkg/service/auth_pipeline.go | 9 ++-- 19 files changed, 163 insertions(+), 48 deletions(-) create mode 100644 pkg/expressions/types.go diff --git a/pkg/evaluators/authorization.go b/pkg/evaluators/authorization.go index 75df06d3..a23f5059 100644 --- a/pkg/evaluators/authorization.go +++ b/pkg/evaluators/authorization.go @@ -57,7 +57,7 @@ func (config *AuthorizationConfig) Call(pipeline auth.AuthPipeline, ctx context. var cacheKey interface{} if cache != nil { - cacheKey = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) + cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) if cachedObj, err := cache.Get(cacheKey); err != nil { logger.V(1).Error(err, "failed to retrieve data from the cache") } else if cachedObj != nil { diff --git a/pkg/evaluators/authorization/authzed.go b/pkg/evaluators/authorization/authzed.go index cb65acda..1759b17b 100644 --- a/pkg/evaluators/authorization/authzed.go +++ b/pkg/evaluators/authorization/authzed.go @@ -48,10 +48,22 @@ func (a *Authzed) Call(pipeline auth.AuthPipeline, ctx gocontext.Context) (inter authJSON := pipeline.GetAuthorizationJSON() + resource, err := authzedObjectFor(a.Resource, a.ResourceKind, authJSON) + if err != nil { + return nil, err + } + object, err := authzedObjectFor(a.Subject, a.SubjectKind, authJSON) + if err != nil { + return nil, err + } + permission, err := a.Permission.ResolveFor(authJSON) + if err != nil { + return nil, err + } resp, err := client.CheckPermission(ctx, &authzedpb.CheckPermissionRequest{ - Resource: authzedObjectFor(a.Resource, a.ResourceKind, authJSON), - Subject: &authzedpb.SubjectReference{Object: authzedObjectFor(a.Subject, a.SubjectKind, authJSON)}, - Permission: fmt.Sprintf("%s", a.Permission.ResolveFor(authJSON)), + Resource: resource, + Subject: &authzedpb.SubjectReference{Object: object}, + Permission: fmt.Sprintf("%s", permission), }) if err != nil { return nil, err @@ -74,9 +86,17 @@ func (a *Authzed) Call(pipeline auth.AuthPipeline, ctx gocontext.Context) (inter return obj, nil } -func authzedObjectFor(name, kind json.JSONValue, authJSON string) *authzedpb.ObjectReference { - return &authzedpb.ObjectReference{ - ObjectId: fmt.Sprintf("%s", name.ResolveFor(authJSON)), - ObjectType: fmt.Sprintf("%s", kind.ResolveFor(authJSON)), +func authzedObjectFor(name, kind json.JSONValue, authJSON string) (*authzedpb.ObjectReference, error) { + objectId, err := name.ResolveFor(authJSON) + if err != nil { + return nil, err } + objectType, err := kind.ResolveFor(authJSON) + if err != nil { + return nil, err + } + return &authzedpb.ObjectReference{ + ObjectId: fmt.Sprintf("%s", objectId), + ObjectType: fmt.Sprintf("%s", objectType), + }, nil } diff --git a/pkg/evaluators/authorization/kubernetes_authz.go b/pkg/evaluators/authorization/kubernetes_authz.go index e37fa80f..a382248b 100644 --- a/pkg/evaluators/authorization/kubernetes_authz.go +++ b/pkg/evaluators/authorization/kubernetes_authz.go @@ -63,26 +63,58 @@ func (k *KubernetesAuthz) Call(pipeline auth.AuthPipeline, ctx gocontext.Context } authJSON := pipeline.GetAuthorizationJSON() - jsonValueToStr := func(value json.JSONValue) string { - return fmt.Sprintf("%s", value.ResolveFor(authJSON)) + jsonValueToStr := func(value json.JSONValue) (string, error) { + resolved, err := value.ResolveFor(authJSON) + if err != nil { + return "", err + } + return fmt.Sprintf("%s", resolved), nil } + user, err := jsonValueToStr(k.User) + if err != nil { + return nil, err + } subjectAccessReview := kubeAuthz.SubjectAccessReview{ Spec: kubeAuthz.SubjectAccessReviewSpec{ - User: jsonValueToStr(k.User), + User: user, }, } if k.ResourceAttributes != nil { resourceAttributes := k.ResourceAttributes + namespace, err := jsonValueToStr(resourceAttributes.Namespace) + if err != nil { + return nil, err + } + group, err := jsonValueToStr(resourceAttributes.Group) + if err != nil { + return nil, err + } + resource, err := jsonValueToStr(resourceAttributes.Resource) + if err != nil { + return nil, err + } + name, err := jsonValueToStr(resourceAttributes.Name) + if err != nil { + return nil, err + } + subresource, err := jsonValueToStr(resourceAttributes.SubResource) + if err != nil { + return nil, err + } + verb, err := jsonValueToStr(resourceAttributes.Verb) + if err != nil { + return nil, err + } subjectAccessReview.Spec.ResourceAttributes = &kubeAuthz.ResourceAttributes{ - Namespace: jsonValueToStr(resourceAttributes.Namespace), - Group: jsonValueToStr(resourceAttributes.Group), - Resource: jsonValueToStr(resourceAttributes.Resource), - Name: jsonValueToStr(resourceAttributes.Name), - Subresource: jsonValueToStr(resourceAttributes.SubResource), - Verb: jsonValueToStr(resourceAttributes.Verb), + Namespace: namespace, + Group: group, + Resource: resource, + Name: name, + Subresource: subresource, + Verb: verb, } } else { request := pipeline.GetHttp() diff --git a/pkg/evaluators/cache.go b/pkg/evaluators/cache.go index 05aa4559..b5bd3a75 100644 --- a/pkg/evaluators/cache.go +++ b/pkg/evaluators/cache.go @@ -16,7 +16,7 @@ var EvaluatorCacheSize int // in megabytes type EvaluatorCache interface { Get(key interface{}) (interface{}, error) Set(key, value interface{}) error - ResolveKeyFor(authJSON string) interface{} + ResolveKeyFor(authJSON string) (interface{}, error) Shutdown() error } @@ -58,7 +58,7 @@ func (c *evaluatorCache) Set(key, value interface{}) error { } } -func (c *evaluatorCache) ResolveKeyFor(authJSON string) interface{} { +func (c *evaluatorCache) ResolveKeyFor(authJSON string) (interface{}, error) { return c.keyTemplate.ResolveFor(authJSON) } diff --git a/pkg/evaluators/identity.go b/pkg/evaluators/identity.go index 1c712f01..887b8a41 100644 --- a/pkg/evaluators/identity.go +++ b/pkg/evaluators/identity.go @@ -84,7 +84,7 @@ func (config *IdentityConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte var cacheKey interface{} if cache != nil { - cacheKey = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) + cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) if cachedObj, err := cache.Get(cacheKey); err != nil { logger.V(1).Error(err, "failed to retrieve data from the cache") } else if cachedObj != nil { @@ -197,7 +197,11 @@ func (config *IdentityConfig) ResolveExtendedProperties(pipeline auth.AuthPipeli authJSON := pipeline.GetAuthorizationJSON() for _, extendedProperty := range config.ExtendedProperties { - extendedIdentityObject[extendedProperty.Name] = extendedProperty.ResolveFor(extendedIdentityObject, authJSON) + resolved, err := extendedProperty.ResolveFor(extendedIdentityObject, authJSON) + if err != nil { + return nil, err + } + extendedIdentityObject[extendedProperty.Name] = resolved } return extendedIdentityObject, nil diff --git a/pkg/evaluators/identity/plain.go b/pkg/evaluators/identity/plain.go index d30f0636..d996d774 100644 --- a/pkg/evaluators/identity/plain.go +++ b/pkg/evaluators/identity/plain.go @@ -18,8 +18,10 @@ type Plain struct { func (p *Plain) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { pattern := json.JSONValue{Pattern: p.Pattern} - if object := pattern.ResolveFor(pipeline.GetAuthorizationJSON()); object != nil { + if object, err := pattern.ResolveFor(pipeline.GetAuthorizationJSON()); object != nil { return object, nil + } else if err != nil { + return nil, err } return nil, fmt.Errorf("could not retrieve identity object or null") } diff --git a/pkg/evaluators/identity_extension.go b/pkg/evaluators/identity_extension.go index b945e10e..f57e1ed9 100644 --- a/pkg/evaluators/identity_extension.go +++ b/pkg/evaluators/identity_extension.go @@ -17,9 +17,9 @@ type IdentityExtension struct { Overwrite bool } -func (i *IdentityExtension) ResolveFor(identityObject map[string]any, authJSON string) interface{} { +func (i *IdentityExtension) ResolveFor(identityObject map[string]any, authJSON string) (interface{}, error) { if value, exists := identityObject[i.Name]; exists && !i.Overwrite { - return value + return value, nil } return i.Value.ResolveFor(authJSON) } diff --git a/pkg/evaluators/identity_extension_test.go b/pkg/evaluators/identity_extension_test.go index edeb3e73..922c1765 100644 --- a/pkg/evaluators/identity_extension_test.go +++ b/pkg/evaluators/identity_extension_test.go @@ -84,7 +84,9 @@ func TestResolveIdentityExtension(t *testing.T) { } for _, tc := range testCases { - actual, _ := json.StringifyJSON(tc.input.ResolveFor(obj, authJSON)) + resolved, err := tc.input.ResolveFor(obj, authJSON) + assert.NilError(t, err) + actual, _ := json.StringifyJSON(resolved) assert.Equal(t, actual, tc.expected, fmt.Sprintf("%s failed: got '%s', want '%s'", tc.name, string(actual), string(tc.expected))) } } diff --git a/pkg/evaluators/metadata.go b/pkg/evaluators/metadata.go index e32eb01e..f04d5695 100644 --- a/pkg/evaluators/metadata.go +++ b/pkg/evaluators/metadata.go @@ -53,7 +53,7 @@ func (config *MetadataConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte var cacheKey interface{} if cache != nil { - cacheKey = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) + cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) if cachedObj, err := cache.Get(cacheKey); err != nil { logger.V(1).Error(err, "failed to retrieve data from the cache") } else if cachedObj != nil { diff --git a/pkg/evaluators/metadata/generic_http.go b/pkg/evaluators/metadata/generic_http.go index f9a8417f..80adec26 100644 --- a/pkg/evaluators/metadata/generic_http.go +++ b/pkg/evaluators/metadata/generic_http.go @@ -127,7 +127,11 @@ func (h *GenericHttp) buildRequest(ctx gocontext.Context, endpoint, authJSON str } for _, header := range h.Headers { - req.Header.Set(header.Name, fmt.Sprintf("%s", header.Value.ResolveFor(authJSON))) + headerValue, err := header.Value.ResolveFor(authJSON) + if err != nil { + return nil, err + } + req.Header.Set(header.Name, fmt.Sprintf("%s", headerValue)) } req.Header.Set("Content-Type", contentType) @@ -152,16 +156,24 @@ func (h *GenericHttp) buildRequest(ctx gocontext.Context, endpoint, authJSON str func (h *GenericHttp) buildRequestBody(authData string) (io.Reader, error) { if h.Body != nil { - if body, err := json.StringifyJSON(h.Body.ResolveFor(authData)); err != nil { - return nil, fmt.Errorf("failed to encode http request") + if resolved, err := h.Body.ResolveFor(authData); err != nil { + return nil, err } else { - return bytes.NewBufferString(body), nil + if body, err := json.StringifyJSON(resolved); err != nil { + return nil, fmt.Errorf("failed to encode http request") + } else { + return bytes.NewBufferString(body), nil + } } } data := make(map[string]interface{}) for _, param := range h.Parameters { - data[param.Name] = param.Value.ResolveFor(authData) + if resolved, err := param.Value.ResolveFor(authData); err != nil { + return nil, err + } else { + data[param.Name] = resolved + } } switch h.ContentType { diff --git a/pkg/evaluators/response.go b/pkg/evaluators/response.go index 6ee4b156..f116ad7e 100644 --- a/pkg/evaluators/response.go +++ b/pkg/evaluators/response.go @@ -82,7 +82,7 @@ func (config *ResponseConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte var cacheKey interface{} if cache != nil { - cacheKey = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) + cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) if cachedObj, err := cache.Get(cacheKey); err != nil { logger.V(1).Error(err, "failed to retrieve data from the cache") } else if cachedObj != nil { diff --git a/pkg/evaluators/response/dynamic_json.go b/pkg/evaluators/response/dynamic_json.go index 7ab9d2ae..8dc04e00 100644 --- a/pkg/evaluators/response/dynamic_json.go +++ b/pkg/evaluators/response/dynamic_json.go @@ -24,7 +24,11 @@ func (j *DynamicJSON) Call(pipeline auth.AuthPipeline, ctx context.Context) (int for _, property := range j.Properties { value := property.Value - obj[property.Name] = value.ResolveFor(authJSON) + if resolved, err := value.ResolveFor(authJSON); err != nil { + return nil, err + } else { + obj[property.Name] = resolved + } } return obj, nil diff --git a/pkg/evaluators/response/plain.go b/pkg/evaluators/response/plain.go index c84f5ded..6e1e61d6 100644 --- a/pkg/evaluators/response/plain.go +++ b/pkg/evaluators/response/plain.go @@ -13,5 +13,5 @@ type Plain struct { func (p *Plain) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { authJSON := pipeline.GetAuthorizationJSON() - return p.ResolveFor(authJSON), nil + return p.ResolveFor(authJSON) } diff --git a/pkg/evaluators/response/wristband.go b/pkg/evaluators/response/wristband.go index db811703..1851d960 100644 --- a/pkg/evaluators/response/wristband.go +++ b/pkg/evaluators/response/wristband.go @@ -120,7 +120,11 @@ func (w *Wristband) Call(pipeline auth.AuthPipeline, ctx context.Context) (inter for _, claim := range w.CustomClaims { value := claim.Value - claims[claim.Name] = value.ResolveFor(authJSON) + if resolved, err := value.ResolveFor(authJSON); err != nil { + return nil, err + } else { + claims[claim.Name] = resolved + } } } diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 9d7be3f6..1663e83a 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -45,6 +45,29 @@ func (p *Predicate) Matches(json string) (bool, error) { return result.Value().(bool), nil } +type Expression struct { + program cel.Program + source string +} + +func (e *Expression) ResolveFor(json string) (interface{}, error) { + input, err := AuthJsonToCel(json) + if err != nil { + return nil, err + } + + result, _, err := e.program.Eval(input) + if err != nil { + return nil, err + } + + if jsonVal, err := ValueToJSON(result); err != nil { + return nil, err + } else { + return jsonVal, nil + } +} + func Compile(expression string, predicate bool, opts ...cel.EnvOption) (cel.Program, error) { envOpts := append([]cel.EnvOption{cel.Declarations( decls.NewConst(RootAuthBinding, decls.NewObjectType("google.protobuf.Struct"), nil), diff --git a/pkg/expressions/types.go b/pkg/expressions/types.go new file mode 100644 index 00000000..d0406fa9 --- /dev/null +++ b/pkg/expressions/types.go @@ -0,0 +1,5 @@ +package expressions + +type Value interface { + ResolveFor(jsonData string) (interface{}, error) +} diff --git a/pkg/json/json.go b/pkg/json/json.go index b7eddcd4..4fe110d6 100644 --- a/pkg/json/json.go +++ b/pkg/json/json.go @@ -38,7 +38,11 @@ type JSONValue struct { // simple pattern or as a template that mixes static value with variable placeholders that resolve to patterns. // In case of a template that mixes no variable placeholder, but it contains nothing but a static string value, users // should use `JSONValue.Static` instead of `JSONValue.Pattern`. -func (v *JSONValue) ResolveFor(jsonData string) interface{} { +func (v *JSONValue) ResolveFor(jsonData string) (interface{}, error) { + return v.resolveForSafe(jsonData), nil +} + +func (v *JSONValue) resolveForSafe(jsonData string) interface{} { if v.Pattern != "" { // If all curly braces in the pattern are for passing arguments to modifiers, then it's likely NOT a template. // To be a template, the pattern must contain at least one curly brace delimiting a variable placeholder. diff --git a/pkg/json/json_test.go b/pkg/json/json_test.go index 0205e048..b5510115 100644 --- a/pkg/json/json_test.go +++ b/pkg/json/json_test.go @@ -33,17 +33,17 @@ func TestJSONValueResolveFor(t *testing.T) { var resolvedValueAsJSON []byte value = JSONValue{Static: "foo"} - assert.Equal(t, value.ResolveFor(jsonData), "foo") - assert.Equal(t, value.ResolveFor(""), "foo") + assert.Equal(t, value.resolveForSafe(jsonData), "foo") + assert.Equal(t, value.resolveForSafe(""), "foo") value = JSONValue{Pattern: "auth.identity.username"} - assert.Equal(t, value.ResolveFor(jsonData), "john") + assert.Equal(t, value.resolveForSafe(jsonData), "john") value = JSONValue{Pattern: "auth.identity.email_verified"} - assert.Equal(t, value.ResolveFor(jsonData), true) + assert.Equal(t, value.resolveForSafe(jsonData), true) value = JSONValue{Pattern: "auth.identity.address"} - resolvedValueAsJSON, _ = json.Marshal(value.ResolveFor(jsonData)) + resolvedValueAsJSON, _ = json.Marshal(value.resolveForSafe(jsonData)) type address struct { Line1 string `json:"line_1"` PostalCode int `json:"postal_code"` @@ -54,22 +54,22 @@ func TestJSONValueResolveFor(t *testing.T) { assert.Equal(t, resolvedAddress.PostalCode, 987654) value = JSONValue{Pattern: "auth.identity.roles"} - resolvedValueAsJSON, _ = json.Marshal(value.ResolveFor(jsonData)) + resolvedValueAsJSON, _ = json.Marshal(value.resolveForSafe(jsonData)) var resolvedRoles []string _ = json.Unmarshal(resolvedValueAsJSON, &resolvedRoles) assert.DeepEqual(t, resolvedRoles, []string{"user", "admin"}) // pattern mixing static and variable placeholders ("template") value = JSONValue{Pattern: "Hello, {auth.identity.username}!"} - assert.Equal(t, value.ResolveFor(jsonData), "Hello, john!") + assert.Equal(t, value.resolveForSafe(jsonData), "Hello, john!") // template with inner patterns passing arguments to modifier value = JSONValue{Pattern: `Email domain: {auth.identity.email.@extract:{"sep":"@","pos":1}}`} - assert.Equal(t, value.ResolveFor(jsonData), "Email domain: test") + assert.Equal(t, value.resolveForSafe(jsonData), "Email domain: test") // simple pattern passing arguments to modifier (not a template) value = JSONValue{Pattern: `auth.identity.email.@extract:{"sep":"@","pos":1}`} - assert.Equal(t, value.ResolveFor(jsonData), "test") + assert.Equal(t, value.resolveForSafe(jsonData), "test") } func TestIsTemplate(t *testing.T) { diff --git a/pkg/service/auth_pipeline.go b/pkg/service/auth_pipeline.go index 7e2478ab..22e112ab 100644 --- a/pkg/service/auth_pipeline.go +++ b/pkg/service/auth_pipeline.go @@ -587,17 +587,20 @@ func (pipeline *AuthPipeline) customizeDenyWith(authResult auth.AuthResult, deny authJSON := pipeline.GetAuthorizationJSON() if denyWith.Message != nil { - authResult.Message, _ = json.StringifyJSON(denyWith.Message.ResolveFor(authJSON)) + resolved, _ := denyWith.Message.ResolveFor(authJSON) + authResult.Message, _ = json.StringifyJSON(resolved) } if denyWith.Body != nil { - authResult.Body, _ = json.StringifyJSON(denyWith.Body.ResolveFor(authJSON)) + resolved, _ := denyWith.Body.ResolveFor(authJSON) + authResult.Body, _ = json.StringifyJSON(resolved) } if len(denyWith.Headers) > 0 { headers := make([]map[string]string, 0) for _, header := range denyWith.Headers { - value, _ := json.StringifyJSON(header.Value.ResolveFor(authJSON)) + resolved, _ := header.Value.ResolveFor(authJSON) + value, _ := json.StringifyJSON(resolved) headers = append(headers, map[string]string{header.Name: value}) } authResult.Headers = headers From d6dc8840166099e56e4d449a286389378b0ab1e3 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 09:29:20 -0400 Subject: [PATCH 07/43] Abstracted expressions.Value away somewhat Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 47 ++++++++++--------- pkg/evaluators/authorization/authzed.go | 14 +++--- pkg/evaluators/authorization/authzed_test.go | 20 ++++---- .../authorization/kubernetes_authz.go | 20 ++++---- .../authorization/kubernetes_authz_test.go | 15 +++--- pkg/evaluators/cache.go | 6 +-- pkg/evaluators/config.go | 5 +- pkg/evaluators/identity_extension.go | 7 ++- pkg/evaluators/identity_extension_test.go | 24 +++++----- pkg/evaluators/identity_test.go | 4 +- pkg/evaluators/metadata/generic_http.go | 3 +- pkg/evaluators/metadata/generic_http_test.go | 6 +-- pkg/evaluators/metadata_test.go | 2 +- pkg/evaluators/response/dynamic_json_test.go | 4 +- pkg/evaluators/response/plain.go | 4 +- pkg/evaluators/response/plain_test.go | 9 +++- pkg/evaluators/response/wristband_test.go | 4 +- pkg/expressions/cel/expressions.go | 11 +++++ pkg/json/json.go | 3 +- pkg/service/auth_pipeline_test.go | 4 +- pkg/service/auth_test.go | 2 +- 21 files changed, 119 insertions(+), 95 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 4a65ac3d..68b23b5a 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" "sort" "sync" @@ -182,13 +183,13 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf for identityCfgName, identity := range authConfigIdentityConfigs { extendedProperties := make([]evaluators.IdentityExtension, len(identity.Defaults)+len(identity.Overrides)) for propertyName, property := range identity.Defaults { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ Static: property.Value, Pattern: property.Selector, }, false)) } for propertyName, property := range identity.Overrides { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ Static: property.Value, Pattern: property.Selector, }, true)) @@ -212,7 +213,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf ttl = api.EvaluatorDefaultCacheTTL } translatedIdentity.Cache = evaluators.NewEvaluatorCache( - *getJsonFromStaticDynamic(&identity.Cache.Key), + getJsonFromStaticDynamic(&identity.Cache.Key), ttl, ) } @@ -310,7 +311,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf ttl = api.EvaluatorDefaultCacheTTL } translatedMetadata.Cache = evaluators.NewEvaluatorCache( - *getJsonFromStaticDynamic(&metadata.Cache.Key), + getJsonFromStaticDynamic(&metadata.Cache.Key), ttl, ) } @@ -383,7 +384,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf ttl = api.EvaluatorDefaultCacheTTL } translatedAuthorization.Cache = evaluators.NewEvaluatorCache( - *getJsonFromStaticDynamic(&authorization.Cache.Key), + getJsonFromStaticDynamic(&authorization.Cache.Key), ttl, ) } @@ -444,17 +445,17 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf resourceAttributes := authorization.KubernetesSubjectAccessReview.ResourceAttributes if resourceAttributes != nil { authorinoResourceAttributes = &authorization_evaluators.KubernetesAuthzResourceAttributes{ - Namespace: json.JSONValue{Static: resourceAttributes.Namespace.Value, Pattern: resourceAttributes.Namespace.Selector}, - Group: json.JSONValue{Static: resourceAttributes.Group.Value, Pattern: resourceAttributes.Group.Selector}, - Resource: json.JSONValue{Static: resourceAttributes.Resource.Value, Pattern: resourceAttributes.Resource.Selector}, - Name: json.JSONValue{Static: resourceAttributes.Name.Value, Pattern: resourceAttributes.Name.Selector}, - SubResource: json.JSONValue{Static: resourceAttributes.SubResource.Value, Pattern: resourceAttributes.SubResource.Selector}, - Verb: json.JSONValue{Static: resourceAttributes.Verb.Value, Pattern: resourceAttributes.Verb.Selector}, + Namespace: &json.JSONValue{Static: resourceAttributes.Namespace.Value, Pattern: resourceAttributes.Namespace.Selector}, + Group: &json.JSONValue{Static: resourceAttributes.Group.Value, Pattern: resourceAttributes.Group.Selector}, + Resource: &json.JSONValue{Static: resourceAttributes.Resource.Value, Pattern: resourceAttributes.Resource.Selector}, + Name: &json.JSONValue{Static: resourceAttributes.Name.Value, Pattern: resourceAttributes.Name.Selector}, + SubResource: &json.JSONValue{Static: resourceAttributes.SubResource.Value, Pattern: resourceAttributes.SubResource.Selector}, + Verb: &json.JSONValue{Static: resourceAttributes.Verb.Value, Pattern: resourceAttributes.Verb.Selector}, } } var err error - translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoResourceAttributes) + translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(&authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoResourceAttributes) if err != nil { return nil, err } @@ -475,7 +476,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf Endpoint: authzed.Endpoint, Insecure: authzed.Insecure, SharedSecret: sharedSecret, - Permission: *getJsonFromStaticDynamic(&authzed.Permission), + Permission: getJsonFromStaticDynamic(&authzed.Permission), } translatedAuthzed.Subject, translatedAuthzed.SubjectKind = spiceDBObjectToJsonValues(authzed.Subject) translatedAuthzed.Resource, translatedAuthzed.ResourceKind = spiceDBObjectToJsonValues(authzed.Resource) @@ -627,7 +628,7 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe for claimName, claim := range wristband.CustomClaims { customClaims = append(customClaims, json.JSONProperty{ Name: claimName, - Value: json.JSONValue{ + Value: &json.JSONValue{ Static: claim.Value, Pattern: claim.Selector, }, @@ -652,7 +653,7 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe for propertyName, property := range successResponse.Json.Properties { jsonProperties = append(jsonProperties, json.JSONProperty{ Name: propertyName, - Value: json.JSONValue{ + Value: &json.JSONValue{ Static: property.Value, Pattern: property.Selector, }, @@ -664,7 +665,7 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe // plain case api.PlainAuthResponse: translatedResponse.Plain = &response_evaluators.Plain{ - JSONValue: json.JSONValue{ + Value: &json.JSONValue{ Static: successResponse.Plain.Value, Pattern: successResponse.Plain.Selector, }, @@ -683,7 +684,7 @@ func injectCache(cache *api.EvaluatorCaching, translatedResponse *evaluators.Res ttl = api.EvaluatorDefaultCacheTTL } translatedResponse.Cache = evaluators.NewEvaluatorCache( - *getJsonFromStaticDynamic(&cache.Key), + getJsonFromStaticDynamic(&cache.Key), ttl, ) } @@ -838,7 +839,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht for name, param := range http.Parameters { params = append(params, json.JSONProperty{ Name: name, - Value: json.JSONValue{ + Value: &json.JSONValue{ Static: param.Value, Pattern: param.Selector, }, @@ -849,7 +850,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht for name, header := range http.Headers { headers = append(headers, json.JSONProperty{ Name: name, - Value: json.JSONValue{ + Value: &json.JSONValue{ Static: header.Value, Pattern: header.Selector, }, @@ -981,7 +982,7 @@ func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) *evaluators.De headers := make([]json.JSONProperty, 0, len(denyWithSpec.Headers)) for name, header := range denyWithSpec.Headers { - headers = append(headers, json.JSONProperty{Name: name, Value: json.JSONValue{Static: header.Value, Pattern: header.Selector}}) + headers = append(headers, json.JSONProperty{Name: name, Value: &json.JSONValue{Static: header.Value, Pattern: header.Selector}}) } return &evaluators.DenyWithValues{ @@ -1003,13 +1004,13 @@ func getJsonFromStaticDynamic(value *api.ValueOrSelector) *json.JSONValue { } } -func spiceDBObjectToJsonValues(obj *api.SpiceDBObject) (name json.JSONValue, kind json.JSONValue) { +func spiceDBObjectToJsonValues(obj *api.SpiceDBObject) (name expressions.Value, kind expressions.Value) { if obj == nil { return } - name = *getJsonFromStaticDynamic(&obj.Name) - kind = *getJsonFromStaticDynamic(&obj.Kind) + name = getJsonFromStaticDynamic(&obj.Name) + kind = getJsonFromStaticDynamic(&obj.Kind) return name, kind } diff --git a/pkg/evaluators/authorization/authzed.go b/pkg/evaluators/authorization/authzed.go index 1759b17b..5bc00ee5 100644 --- a/pkg/evaluators/authorization/authzed.go +++ b/pkg/evaluators/authorization/authzed.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/kuadrant/authorino/pkg/auth" - "github.com/kuadrant/authorino/pkg/json" + "github.com/kuadrant/authorino/pkg/expressions" "google.golang.org/grpc" insecuregrpc "google.golang.org/grpc/credentials/insecure" @@ -19,11 +19,11 @@ type Authzed struct { Insecure bool SharedSecret string - Subject json.JSONValue - SubjectKind json.JSONValue - Resource json.JSONValue - ResourceKind json.JSONValue - Permission json.JSONValue + Subject expressions.Value + SubjectKind expressions.Value + Resource expressions.Value + ResourceKind expressions.Value + Permission expressions.Value } type permissionResponse struct { @@ -86,7 +86,7 @@ func (a *Authzed) Call(pipeline auth.AuthPipeline, ctx gocontext.Context) (inter return obj, nil } -func authzedObjectFor(name, kind json.JSONValue, authJSON string) (*authzedpb.ObjectReference, error) { +func authzedObjectFor(name, kind expressions.Value, authJSON string) (*authzedpb.ObjectReference, error) { objectId, err := name.ResolveFor(authJSON) if err != nil { return nil, err diff --git a/pkg/evaluators/authorization/authzed_test.go b/pkg/evaluators/authorization/authzed_test.go index dd133654..ad29f482 100644 --- a/pkg/evaluators/authorization/authzed_test.go +++ b/pkg/evaluators/authorization/authzed_test.go @@ -52,11 +52,11 @@ func TestAuthzedCallAuthorized(t *testing.T) { Endpoint: testAuthzedServerEndpoint, Insecure: true, SharedSecret: "secret", - Subject: json.JSONValue{Static: "1"}, - SubjectKind: json.JSONValue{Static: "user"}, - Resource: json.JSONValue{Static: "123"}, - ResourceKind: json.JSONValue{Static: "post"}, - Permission: json.JSONValue{Static: "read"}, + Subject: &json.JSONValue{Static: "1"}, + SubjectKind: &json.JSONValue{Static: "user"}, + Resource: &json.JSONValue{Static: "123"}, + ResourceKind: &json.JSONValue{Static: "post"}, + Permission: &json.JSONValue{Static: "read"}, } obj, err := authzed.Call(pipelineMock, ctx) @@ -91,11 +91,11 @@ func TestAuthzedCallForbidden(t *testing.T) { Endpoint: testAuthzedServerEndpoint, Insecure: true, SharedSecret: "secret", - Subject: json.JSONValue{Static: "1"}, - SubjectKind: json.JSONValue{Static: "user"}, - Resource: json.JSONValue{Static: "123"}, - ResourceKind: json.JSONValue{Static: "post"}, - Permission: json.JSONValue{Static: "read"}, + Subject: &json.JSONValue{Static: "1"}, + SubjectKind: &json.JSONValue{Static: "user"}, + Resource: &json.JSONValue{Static: "123"}, + ResourceKind: &json.JSONValue{Static: "post"}, + Permission: &json.JSONValue{Static: "read"}, } obj, err := authzed.Call(pipelineMock, ctx) diff --git a/pkg/evaluators/authorization/kubernetes_authz.go b/pkg/evaluators/authorization/kubernetes_authz.go index a382248b..6f9aa704 100644 --- a/pkg/evaluators/authorization/kubernetes_authz.go +++ b/pkg/evaluators/authorization/kubernetes_authz.go @@ -3,11 +3,11 @@ package authorization import ( gocontext "context" "fmt" + "github.com/kuadrant/authorino/pkg/expressions" "strings" "github.com/kuadrant/authorino/pkg/auth" "github.com/kuadrant/authorino/pkg/context" - "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/log" kubeAuthz "k8s.io/api/authorization/v1" @@ -21,7 +21,7 @@ type kubernetesSubjectAccessReviewer interface { SubjectAccessReviews() kubeAuthzClient.SubjectAccessReviewInterface } -func NewKubernetesAuthz(user json.JSONValue, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes) (*KubernetesAuthz, error) { +func NewKubernetesAuthz(user expressions.Value, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes) (*KubernetesAuthz, error) { config, err := rest.InClusterConfig() if err != nil { return nil, err @@ -41,16 +41,16 @@ func NewKubernetesAuthz(user json.JSONValue, groups []string, resourceAttributes } type KubernetesAuthzResourceAttributes struct { - Namespace json.JSONValue - Group json.JSONValue - Resource json.JSONValue - Name json.JSONValue - SubResource json.JSONValue - Verb json.JSONValue + Namespace expressions.Value + Group expressions.Value + Resource expressions.Value + Name expressions.Value + SubResource expressions.Value + Verb expressions.Value } type KubernetesAuthz struct { - User json.JSONValue + User expressions.Value Groups []string ResourceAttributes *KubernetesAuthzResourceAttributes @@ -63,7 +63,7 @@ func (k *KubernetesAuthz) Call(pipeline auth.AuthPipeline, ctx gocontext.Context } authJSON := pipeline.GetAuthorizationJSON() - jsonValueToStr := func(value json.JSONValue) (string, error) { + jsonValueToStr := func(value expressions.Value) (string, error) { resolved, err := value.ResolveFor(authJSON) if err != nil { return "", err diff --git a/pkg/evaluators/authorization/kubernetes_authz_test.go b/pkg/evaluators/authorization/kubernetes_authz_test.go index f1c29e33..4fa1b576 100644 --- a/pkg/evaluators/authorization/kubernetes_authz_test.go +++ b/pkg/evaluators/authorization/kubernetes_authz_test.go @@ -2,6 +2,7 @@ package authorization import ( "context" + "github.com/kuadrant/authorino/pkg/expressions" "testing" mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" @@ -58,7 +59,7 @@ func (client *k8sAuthorizationClientMock) GetRequest() kubeAuthz.SubjectAccessRe return client.request } -func newKubernetesAuthz(user json.JSONValue, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes, subjectAccessReviewResponseStatus kubeAuthz.SubjectAccessReviewStatus) *KubernetesAuthz { +func newKubernetesAuthz(user expressions.Value, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes, subjectAccessReviewResponseStatus kubeAuthz.SubjectAccessReviewStatus) *KubernetesAuthz { return &KubernetesAuthz{ User: user, Groups: groups, @@ -80,7 +81,7 @@ func TestKubernetesAuthzNonResource_Allowed(t *testing.T) { pipelineMock.EXPECT().GetHttp().Return(request) kubernetesAuth := newKubernetesAuthz( - json.JSONValue{Pattern: "auth.identity.username"}, + &json.JSONValue{Pattern: "auth.identity.username"}, []string{}, nil, kubeAuthz.SubjectAccessReviewStatus{Allowed: true, Reason: ""}, @@ -108,7 +109,7 @@ func TestKubernetesAuthzNonResource_Denied(t *testing.T) { pipelineMock.EXPECT().GetHttp().Return(request) kubernetesAuth := newKubernetesAuthz( - json.JSONValue{Pattern: "auth.identity.username"}, + &json.JSONValue{Pattern: "auth.identity.username"}, []string{}, nil, kubeAuthz.SubjectAccessReviewStatus{Allowed: false, Reason: "some-reason"}, @@ -133,9 +134,9 @@ func TestKubernetesAuthzResource_Allowed(t *testing.T) { pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john"}}}`) kubernetesAuth := newKubernetesAuthz( - json.JSONValue{Pattern: "auth.identity.username"}, + &json.JSONValue{Pattern: "auth.identity.username"}, []string{}, - &KubernetesAuthzResourceAttributes{Namespace: json.JSONValue{Static: "default"}}, + &KubernetesAuthzResourceAttributes{Namespace: &json.JSONValue{Static: "default"}}, kubeAuthz.SubjectAccessReviewStatus{Allowed: true, Reason: ""}, ) authorized, err := kubernetesAuth.Call(pipelineMock, context.TODO()) @@ -157,9 +158,9 @@ func TestKubernetesAuthzResource_Denied(t *testing.T) { pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john"}}}`) kubernetesAuth := newKubernetesAuthz( - json.JSONValue{Pattern: "auth.identity.username"}, + &json.JSONValue{Pattern: "auth.identity.username"}, []string{}, - &KubernetesAuthzResourceAttributes{Namespace: json.JSONValue{Static: "default"}}, + &KubernetesAuthzResourceAttributes{Namespace: &json.JSONValue{Static: "default"}}, kubeAuthz.SubjectAccessReviewStatus{Allowed: false, Reason: "some-reason"}, ) authorized, err := kubernetesAuth.Call(pipelineMock, context.TODO()) diff --git a/pkg/evaluators/cache.go b/pkg/evaluators/cache.go index b5bd3a75..1bf03f52 100644 --- a/pkg/evaluators/cache.go +++ b/pkg/evaluators/cache.go @@ -4,7 +4,7 @@ import ( gojson "encoding/json" "time" - "github.com/kuadrant/authorino/pkg/json" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/coocood/freecache" gocache "github.com/eko/gocache/cache" @@ -20,7 +20,7 @@ type EvaluatorCache interface { Shutdown() error } -func NewEvaluatorCache(keyTemplate json.JSONValue, ttl int) EvaluatorCache { +func NewEvaluatorCache(keyTemplate expressions.Value, ttl int) EvaluatorCache { duration := time.Duration(ttl) * time.Second cacheClient := freecache.NewCache(EvaluatorCacheSize * 1024 * 1024) cacheStore := cache_store.NewFreecache(cacheClient, &cache_store.Options{Expiration: duration}) @@ -33,7 +33,7 @@ func NewEvaluatorCache(keyTemplate json.JSONValue, ttl int) EvaluatorCache { // evaluatorCache caches JSON values (objects, arrays, strings, etc) type evaluatorCache struct { - keyTemplate json.JSONValue + keyTemplate expressions.Value store *gocache.Cache } diff --git a/pkg/evaluators/config.go b/pkg/evaluators/config.go index 3d0f58c4..a2b8c13b 100644 --- a/pkg/evaluators/config.go +++ b/pkg/evaluators/config.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/kuadrant/authorino/pkg/auth" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/jsonexp" @@ -74,7 +75,7 @@ type DenyWith struct { type DenyWithValues struct { Code int32 - Message *json.JSONValue + Message expressions.Value Headers []json.JSONProperty - Body *json.JSONValue + Body expressions.Value } diff --git a/pkg/evaluators/identity_extension.go b/pkg/evaluators/identity_extension.go index f57e1ed9..63dc87a9 100644 --- a/pkg/evaluators/identity_extension.go +++ b/pkg/evaluators/identity_extension.go @@ -1,8 +1,11 @@ package evaluators -import "github.com/kuadrant/authorino/pkg/json" +import ( + "github.com/kuadrant/authorino/pkg/expressions" + "github.com/kuadrant/authorino/pkg/json" +) -func NewIdentityExtension(name string, value json.JSONValue, overwrite bool) IdentityExtension { +func NewIdentityExtension(name string, value expressions.Value, overwrite bool) IdentityExtension { return IdentityExtension{ JSONProperty: json.JSONProperty{ Name: name, diff --git a/pkg/evaluators/identity_extension_test.go b/pkg/evaluators/identity_extension_test.go index 922c1765..34ee0f2b 100644 --- a/pkg/evaluators/identity_extension_test.go +++ b/pkg/evaluators/identity_extension_test.go @@ -23,62 +23,62 @@ func TestResolveIdentityExtension(t *testing.T) { }{ { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, false), expected: "beth", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, false), expected: "foo", }, { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "beth", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "1234567890", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "beth", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, } diff --git a/pkg/evaluators/identity_test.go b/pkg/evaluators/identity_test.go index 8e16f6f2..39620adf 100644 --- a/pkg/evaluators/identity_test.go +++ b/pkg/evaluators/identity_test.go @@ -41,8 +41,8 @@ func TestIdentityConfig_ResolveExtendedProperties(t *testing.T) { Name: "test", KubernetesAuth: &identity.KubernetesAuth{}, ExtendedProperties: []IdentityExtension{ - NewIdentityExtension("prop1", json.JSONValue{Static: "value1"}, true), - NewIdentityExtension("prop2", json.JSONValue{Pattern: "auth.identity.sub"}, true), + NewIdentityExtension("prop1", &json.JSONValue{Static: "value1"}, true), + NewIdentityExtension("prop2", &json.JSONValue{Pattern: "auth.identity.sub"}, true), }, } diff --git a/pkg/evaluators/metadata/generic_http.go b/pkg/evaluators/metadata/generic_http.go index 80adec26..5e4be206 100644 --- a/pkg/evaluators/metadata/generic_http.go +++ b/pkg/evaluators/metadata/generic_http.go @@ -5,6 +5,7 @@ import ( gocontext "context" gojson "encoding/json" "fmt" + "github.com/kuadrant/authorino/pkg/expressions" "io" "net/http" "net/url" @@ -23,7 +24,7 @@ import ( type GenericHttp struct { Endpoint string Method string - Body *json.JSONValue + Body expressions.Value Parameters []json.JSONProperty Headers []json.JSONProperty ContentType string diff --git a/pkg/evaluators/metadata/generic_http_test.go b/pkg/evaluators/metadata/generic_http_test.go index b00ecbda..3f56a117 100644 --- a/pkg/evaluators/metadata/generic_http_test.go +++ b/pkg/evaluators/metadata/generic_http_test.go @@ -80,7 +80,7 @@ func TestGenericHttpCallWithPOST(t *testing.T) { metadata := &GenericHttp{ Endpoint: endpoint, Method: "POST", - Parameters: []json.JSONProperty{{Name: "user", Value: json.JSONValue{Pattern: "auth.identity.user"}}}, + Parameters: []json.JSONProperty{{Name: "user", Value: &json.JSONValue{Pattern: "auth.identity.user"}}}, ContentType: "application/x-www-form-urlencoded", SharedSecret: "secret", AuthCredentials: sharedCredsMock, @@ -226,8 +226,8 @@ func TestGenericHttpCallWithCustomHeaders(t *testing.T) { Endpoint: endpoint, Method: "GET", Headers: []json.JSONProperty{ - {Name: "X-Requested-By", Value: json.JSONValue{Static: "authorino"}}, - {Name: "Content-Type", Value: json.JSONValue{Static: "to-be-overwritten"}}, + {Name: "X-Requested-By", Value: &json.JSONValue{Static: "authorino"}}, + {Name: "Content-Type", Value: &json.JSONValue{Static: "to-be-overwritten"}}, }, AuthCredentials: sharedCredsMock, } diff --git a/pkg/evaluators/metadata_test.go b/pkg/evaluators/metadata_test.go index c2cf70e0..66caeba3 100644 --- a/pkg/evaluators/metadata_test.go +++ b/pkg/evaluators/metadata_test.go @@ -59,7 +59,7 @@ func TestMetadataCaching(t *testing.T) { assert.NilError(t, err) // With caching of metadata - cache := NewEvaluatorCache(json.JSONValue{Static: "x"}, 2) // 2 seconds ttl + cache := NewEvaluatorCache(&json.JSONValue{Static: "x"}, 2) // 2 seconds ttl metadataConfig.Cache = cache defer metadataConfig.Clean(context.TODO()) diff --git a/pkg/evaluators/response/dynamic_json_test.go b/pkg/evaluators/response/dynamic_json_test.go index 47d48b93..1b064e6c 100644 --- a/pkg/evaluators/response/dynamic_json_test.go +++ b/pkg/evaluators/response/dynamic_json_test.go @@ -17,8 +17,8 @@ func TestDynamicJSONCall(t *testing.T) { defer ctrl.Finish() jsonProperties := []json.JSONProperty{ - {Name: "prop1", Value: json.JSONValue{Static: "value1"}}, - {Name: "prop2", Value: json.JSONValue{Pattern: "auth.identity.username"}}, + {Name: "prop1", Value: &json.JSONValue{Static: "value1"}}, + {Name: "prop2", Value: &json.JSONValue{Pattern: "auth.identity.username"}}, } jsonResponseEvaluator := NewDynamicJSONResponse(jsonProperties) diff --git a/pkg/evaluators/response/plain.go b/pkg/evaluators/response/plain.go index 6e1e61d6..6b3611b6 100644 --- a/pkg/evaluators/response/plain.go +++ b/pkg/evaluators/response/plain.go @@ -4,11 +4,11 @@ import ( "context" "github.com/kuadrant/authorino/pkg/auth" - "github.com/kuadrant/authorino/pkg/json" + "github.com/kuadrant/authorino/pkg/expressions" ) type Plain struct { - json.JSONValue + expressions.Value } func (p *Plain) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { diff --git a/pkg/evaluators/response/plain_test.go b/pkg/evaluators/response/plain_test.go index 76ad5ee1..83ca2436 100644 --- a/pkg/evaluators/response/plain_test.go +++ b/pkg/evaluators/response/plain_test.go @@ -3,6 +3,7 @@ package response import ( "context" "fmt" + "github.com/kuadrant/authorino/pkg/json" "testing" mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" @@ -16,7 +17,9 @@ func TestPlainCallWithStaticValue(t *testing.T) { defer ctrl.Finish() ev := Plain{} - ev.Static = "value1" + ev.Value = &json.JSONValue{ + Static: "value1", + } pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john"}}}`) @@ -32,7 +35,9 @@ func TestPlainCallWithPattern(t *testing.T) { defer ctrl.Finish() ev := Plain{} - ev.Pattern = "auth.identity.username" + ev.Value = &json.JSONValue{ + Pattern: "auth.identity.username", + } pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john"}}}`) diff --git a/pkg/evaluators/response/wristband_test.go b/pkg/evaluators/response/wristband_test.go index 354ee91e..aebd8b7c 100644 --- a/pkg/evaluators/response/wristband_test.go +++ b/pkg/evaluators/response/wristband_test.go @@ -160,11 +160,11 @@ func TestWristbandCall(t *testing.T) { claims := []json.JSONProperty{ { Name: "sta", - Value: json.JSONValue{Static: "foo"}, + Value: &json.JSONValue{Static: "foo"}, }, { Name: "dyn", - Value: json.JSONValue{Pattern: "auth.identity"}, + Value: &json.JSONValue{Pattern: "auth.identity"}, }, } signingKey, _ := NewSigningKey("my-signing-key", "ES256", []byte(ellipticCurveSigningKey)) diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 1663e83a..c1247758 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -50,6 +50,17 @@ type Expression struct { source string } +func NewExpression(source string) (*Expression, error) { + program, err := Compile(source, false) + if err != nil { + return nil, err + } + return &Expression{ + program: program, + source: source, + }, nil +} + func (e *Expression) ResolveFor(json string) (interface{}, error) { input, err := AuthJsonToCel(json) if err != nil { diff --git a/pkg/json/json.go b/pkg/json/json.go index 4fe110d6..1011b6dd 100644 --- a/pkg/json/json.go +++ b/pkg/json/json.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/kuadrant/authorino/pkg/expressions" "io" "mime" "net/http" @@ -23,7 +24,7 @@ var ( // a pattern for a value fetched dynamically from the authorization JSON type JSONProperty struct { Name string - Value JSONValue + Value expressions.Value } type JSONValue struct { diff --git a/pkg/service/auth_pipeline_test.go b/pkg/service/auth_pipeline_test.go index 446ddad7..d3feadf9 100644 --- a/pkg/service/auth_pipeline_test.go +++ b/pkg/service/auth_pipeline_test.go @@ -340,8 +340,8 @@ func TestEvaluateWithCustomDenyOptions(t *testing.T) { Unauthenticated: &evaluators.DenyWithValues{ Code: 302, Headers: []json.JSONProperty{ - {Name: "X-Static-Header", Value: json.JSONValue{Static: "some-value"}}, - {Name: "Location", Value: json.JSONValue{Pattern: "https://my-app.io/login?redirect_to=https://{context.request.http.host}{context.request.http.path}"}}, + {Name: "X-Static-Header", Value: &json.JSONValue{Static: "some-value"}}, + {Name: "Location", Value: &json.JSONValue{Pattern: "https://my-app.io/login?redirect_to=https://{context.request.http.host}{context.request.http.path}"}}, }, Body: &json.JSONValue{ Static: authConfigStaticResponse, diff --git a/pkg/service/auth_test.go b/pkg/service/auth_test.go index 26366e04..d23ce0f7 100644 --- a/pkg/service/auth_test.go +++ b/pkg/service/auth_test.go @@ -256,7 +256,7 @@ func TestAuthServiceRawHTTPAuthorization_WithHeaders(t *testing.T) { Wrapper: "httpHeader", WrapperKey: "x-auth-data", DynamicJSON: &response.DynamicJSON{ - Properties: []json.JSONProperty{{Name: "headers", Value: json.JSONValue{Pattern: "context.request.http.headers"}}}, + Properties: []json.JSONProperty{{Name: "headers", Value: &json.JSONValue{Pattern: "context.request.http.headers"}}}, }, }} indexMock := mock_index.NewMockIndex(mockController) From cd1b51069d8f6d1450580c88ba372026b987a018 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 09:32:25 -0400 Subject: [PATCH 08/43] Fix imports Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 2 +- pkg/evaluators/authorization/kubernetes_authz_test.go | 2 +- pkg/evaluators/metadata/generic_http.go | 2 +- pkg/evaluators/response/plain_test.go | 2 +- pkg/json/json.go | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 68b23b5a..928a3935 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -19,7 +19,6 @@ package controllers import ( "context" "fmt" - "github.com/kuadrant/authorino/pkg/expressions" "sort" "sync" @@ -30,6 +29,7 @@ import ( identity_evaluators "github.com/kuadrant/authorino/pkg/evaluators/identity" metadata_evaluators "github.com/kuadrant/authorino/pkg/evaluators/metadata" response_evaluators "github.com/kuadrant/authorino/pkg/evaluators/response" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/expressions/cel" "github.com/kuadrant/authorino/pkg/index" "github.com/kuadrant/authorino/pkg/json" diff --git a/pkg/evaluators/authorization/kubernetes_authz_test.go b/pkg/evaluators/authorization/kubernetes_authz_test.go index 4fa1b576..77a54826 100644 --- a/pkg/evaluators/authorization/kubernetes_authz_test.go +++ b/pkg/evaluators/authorization/kubernetes_authz_test.go @@ -2,10 +2,10 @@ package authorization import ( "context" - "github.com/kuadrant/authorino/pkg/expressions" "testing" mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/json" envoy_auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" diff --git a/pkg/evaluators/metadata/generic_http.go b/pkg/evaluators/metadata/generic_http.go index 5e4be206..3bf7f2af 100644 --- a/pkg/evaluators/metadata/generic_http.go +++ b/pkg/evaluators/metadata/generic_http.go @@ -5,7 +5,6 @@ import ( gocontext "context" gojson "encoding/json" "fmt" - "github.com/kuadrant/authorino/pkg/expressions" "io" "net/http" "net/url" @@ -13,6 +12,7 @@ import ( "github.com/kuadrant/authorino/pkg/auth" "github.com/kuadrant/authorino/pkg/context" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/log" "github.com/kuadrant/authorino/pkg/oauth2" diff --git a/pkg/evaluators/response/plain_test.go b/pkg/evaluators/response/plain_test.go index 83ca2436..993e63da 100644 --- a/pkg/evaluators/response/plain_test.go +++ b/pkg/evaluators/response/plain_test.go @@ -3,10 +3,10 @@ package response import ( "context" "fmt" - "github.com/kuadrant/authorino/pkg/json" "testing" mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" + "github.com/kuadrant/authorino/pkg/json" "gotest.tools/assert" "github.com/golang/mock/gomock" diff --git a/pkg/json/json.go b/pkg/json/json.go index 1011b6dd..738b102a 100644 --- a/pkg/json/json.go +++ b/pkg/json/json.go @@ -4,7 +4,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/kuadrant/authorino/pkg/expressions" "io" "mime" "net/http" @@ -12,6 +11,8 @@ import ( "strings" "unicode" + "github.com/kuadrant/authorino/pkg/expressions" + "github.com/tidwall/gjson" ) From 5431fed9c6140ae3e6fb097102c7adc37ac207fe Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 10:22:06 -0400 Subject: [PATCH 09/43] Wired cel.Expression in the AuthConfig Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 105 +++++++++++++++++++------- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 928a3935..703e376c 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -212,8 +212,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf if ttl == 0 { ttl = api.EvaluatorDefaultCacheTTL } + key, err := getJsonFromStaticDynamic(&identity.Cache.Key) + if err != nil { + return nil, err + } translatedIdentity.Cache = evaluators.NewEvaluatorCache( - getJsonFromStaticDynamic(&identity.Cache.Key), + key, ttl, ) } @@ -310,8 +314,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf if ttl == 0 { ttl = api.EvaluatorDefaultCacheTTL } + key, err := getJsonFromStaticDynamic(&metadata.Cache.Key) + if err != nil { + return nil, err + } translatedMetadata.Cache = evaluators.NewEvaluatorCache( - getJsonFromStaticDynamic(&metadata.Cache.Key), + key, ttl, ) } @@ -383,8 +391,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf if ttl == 0 { ttl = api.EvaluatorDefaultCacheTTL } + key, err := getJsonFromStaticDynamic(&authorization.Cache.Key) + if err != nil { + return nil, err + } translatedAuthorization.Cache = evaluators.NewEvaluatorCache( - getJsonFromStaticDynamic(&authorization.Cache.Key), + key, ttl, ) } @@ -472,15 +484,24 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf sharedSecret = string(secret.Data[secretRef.Key]) } + permission, err := getJsonFromStaticDynamic(&authzed.Permission) + if err != nil { + return nil, err + } translatedAuthzed := &authorization_evaluators.Authzed{ Endpoint: authzed.Endpoint, Insecure: authzed.Insecure, SharedSecret: sharedSecret, - Permission: getJsonFromStaticDynamic(&authzed.Permission), + Permission: permission, + } + translatedAuthzed.Subject, translatedAuthzed.SubjectKind, err = spiceDBObjectToJsonValues(authzed.Subject) + if err != nil { + return nil, err + } + translatedAuthzed.Resource, translatedAuthzed.ResourceKind, err = spiceDBObjectToJsonValues(authzed.Resource) + if err != nil { + return nil, err } - translatedAuthzed.Subject, translatedAuthzed.SubjectKind = spiceDBObjectToJsonValues(authzed.Subject) - translatedAuthzed.Resource, translatedAuthzed.ResourceKind = spiceDBObjectToJsonValues(authzed.Resource) - translatedAuthorization.Authzed = translatedAuthzed case api.UnknownAuthorizationMethod: @@ -586,10 +607,18 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf // denyWith if responseConfig := authConfig.Spec.Response; responseConfig != nil { if denyWith := responseConfig.Unauthenticated; denyWith != nil { - translatedAuthConfig.Unauthenticated = buildAuthorinoDenyWithValues(denyWith) + value, err := buildAuthorinoDenyWithValues(denyWith) + if err != nil { + return nil, err + } + translatedAuthConfig.Unauthenticated = value } if denyWith := responseConfig.Unauthorized; denyWith != nil { - translatedAuthConfig.Unauthorized = buildAuthorinoDenyWithValues(denyWith) + value, err := buildAuthorinoDenyWithValues(denyWith) + if err != nil { + return nil, err + } + translatedAuthConfig.Unauthorized = value } } @@ -677,17 +706,22 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe return nil } -func injectCache(cache *api.EvaluatorCaching, translatedResponse *evaluators.ResponseConfig) { +func injectCache(cache *api.EvaluatorCaching, translatedResponse *evaluators.ResponseConfig) error { if cache != nil { ttl := cache.TTL if ttl == 0 { ttl = api.EvaluatorDefaultCacheTTL } - translatedResponse.Cache = evaluators.NewEvaluatorCache( - getJsonFromStaticDynamic(&cache.Key), - ttl, - ) + if key, err := getJsonFromStaticDynamic(&cache.Key); err != nil { + return err + } else { + translatedResponse.Cache = evaluators.NewEvaluatorCache( + key, + ttl, + ) + } } + return nil } func (r *AuthConfigReconciler) addToIndex(ctx context.Context, resourceNamespace, resourceId string, authConfig *evaluators.AuthConfig, hosts []string) (linkedHosts, looseHosts []string, err error) { @@ -975,9 +1009,9 @@ func buildJSONExpressionPattern(expression api.PatternExpression) jsonexp.Expres } } -func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) *evaluators.DenyWithValues { +func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) (*evaluators.DenyWithValues, error) { if denyWithSpec == nil { - return nil + return nil, nil } headers := make([]json.JSONProperty, 0, len(denyWithSpec.Headers)) @@ -985,32 +1019,49 @@ func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) *evaluators.De headers = append(headers, json.JSONProperty{Name: name, Value: &json.JSONValue{Static: header.Value, Pattern: header.Selector}}) } + message, err := getJsonFromStaticDynamic(denyWithSpec.Message) + if err != nil { + return nil, err + } + body, err := getJsonFromStaticDynamic(denyWithSpec.Body) + if err != nil { + return nil, err + } return &evaluators.DenyWithValues{ Code: int32(denyWithSpec.Code), - Message: getJsonFromStaticDynamic(denyWithSpec.Message), + Message: message, Headers: headers, - Body: getJsonFromStaticDynamic(denyWithSpec.Body), - } + Body: body, + }, nil } -func getJsonFromStaticDynamic(value *api.ValueOrSelector) *json.JSONValue { +func getJsonFromStaticDynamic(value *api.ValueOrSelector) (expressions.Value, error) { if value == nil { - return nil + return nil, nil + } + + if value.Expression.Expression != "" { + return cel.NewExpression(value.Expression.Expression) } return &json.JSONValue{ Static: value.Value, Pattern: value.Selector, - } + }, nil } -func spiceDBObjectToJsonValues(obj *api.SpiceDBObject) (name expressions.Value, kind expressions.Value) { +func spiceDBObjectToJsonValues(obj *api.SpiceDBObject) (name expressions.Value, kind expressions.Value, err error) { if obj == nil { return } - name = getJsonFromStaticDynamic(&obj.Name) - kind = getJsonFromStaticDynamic(&obj.Kind) - - return name, kind + nameResolved, err := getJsonFromStaticDynamic(&obj.Name) + if err != nil { + return nil, nil, err + } + kindResolved, err := getJsonFromStaticDynamic(&obj.Kind) + if err != nil { + return nil, nil, err + } + return nameResolved, kindResolved, nil } From 69d0613506d2647bf2db983d98f7ecefcb87b416 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 10:23:46 -0400 Subject: [PATCH 10/43] Fix imports Signed-off-by: Alex Snaps --- pkg/evaluators/authorization/kubernetes_authz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/evaluators/authorization/kubernetes_authz.go b/pkg/evaluators/authorization/kubernetes_authz.go index 6f9aa704..613a7778 100644 --- a/pkg/evaluators/authorization/kubernetes_authz.go +++ b/pkg/evaluators/authorization/kubernetes_authz.go @@ -3,11 +3,11 @@ package authorization import ( gocontext "context" "fmt" - "github.com/kuadrant/authorino/pkg/expressions" "strings" "github.com/kuadrant/authorino/pkg/auth" "github.com/kuadrant/authorino/pkg/context" + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/log" kubeAuthz "k8s.io/api/authorization/v1" From d946f1bfdab4c44889cf801c53dcd54ee9312610 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 10:29:04 -0400 Subject: [PATCH 11/43] Deal with nil expression.Value as "" Signed-off-by: Alex Snaps --- pkg/evaluators/authorization/kubernetes_authz.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/evaluators/authorization/kubernetes_authz.go b/pkg/evaluators/authorization/kubernetes_authz.go index 613a7778..947327ea 100644 --- a/pkg/evaluators/authorization/kubernetes_authz.go +++ b/pkg/evaluators/authorization/kubernetes_authz.go @@ -64,6 +64,9 @@ func (k *KubernetesAuthz) Call(pipeline auth.AuthPipeline, ctx gocontext.Context authJSON := pipeline.GetAuthorizationJSON() jsonValueToStr := func(value expressions.Value) (string, error) { + if value == nil { + return "", nil + } resolved, err := value.ResolveFor(authJSON) if err != nil { return "", err From ca31308d115715a56b18e349f3fd55e49a985fec Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 11:22:10 -0400 Subject: [PATCH 12/43] v1beta3 API This reverts commit c3fa020de2eb923b07b038b027aa02fafcaaf3f3. Signed-off-by: Alex Snaps --- api/v1beta2/auth_config_types.go | 12 +- api/v1beta2/zz_generated.deepcopy.go | 33 - api/v1beta3/auth_config_conversion.go | 4 + api/v1beta3/auth_config_types.go | 12 +- api/v1beta3/auth_config_webhook.go | 11 + api/v1beta3/zz_generated.deepcopy.go | 33 + .../authorino.kuadrant.io_authconfigs.yaml | 2302 +++++++++++++++ install/manifests.yaml | 2494 ++++++++++++++++- 8 files changed, 4760 insertions(+), 141 deletions(-) create mode 100644 api/v1beta3/auth_config_conversion.go create mode 100644 api/v1beta3/auth_config_webhook.go diff --git a/api/v1beta2/auth_config_types.go b/api/v1beta2/auth_config_types.go index fd4f9e25..c689e51b 100644 --- a/api/v1beta2/auth_config_types.go +++ b/api/v1beta2/auth_config_types.go @@ -163,21 +163,13 @@ type PatternExpression struct { Value string `json:"value,omitempty"` } -type CelExpression struct { - Expression string `json:"expression,omitempty"` -} - -type CelPredicate struct { - Predicate string `json:"predicate,omitempty"` -} - // +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches type PatternExpressionOperator string type PatternExpressionOrRef struct { PatternExpression `json:",omitempty"` PatternRef `json:",omitempty"` - CelPredicate `json:",omitempty"` + // A list of pattern expressions to be evaluated as a logical AND. All []UnstructuredPatternExpressionOrRef `json:"all,omitempty"` // A list of pattern expressions to be evaluated as a logical OR. @@ -206,8 +198,6 @@ type ValueOrSelector struct { // Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector,omitempty"` - - Expression CelExpression `json:",omitempty"` } type CommonEvaluatorSpec struct { diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index d578097a..29171143 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -481,36 +481,6 @@ 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 @@ -1028,7 +998,6 @@ 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)) @@ -1115,7 +1084,6 @@ 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. @@ -1319,7 +1287,6 @@ 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/api/v1beta3/auth_config_conversion.go b/api/v1beta3/auth_config_conversion.go new file mode 100644 index 00000000..418e7a5c --- /dev/null +++ b/api/v1beta3/auth_config_conversion.go @@ -0,0 +1,4 @@ +package v1beta3 + +// Hub marks this version as a conversion hub. +func (a *AuthConfig) Hub() {} diff --git a/api/v1beta3/auth_config_types.go b/api/v1beta3/auth_config_types.go index 0c3be7ed..28b33661 100644 --- a/api/v1beta3/auth_config_types.go +++ b/api/v1beta3/auth_config_types.go @@ -164,13 +164,21 @@ type PatternExpression struct { Value string `json:"value,omitempty"` } +type CelExpression struct { + Expression string `json:"expression,omitempty"` +} + +type CelPredicate struct { + Predicate string `json:"predicate,omitempty"` +} + // +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches type PatternExpressionOperator string type PatternExpressionOrRef struct { PatternExpression `json:",omitempty"` PatternRef `json:",omitempty"` - + CelPredicate `json:",omitempty"` // A list of pattern expressions to be evaluated as a logical AND. All []UnstructuredPatternExpressionOrRef `json:"all,omitempty"` // A list of pattern expressions to be evaluated as a logical OR. @@ -199,6 +207,8 @@ type ValueOrSelector struct { // Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector,omitempty"` + + Expression CelExpression `json:",omitempty"` } type CommonEvaluatorSpec struct { diff --git a/api/v1beta3/auth_config_webhook.go b/api/v1beta3/auth_config_webhook.go new file mode 100644 index 00000000..4946921f --- /dev/null +++ b/api/v1beta3/auth_config_webhook.go @@ -0,0 +1,11 @@ +package v1beta3 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (a *AuthConfig) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(a). + Complete() +} diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index 5b0414ce..94ddff2b 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -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/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index e96ee99d..4dd72189 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -50,6 +50,2308 @@ spec: type: boolean name: v1beta2 schema: + openAPIV3Schema: + description: AuthConfig is the schema for Authorino's AuthConfig API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specifies the desired state of the AuthConfig resource, i.e. + the authencation/authorization scheme to be applied to protect the matching + service hosts. + properties: + authentication: + additionalProperties: + properties: + anonymous: + description: Anonymous access. + type: object + apiKey: + description: Authentication based on API keys stored in Kubernetes + secrets. + properties: + allNamespaces: + default: false + description: |- + Whether Authorino should look for API key secrets in all namespaces or only in the same namespace as the AuthConfig. + Enabling this option in namespaced Authorino instances has no effect. + type: boolean + selector: + description: Label selector used by Authorino to match secrets + from the cluster storing valid credentials to authenticate + to this service + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - selector + type: object + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external data + in the cache before pulled again from the source. + type: integer + required: + - key + type: object + credentials: + description: |- + Defines where credentials are required to be passed in the request for authentication based on this config. + If omitted, it defaults to credentials passed in the HTTP Authorization header and the "Bearer" prefix prepended to the secret credential value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + defaults: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Set default property values (claims) for the resolved identity object, that are set before appending the object to + the authorization JSON. If the property is already present in the resolved identity object, the default value is ignored. + It requires the resolved identity object to always be a JSON object. + Do not use this option with identity objects of other JSON types (array, string, etc). + type: object + jwt: + description: Authentication based on JWT tokens. + properties: + issuerUrl: + description: |- + URL of the issuer of the JWT. + If `jwksUrl` is omitted, Authorino will append the path to the OpenID Connect Well-Known Discovery endpoint + (i.e. "/.well-known/openid-configuration") to this URL, to discover the OIDC configuration where to obtain + the "jkws_uri" claim from. + The value must coincide with the value of the "iss" (issuer) claim of the discovered OpenID Connect configuration. + type: string + ttl: + description: |- + Decides how long to wait before refreshing the JWKS (in seconds). + If omitted, Authorino will never refresh the JWKS. + type: integer + type: object + kubernetesTokenReview: + description: Authentication by Kubernetes token review. + properties: + audiences: + description: |- + The list of audiences (scopes) that must be claimed in a Kubernetes authentication token supplied in the request, and reviewed by Authorino. + If omitted, Authorino will review tokens expecting the host name of the requested protected service amongst the audiences. + items: + type: string + type: array + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + oauth2Introspection: + description: Authentication by OAuth2 token introspection. + properties: + credentialsRef: + description: Reference to a Kubernetes secret in the same + namespace, that stores client credentials to the OAuth2 + server. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: The full URL of the token introspection endpoint. + type: string + tokenTypeHint: + description: |- + The token type hint for the token introspection. + If omitted, it defaults to "access_token". + type: string + required: + - credentialsRef + - endpoint + type: object + overrides: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Overrides the resolved identity object by setting the additional properties (claims) specified in this config, + before appending the object to the authorization JSON. + It requires the resolved identity object to always be a JSON object. + Do not use this option with identity objects of other JSON types (array, string, etc). + type: object + plain: + description: |- + Identity object extracted from the context. + Use this method when authentication is performed beforehand by a proxy and the resulting object passed to Authorino as JSON in the auth request. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + required: + - selector + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to be evaluated + as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to be evaluated + as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + x509: + description: |- + Authentication based on client X.509 certificates. + The certificates presented by the clients must be signed by a trusted CA whose certificates are stored in Kubernetes secrets. + properties: + allNamespaces: + default: false + description: |- + Whether Authorino should look for TLS secrets in all namespaces or only in the same namespace as the AuthConfig. + Enabling this option in namespaced Authorino instances has no effect. + type: boolean + selector: + description: |- + Label selector used by Authorino to match secrets from the cluster storing trusted CA certificates to validate + clients trying to authenticate to this service + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - selector + type: object + type: object + description: |- + Authentication configs. + At least one config MUST evaluate to a valid identity object for the auth request to be successful. + type: object + authorization: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external data + in the cache before pulled again from the source. + type: integer + required: + - key + type: object + kubernetesSubjectAccessReview: + description: Authorization by Kubernetes SubjectAccessReview + properties: + groups: + description: Groups the user must be a member of or, if + `user` is omitted, the groups to check for authorization + in the Kubernetes RBAC. + items: + type: string + type: array + resourceAttributes: + description: |- + Use resourceAttributes to check permissions on Kubernetes resources. + If omitted, it performs a non-resource SubjectAccessReview, with verb and path inferred from the request. + properties: + group: + description: |- + API group of the resource. + Use '*' for all API groups. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + description: |- + Resource name + Omit it to check for authorization on all resources of the specified kind. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + namespace: + description: Namespace where the user must have permissions + on the resource. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + resource: + description: |- + Resource kind + Use '*' for all resource kinds. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + subresource: + description: Subresource kind + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + verb: + description: |- + Verb to check for authorization on the resource. + Use '*' for all verbs. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + user: + description: |- + User to check for authorization in the Kubernetes RBAC. + Omit it to check for group authorization only. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + opa: + description: Open Policy Agent (OPA) Rego policy. + properties: + allValues: + default: false + description: |- + Returns the value of all Rego rules in the virtual document. Values can be read in subsequent evaluators/phases of the Auth Pipeline. + Otherwise, only the default `allow` rule will be exposed. + Returning all Rego rules can affect performance of OPA policies during reconciliation (policy precompile) and at runtime. + type: boolean + externalPolicy: + description: |- + Settings for fetching the OPA policy from an external registry. + Use it alternatively to 'rego'. + For the configurations of the HTTP request, the following options are not implemented: 'method', 'body', 'bodyParameters', + 'contentType', 'headers', 'oauth2'. Use it only with: 'url', 'sharedSecret', 'credentials'. + properties: + body: + description: |- + Raw body of the HTTP request. + 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: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; 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). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service by + OAuth2 Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret key + that stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for the requests + to the token URL. + type: object + scopes: + description: Optional scopes for the client credentials + grant, if supported by he OAuth2 server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 resource + server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + ttl: + description: Duration (in seconds) of the external data + in the cache before pulled again from the source. + type: integer + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + rego: + description: |- + Authorization policy as a Rego language document. + The Rego document must include the "allow" condition, set by Authorino to "false" by default (i.e. requests are unauthorized unless changed). + The Rego document must NOT include the "package" declaration in line 1. + type: string + type: object + patternMatching: + description: Pattern-matching authorization rules. + properties: + patterns: + items: + properties: + all: + description: A list of pattern expressions to be evaluated + as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to be evaluated + as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - patterns + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + spicedb: + description: Authorization decision delegated to external Authzed/SpiceDB + server. + properties: + endpoint: + description: Hostname and port number to the GRPC interface + of the SpiceDB server (e.g. spicedb:50051). + type: string + insecure: + description: Insecure HTTP connection (i.e. disables TLS + verification) + type: boolean + permission: + description: The name of the permission (or relation) on + which to execute the check. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + resource: + description: The resource on which to check the permission + or relation. + properties: + kind: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + sharedSecretRef: + description: Reference to a Secret key whose value will + be used by Authorino to authenticate with the Authzed + service. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + subject: + description: The subject that will be checked for the permission + or relation. + properties: + kind: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + name: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + required: + - endpoint + type: object + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to be evaluated + as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to be evaluated + as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + type: object + description: |- + Authorization policies. + All policies MUST evaluate to "allowed = true" for the auth request be successful. + type: object + callbacks: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external data + in the cache before pulled again from the source. + type: integer + required: + - key + type: object + http: + description: Settings of the external HTTP request + properties: + body: + description: |- + Raw body of the HTTP request. + 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: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; 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). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service by OAuth2 + Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret key that + stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for the requests + to the token URL. + type: object + scopes: + description: Optional scopes for the client credentials + grant, if supported by he OAuth2 server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 resource + server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to be evaluated + as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to be evaluated + as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - http + type: object + description: |- + Callback functions. + Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + type: object + hosts: + description: |- + The list of public host names of the services protected by this authentication/authorization scheme. + Authorino uses the requested host to lookup for the corresponding authentication/authorization configs to enforce. + items: + type: string + type: array + metadata: + additionalProperties: + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external data + in the cache before pulled again from the source. + type: integer + required: + - key + type: object + http: + description: External source of auth metadata via HTTP request + properties: + body: + description: |- + Raw body of the HTTP request. + 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: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + bodyParameters: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Custom parameters to encode in the body of the HTTP request. + Superseded by 'body'; 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). + type: object + contentType: + default: application/x-www-form-urlencoded + description: |- + Content-Type of the request body. Shapes how 'bodyParameters' are encoded. + Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. + enum: + - application/x-www-form-urlencoded + - application/json + type: string + credentials: + description: |- + Defines where client credentials will be passed in the request to the service. + If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. + properties: + authorizationHeader: + properties: + prefix: + type: string + type: object + cookie: + properties: + name: + type: string + required: + - name + type: object + customHeader: + properties: + name: + type: string + required: + - name + type: object + queryString: + properties: + name: + type: string + required: + - name + type: object + type: object + headers: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Custom headers in the HTTP request. + type: object + method: + default: GET + description: |- + HTTP verb used in the request to the service. Accepted values: GET (default), POST. + When the request method is POST, the authorization JSON is passed in the body of the request. + enum: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + - OPTIONS + - CONNECT + - TRACE + type: string + oauth2: + description: Authentication with the HTTP service by OAuth2 + Client Credentials grant. + properties: + cache: + default: true + description: |- + Caches and reuses the token until expired. + Set it to false to force fetch the token at every authorization request regardless of expiration. + type: boolean + clientId: + description: OAuth2 Client ID. + type: string + clientSecretRef: + description: Reference to a Kuberentes Secret key that + stores that OAuth2 Client Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + extraParams: + additionalProperties: + type: string + description: Optional extra parameters for the requests + to the token URL. + type: object + scopes: + description: Optional scopes for the client credentials + grant, if supported by he OAuth2 server. + items: + type: string + type: array + tokenUrl: + description: Token endpoint URL of the OAuth2 resource + server. + type: string + required: + - clientId + - clientSecretRef + - tokenUrl + type: object + sharedSecretRef: + description: |- + Reference to a Secret key whose value will be passed by Authorino in the request. + The HTTP service can use the shared secret to authenticate the origin of the request. + Ignored if used together with oauth2. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the Authorino's + namespace to select from. + type: string + required: + - key + - name + type: object + url: + description: |- + Endpoint URL of the HTTP service. + The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported + by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. + E.g. https://ext-auth-server.io/metadata?p={request.path} + type: string + required: + - url + type: object + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + uma: + description: User-Managed Access (UMA) source of resource data. + properties: + credentialsRef: + description: Reference to a Kubernetes secret in the same + namespace, that stores client credentials to the resource + registration API of the UMA server. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: |- + The endpoint of the UMA server. + The value must coincide with the "issuer" claim of the UMA config discovered from the well-known uma configuration endpoint. + type: string + required: + - credentialsRef + - endpoint + type: object + userInfo: + description: OpendID Connect UserInfo linked to an OIDC authentication + config specified in this same AuthConfig. + properties: + identitySource: + description: The name of an OIDC-enabled JWT authentication + config whose OpenID Connect configuration discovered includes + the OIDC "userinfo_endpoint" claim. + type: string + required: + - identitySource + type: object + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to be evaluated + as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to be evaluated + as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + type: object + description: |- + Metadata sources. + Authorino fetches auth metadata as JSON from sources specified in this config. + type: object + patterns: + additionalProperties: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + description: Named sets of patterns that can be referred in `when` + conditions and in pattern-matching authorization policy rules. + type: object + response: + description: |- + Response items. + Authorino builds custom responses to the client of the auth request. + properties: + success: + description: |- + Response items to be included in the auth response when the request is authenticated and authorized. + For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. + properties: + dynamicMetadata: + additionalProperties: + description: Settings of the success custom response item. + properties: + cache: + description: |- + Caching options for the resolved object returned when applying this config. + Omit it to avoid caching objects for this config. + properties: + key: + description: |- + Key used to store the entry in the cache. + The resolved key must be unique within the scope of this particular config. + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + ttl: + default: 60 + description: Duration (in seconds) of the external + data in the cache before pulled again from the + source. + type: integer + required: + - key + type: object + json: + description: |- + JSON object + Specify it as the list of properties of the object, whose values can combine static values and values selected from the authorization JSON. + properties: + properties: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + required: + - properties + type: object + key: + description: |- + The key used to add the custom response item (name of the HTTP header or root property of the Dynamic Metadata object). + If omitted, it will be set to the name of the response config. + type: string + metrics: + default: false + description: Whether this config should generate individual + observability metrics + type: boolean + plain: + description: Plain text content + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + priority: + default: 0 + description: |- + Priority group of the config. + All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. + type: integer + when: + description: |- + Conditions for Authorino to enforce this config. + If omitted, the config will be enforced for all requests. + If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. + items: + properties: + all: + description: A list of pattern expressions to + be evaluated as a logical AND. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + any: + description: A list of pattern expressions to + be evaluated as a logical OR. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + patternRef: + description: Reference to a named set of pattern + expressions + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + wristband: + description: Authorino Festival Wristband token + properties: + customClaims: + additionalProperties: + properties: + 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}!"). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. + type: string + value: + description: Static value + x-kubernetes-preserve-unknown-fields: true + type: object + description: Any claims to be added to the wristband + token apart from the standard JWT claims (iss, + iat, exp) added by default. + type: object + issuer: + description: 'The endpoint to the Authorino service + that issues the wristband (format: ://:/, + where = /://:/, + where = /://:/, + where = /://:/, + where = / Date: Tue, 15 Oct 2024 12:05:55 -0400 Subject: [PATCH 13/43] HttpEndpointSpec.DynamicUrl Signed-off-by: Alex Snaps --- api/v1beta3/auth_config_types.go | 2 + api/v1beta3/zz_generated.deepcopy.go | 1 + controllers/auth_config_controller.go | 11 +++++ .../authorino.kuadrant.io_authconfigs.yaml | 15 +++++++ install/manifests.yaml | 15 +++++++ pkg/evaluators/metadata/generic_http.go | 13 +++++- pkg/evaluators/response/dynamic_cel.go | 2 +- pkg/expressions/cel/expressions.go | 43 +++++++++++++++---- 8 files changed, 91 insertions(+), 11 deletions(-) diff --git a/api/v1beta3/auth_config_types.go b/api/v1beta3/auth_config_types.go index 28b33661..5bcb3613 100644 --- a/api/v1beta3/auth_config_types.go +++ b/api/v1beta3/auth_config_types.go @@ -449,6 +449,8 @@ type HttpEndpointSpec struct { // E.g. https://ext-auth-server.io/metadata?p={request.path} Url string `json:"url"` + UrlExpression CelExpression `json:"urlExpression,omitempty"` + // HTTP verb used in the request to the service. Accepted values: GET (default), POST. // When the request method is POST, the authorization JSON is passed in the body of the request. // +optional diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index 94ddff2b..f03c1935 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -697,6 +697,7 @@ func (in *HeaderSuccessResponseSpec) DeepCopy() *HeaderSuccessResponseSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HttpEndpointSpec) DeepCopyInto(out *HttpEndpointSpec) { *out = *in + out.UrlExpression = in.UrlExpression if in.Method != nil { in, out := &in.Method, &out.Method *out = new(HttpMethod) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 703e376c..976e6412 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -896,8 +896,19 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht method = string(*m) } + var dynamicEndpoint *cel.Expression + if http.UrlExpression.Expression != "" { + endpoint, err := cel.NewStringExpression(http.UrlExpression.Expression) + if err != nil { + return nil, err + } else { + dynamicEndpoint = endpoint + } + } + ev := &metadata_evaluators.GenericHttp{ Endpoint: http.Url, + DynamicEndpoint: dynamicEndpoint, Method: method, Body: body, Parameters: params, diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 4dd72189..fbac5628 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -3121,6 +3121,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object @@ -3581,6 +3586,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object @@ -3882,6 +3892,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object diff --git a/install/manifests.yaml b/install/manifests.yaml index 61b75cbd..c6fb2fa8 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -5886,6 +5886,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object @@ -6346,6 +6351,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object @@ -6647,6 +6657,11 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + properties: + expression: + type: string + type: object required: - url type: object diff --git a/pkg/evaluators/metadata/generic_http.go b/pkg/evaluators/metadata/generic_http.go index 3bf7f2af..9e56ca81 100644 --- a/pkg/evaluators/metadata/generic_http.go +++ b/pkg/evaluators/metadata/generic_http.go @@ -13,6 +13,7 @@ import ( "github.com/kuadrant/authorino/pkg/auth" "github.com/kuadrant/authorino/pkg/context" "github.com/kuadrant/authorino/pkg/expressions" + "github.com/kuadrant/authorino/pkg/expressions/cel" "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/log" "github.com/kuadrant/authorino/pkg/oauth2" @@ -23,6 +24,7 @@ import ( type GenericHttp struct { Endpoint string + DynamicEndpoint *cel.Expression Method string Body expressions.Value Parameters []json.JSONProperty @@ -40,7 +42,16 @@ func (h *GenericHttp) Call(pipeline auth.AuthPipeline, ctx gocontext.Context) (i } authJSON := pipeline.GetAuthorizationJSON() - endpoint := json.ReplaceJSONPlaceholders(h.Endpoint, authJSON) + var endpoint string + if h.DynamicEndpoint != nil { + if val, err := h.DynamicEndpoint.EvaluateStringValue(authJSON); err != nil { + return nil, err + } else { + endpoint = val + } + } else { + endpoint = json.ReplaceJSONPlaceholders(h.Endpoint, authJSON) + } req, err := h.buildRequest(ctx, endpoint, authJSON) if err != nil { diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go index d538c5b7..dcd4e118 100644 --- a/pkg/evaluators/response/dynamic_cel.go +++ b/pkg/evaluators/response/dynamic_cel.go @@ -12,7 +12,7 @@ func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { cel_exp := DynamicCEL{} - if program, err := cel.Compile(expression, false); err != nil { + if program, err := cel.Compile(expression, nil); err != nil { return nil, err } else { cel_exp.program = program diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index c1247758..a92a75b4 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -23,7 +23,7 @@ type Predicate struct { } func NewPredicate(source string) (*Predicate, error) { - program, err := Compile(source, true) + program, err := Compile(source, cel.BoolType) if err != nil { return nil, err } @@ -51,7 +51,7 @@ type Expression struct { } func NewExpression(source string) (*Expression, error) { - program, err := Compile(source, false) + program, err := Compile(source, nil) if err != nil { return nil, err } @@ -61,13 +61,19 @@ func NewExpression(source string) (*Expression, error) { }, nil } -func (e *Expression) ResolveFor(json string) (interface{}, error) { - input, err := AuthJsonToCel(json) +func NewStringExpression(source string) (*Expression, error) { + program, err := Compile(source, cel.StringType) if err != nil { return nil, err } + return &Expression{ + program: program, + source: source, + }, nil +} - result, _, err := e.program.Eval(input) +func (e *Expression) ResolveFor(json string) (interface{}, error) { + result, _, err := e.Evaluate(json) if err != nil { return nil, err } @@ -79,7 +85,26 @@ func (e *Expression) ResolveFor(json string) (interface{}, error) { } } -func Compile(expression string, predicate bool, opts ...cel.EnvOption) (cel.Program, error) { +func (e *Expression) Evaluate(json string) (ref.Val, *cel.EvalDetails, error) { + input, err := AuthJsonToCel(json) + if err != nil { + return nil, nil, err + } + + return e.program.Eval(input) +} + +func (e *Expression) EvaluateStringValue(json string) (string, error) { + if result, _, err := e.Evaluate(json); err != nil { + return "", err + } else if !reflect.DeepEqual(result.Type(), cel.StringType) { + return "", err + } else { + return result.Value().(string), nil + } +} + +func Compile(expression string, expectedType *cel.Type, opts ...cel.EnvOption) (cel.Program, error) { envOpts := append([]cel.EnvOption{cel.Declarations( decls.NewConst(RootAuthBinding, decls.NewObjectType("google.protobuf.Struct"), nil), decls.NewConst(RootContextBinding, decls.NewObjectType("google.protobuf.Struct"), nil), @@ -99,9 +124,9 @@ func Compile(expression string, predicate bool, opts ...cel.EnvOption) (cel.Prog 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) + if expectedType != nil { + if !reflect.DeepEqual(checked.OutputType(), expectedType) && !reflect.DeepEqual(checked.OutputType(), cel.DynType) { + return nil, fmt.Errorf("type error: got %v, wanted %v output type", checked.OutputType(), expectedType) } } From c1c24180b7f2a88f179a1afce2278ef4430bec5c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 12:56:42 -0400 Subject: [PATCH 14/43] Facilitate StringValue, mimics expressions.Value Signed-off-by: Alex Snaps --- api/v1beta3/auth_config_types.go | 4 +++- controllers/auth_config_controller.go | 2 +- pkg/evaluators/metadata/generic_http.go | 7 +++---- pkg/expressions/cel/expressions.go | 18 ++++++++++++++---- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/api/v1beta3/auth_config_types.go b/api/v1beta3/auth_config_types.go index 5bcb3613..db2bbb2d 100644 --- a/api/v1beta3/auth_config_types.go +++ b/api/v1beta3/auth_config_types.go @@ -412,6 +412,8 @@ type PlainIdentitySpec struct { // Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector"` + + Expression CelExpression `json:",omitempty"` } type AnonymousAccessSpec struct{} @@ -449,7 +451,7 @@ type HttpEndpointSpec struct { // E.g. https://ext-auth-server.io/metadata?p={request.path} Url string `json:"url"` - UrlExpression CelExpression `json:"urlExpression,omitempty"` + UrlExpression CelExpression `json:",omitempty"` // HTTP verb used in the request to the service. Accepted values: GET (default), POST. // When the request method is POST, the authorization JSON is passed in the body of the request. diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 976e6412..235125e4 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -896,7 +896,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht method = string(*m) } - var dynamicEndpoint *cel.Expression + var dynamicEndpoint expressions.Value if http.UrlExpression.Expression != "" { endpoint, err := cel.NewStringExpression(http.UrlExpression.Expression) if err != nil { diff --git a/pkg/evaluators/metadata/generic_http.go b/pkg/evaluators/metadata/generic_http.go index 9e56ca81..437f21eb 100644 --- a/pkg/evaluators/metadata/generic_http.go +++ b/pkg/evaluators/metadata/generic_http.go @@ -13,7 +13,6 @@ import ( "github.com/kuadrant/authorino/pkg/auth" "github.com/kuadrant/authorino/pkg/context" "github.com/kuadrant/authorino/pkg/expressions" - "github.com/kuadrant/authorino/pkg/expressions/cel" "github.com/kuadrant/authorino/pkg/json" "github.com/kuadrant/authorino/pkg/log" "github.com/kuadrant/authorino/pkg/oauth2" @@ -24,7 +23,7 @@ import ( type GenericHttp struct { Endpoint string - DynamicEndpoint *cel.Expression + DynamicEndpoint expressions.Value Method string Body expressions.Value Parameters []json.JSONProperty @@ -44,10 +43,10 @@ func (h *GenericHttp) Call(pipeline auth.AuthPipeline, ctx gocontext.Context) (i authJSON := pipeline.GetAuthorizationJSON() var endpoint string if h.DynamicEndpoint != nil { - if val, err := h.DynamicEndpoint.EvaluateStringValue(authJSON); err != nil { + if val, err := h.DynamicEndpoint.ResolveFor(authJSON); err != nil { return nil, err } else { - endpoint = val + endpoint = val.(string) } } else { endpoint = json.ReplaceJSONPlaceholders(h.Endpoint, authJSON) diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index a92a75b4..8cd7b602 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -50,6 +50,10 @@ type Expression struct { source string } +type StringExpression struct { + expression Expression +} + func NewExpression(source string) (*Expression, error) { program, err := Compile(source, nil) if err != nil { @@ -61,14 +65,16 @@ func NewExpression(source string) (*Expression, error) { }, nil } -func NewStringExpression(source string) (*Expression, error) { +func NewStringExpression(source string) (*StringExpression, error) { program, err := Compile(source, cel.StringType) if err != nil { return nil, err } - return &Expression{ - program: program, - source: source, + return &StringExpression{ + expression: Expression{ + program: program, + source: source, + }, }, nil } @@ -85,6 +91,10 @@ func (e *Expression) ResolveFor(json string) (interface{}, error) { } } +func (e *StringExpression) ResolveFor(json string) (interface{}, error) { + return e.expression.EvaluateStringValue(json) +} + func (e *Expression) Evaluate(json string) (ref.Val, *cel.EvalDetails, error) { input, err := AuthJsonToCel(json) if err != nil { From 90201cee3bdc997eda786cc16a2510078143ebf4 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 13:04:57 -0400 Subject: [PATCH 15/43] PlainIdentitySpec.Value Signed-off-by: Alex Snaps --- api/v1beta3/zz_generated.deepcopy.go | 1 + controllers/auth_config_controller.go | 10 +++++++- .../authorino.kuadrant.io_authconfigs.yaml | 23 +++++++------------ install/manifests.yaml | 23 +++++++------------ pkg/evaluators/identity/plain.go | 6 ++--- pkg/evaluators/identity/plain_test.go | 11 +++++---- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index f03c1935..1f89a632 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -1132,6 +1132,7 @@ func (in *PlainAuthResponseSpec) DeepCopy() *PlainAuthResponseSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PlainIdentitySpec) DeepCopyInto(out *PlainIdentitySpec) { *out = *in + out.Expression = in.Expression } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlainIdentitySpec. diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 235125e4..e871c961 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -282,7 +282,15 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf } case api.PlainIdentityAuthentication: - translatedIdentity.Plain = &identity_evaluators.Plain{Pattern: identity.Plain.Selector} + if identity.Plain.Expression.Expression != "" { + expression, err := cel.NewStringExpression(identity.Plain.Expression.Expression) + if err != nil { + return nil, err + } + translatedIdentity.Plain = &identity_evaluators.Plain{Value: expression, Pattern: identity.Plain.Expression.Expression} + } else { + translatedIdentity.Plain = &identity_evaluators.Plain{Value: &json.JSONValue{Pattern: identity.Plain.Selector}, Pattern: identity.Plain.Selector} + } case api.AnonymousAccessAuthentication: translatedIdentity.Noop = &identity_evaluators.Noop{AuthCredentials: authCred} diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index fbac5628..d21dd4eb 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2612,6 +2612,8 @@ spec: Identity object extracted from the context. Use this method when authentication is performed beforehand by a proxy and the resulting object passed to Authorino as JSON in the auth request. 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}!"). @@ -3009,6 +3011,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -3121,11 +3125,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object @@ -3478,6 +3477,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -3586,11 +3587,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object @@ -3784,6 +3780,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -3892,11 +3890,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object diff --git a/install/manifests.yaml b/install/manifests.yaml index c6fb2fa8..dcd5a9b4 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -5377,6 +5377,8 @@ spec: Identity object extracted from the context. Use this method when authentication is performed beforehand by a proxy and the resulting object passed to Authorino as JSON in the auth request. 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}!"). @@ -5774,6 +5776,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -5886,11 +5890,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object @@ -6243,6 +6242,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -6351,11 +6352,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object @@ -6549,6 +6545,8 @@ spec: - name type: object type: object + expression: + type: string headers: additionalProperties: properties: @@ -6657,11 +6655,6 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string - urlExpression: - properties: - expression: - type: string - type: object required: - url type: object diff --git a/pkg/evaluators/identity/plain.go b/pkg/evaluators/identity/plain.go index d996d774..c6bc5ee0 100644 --- a/pkg/evaluators/identity/plain.go +++ b/pkg/evaluators/identity/plain.go @@ -7,18 +7,18 @@ import ( "net/http" "github.com/kuadrant/authorino/pkg/auth" - "github.com/kuadrant/authorino/pkg/json" + "github.com/kuadrant/authorino/pkg/expressions" envoy_auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" ) type Plain struct { + Value expressions.Value Pattern string } func (p *Plain) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { - pattern := json.JSONValue{Pattern: p.Pattern} - if object, err := pattern.ResolveFor(pipeline.GetAuthorizationJSON()); object != nil { + if object, err := p.Value.ResolveFor(pipeline.GetAuthorizationJSON()); object != nil { return object, nil } else if err != nil { return nil, err diff --git a/pkg/evaluators/identity/plain_test.go b/pkg/evaluators/identity/plain_test.go index 18d8fe21..ed0ecc41 100644 --- a/pkg/evaluators/identity/plain_test.go +++ b/pkg/evaluators/identity/plain_test.go @@ -6,6 +6,7 @@ import ( "testing" mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" + j "github.com/kuadrant/authorino/pkg/json" "github.com/golang/mock/gomock" "gotest.tools/assert" @@ -18,7 +19,7 @@ func TestPlainCall(t *testing.T) { pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"body":"{\"username\":\"john\"}"}}}}`) - plain := &Plain{Pattern: "context.request.http.body.@fromstr"} + plain := &Plain{Value: &j.JSONValue{Pattern: "context.request.http.body.@fromstr"}, Pattern: "context.request.http.body.@fromstr"} id, err := plain.Call(pipelineMock, nil) assert.NilError(t, err) j, _ := json.Marshal(id) @@ -32,7 +33,7 @@ func TestPlainCallWithUresolvableObject(t *testing.T) { pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{}`) - plain := &Plain{Pattern: "context.request.http.body.@fromstr"} + plain := &Plain{Value: &j.JSONValue{Pattern: "context.request.http.body.@fromstr"}, Pattern: "context.request.http.body.@fromstr"} id, err := plain.Call(pipelineMock, nil) assert.ErrorContains(t, err, "could not retrieve identity object") assert.Check(t, id == nil) @@ -45,19 +46,19 @@ func TestPlainCallWithInvalidPatttern(t *testing.T) { pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"body":"{\"username\":\"john\"}"}}}}`) - plain := &Plain{Pattern: "not a valid json path"} + plain := &Plain{Value: &j.JSONValue{Pattern: "not a valid json path"}, Pattern: "not a valid json path"} id, err := plain.Call(pipelineMock, nil) assert.ErrorContains(t, err, "could not retrieve identity object") assert.Check(t, id == nil) } func TestPlainGetCredentialsKeySelector(t *testing.T) { - plain := &Plain{Pattern: "context.request.http.body.@fromstr"} + plain := &Plain{Value: &j.JSONValue{Pattern: "context.request.http.body.@fromstr"}, Pattern: "context.request.http.body.@fromstr"} assert.Equal(t, plain.GetCredentialsKeySelector(), "context.request.http.body.@fromstr") } func TestPlainGetCredentialsIn(t *testing.T) { - plain := &Plain{Pattern: "context.request.http.body.@fromstr"} + plain := &Plain{Value: &j.JSONValue{Pattern: "context.request.http.body.@fromstr"}, Pattern: "context.request.http.body.@fromstr"} assert.Equal(t, plain.GetCredentialsIn(), "context.request.http.body.@fromstr") } From c9580a4a741e707b78307d334437888fba2694c9 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 15 Oct 2024 13:05:44 -0400 Subject: [PATCH 16/43] Deleted response.DynamicCEL Signed-off-by: Alex Snaps --- pkg/evaluators/response/dynamic_cel.go | 43 --------------------- pkg/evaluators/response/dynamic_cel_test.go | 35 ----------------- 2 files changed, 78 deletions(-) delete mode 100644 pkg/evaluators/response/dynamic_cel.go delete mode 100644 pkg/evaluators/response/dynamic_cel_test.go diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go deleted file mode 100644 index dcd4e118..00000000 --- a/pkg/evaluators/response/dynamic_cel.go +++ /dev/null @@ -1,43 +0,0 @@ -package response - -import ( - "context" - - interpreter "github.com/google/cel-go/cel" - "github.com/kuadrant/authorino/pkg/auth" - "github.com/kuadrant/authorino/pkg/expressions/cel" -) - -func NewDynamicCelResponse(expression string) (*DynamicCEL, error) { - - cel_exp := DynamicCEL{} - - if program, err := cel.Compile(expression, nil); err != nil { - return nil, err - } else { - cel_exp.program = program - } - - return &cel_exp, nil -} - -type DynamicCEL struct { - program interpreter.Program -} - -func (c *DynamicCEL) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { - input, err := cel.AuthJsonToCel(pipeline.GetAuthorizationJSON()) - if err != nil { - return nil, err - } - result, _, err := c.program.Eval(input) - if err != nil { - return nil, err - } - - if jsonVal, err := cel.ValueToJSON(result); err != nil { - return nil, err - } else { - return jsonVal, nil - } -} diff --git a/pkg/evaluators/response/dynamic_cel_test.go b/pkg/evaluators/response/dynamic_cel_test.go deleted file mode 100644 index 1089d444..00000000 --- a/pkg/evaluators/response/dynamic_cel_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package response - -import ( - "context" - "encoding/json" - "testing" - - mock_auth "github.com/kuadrant/authorino/pkg/auth/mocks" - "gotest.tools/assert" - - "github.com/golang/mock/gomock" -) - -func TestDynamicCELCall(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - celResponseEvaluator, _ := NewDynamicCelResponse(`{"prop1": "value1", "prop2": auth.identity.username}`) - - pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) - pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"auth":{"identity":{"username":"john","evil": false}}}`) - - response, err := celResponseEvaluator.Call(pipelineMock, context.TODO()) - assert.NilError(t, err) - - // We need to parse this response: https://protobuf.dev/reference/go/faq/#unstable-json - result := struct { - Prop1 string `json:"prop1"` - Prop2 string `json:"prop2"` - }{} - assert.NilError(t, json.Unmarshal([]byte(response.(string)), &result)) - - assert.Equal(t, result.Prop1, "value1") - assert.Equal(t, result.Prop2, "john") -} From bd7b2fc530f9badd5e472f01b83c7f070e39dbd2 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 16 Oct 2024 10:02:04 +0200 Subject: [PATCH 17/43] remove unrelated rbac from the manifests Signed-off-by: Guilherme Cassolato --- install/manifests.yaml | 146 ----------------------------------------- install/rbac/role.yaml | 146 ----------------------------------------- 2 files changed, 292 deletions(-) diff --git a/install/manifests.yaml b/install/manifests.yaml index dcd5a9b4..981599df 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -7556,80 +7556,6 @@ 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: @@ -7650,12 +7576,6 @@ rules: - get - patch - update -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - apiGroups: - coordination.k8s.io resources: @@ -7673,69 +7593,3 @@ 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 2328df39..69520e9e 100644 --- a/install/rbac/role.yaml +++ b/install/rbac/role.yaml @@ -4,80 +4,6 @@ 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: @@ -98,12 +24,6 @@ rules: - get - patch - update -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - apiGroups: - coordination.k8s.io resources: @@ -121,69 +41,3 @@ 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 From 686fb5d8052d8de12c93cbcab45b605b77b2ecf8 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 16 Oct 2024 11:44:36 -0400 Subject: [PATCH 18/43] Bind all well known attributes, no 'context' anymore Signed-off-by: Alex Snaps --- controllers/auth_config_controller_test.go | 2 +- pkg/expressions/cel/expressions.go | 25 ++++++++++++++++------ pkg/expressions/cel/expressions_test.go | 6 +++--- tests/v1beta2/authconfig.yaml | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/controllers/auth_config_controller_test.go b/controllers/auth_config_controller_test.go index b7e97f3b..bd8bd803 100644 --- a/controllers/auth_config_controller_test.go +++ b/controllers/auth_config_controller_test.go @@ -96,7 +96,7 @@ func newTestAuthConfig(authConfigLabels map[string]string) api.AuthConfig { Patterns: []api.PatternExpressionOrRef{ { CelPredicate: api.CelPredicate{ - Predicate: "context.identity.role == 'admin'", + Predicate: "auth.identity.role == 'admin'", }, }, }, diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 8cd7b602..32c9d50b 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -14,8 +14,11 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) +const RootMetadataBinding = "metadata" +const RootRequestBinding = "request" +const RootSourceBinding = "source" +const RootDestinationBinding = "destination" const RootAuthBinding = "auth" -const RootContextBinding = "context" type Predicate struct { program cel.Program @@ -116,8 +119,11 @@ func (e *Expression) EvaluateStringValue(json string) (string, error) { func Compile(expression string, expectedType *cel.Type, opts ...cel.EnvOption) (cel.Program, error) { envOpts := append([]cel.EnvOption{cel.Declarations( + decls.NewConst(RootMetadataBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + decls.NewConst(RootRequestBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + decls.NewConst(RootSourceBinding, decls.NewObjectType("google.protobuf.Struct"), nil), + decls.NewConst(RootDestinationBinding, decls.NewObjectType("google.protobuf.Struct"), nil), decls.NewConst(RootAuthBinding, decls.NewObjectType("google.protobuf.Struct"), nil), - decls.NewConst(RootContextBinding, decls.NewObjectType("google.protobuf.Struct"), nil), )}, opts...) env, env_err := cel.NewEnv(envOpts...) if env_err != nil { @@ -166,11 +172,18 @@ func AuthJsonToCel(json string) (map[string]interface{}, error) { if err := jsonpb.Unmarshal(strings.NewReader(json), &data); err != nil { return nil, err } - auth := data.GetFields()["auth"] - context := data.GetFields()["context"] + metadata := data.GetFields()[RootMetadataBinding] + request := data.GetFields()[RootRequestBinding] + source := data.GetFields()[RootSourceBinding] + destination := data.GetFields()[RootDestinationBinding] + auth := data.GetFields()[RootAuthBinding] + input := map[string]interface{}{ - RootAuthBinding: auth, - RootContextBinding: context, + RootMetadataBinding: metadata, + RootRequestBinding: request, + RootSourceBinding: source, + RootDestinationBinding: destination, + RootAuthBinding: auth, } return input, nil } diff --git a/pkg/expressions/cel/expressions_test.go b/pkg/expressions/cel/expressions_test.go index e7f228ef..587ac4cd 100644 --- a/pkg/expressions/cel/expressions_test.go +++ b/pkg/expressions/cel/expressions_test.go @@ -13,7 +13,7 @@ func TestPredicate(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - predicate, err := NewPredicate(`context`) + predicate, err := NewPredicate(`auth`) assert.ErrorContains(t, err, "wanted bool output type") pipelineMock := mock_auth.NewMockAuthPipeline(ctrl) @@ -34,10 +34,10 @@ func TestPredicate(t *testing.T) { assert.NilError(t, err) assert.Equal(t, response, true) - predicate, err = NewPredicate(`context.request.http.method == "GET"`) + predicate, err = NewPredicate(`request.http.method == "GET"`) assert.NilError(t, err) - pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http": {"method": "GET"}}}}`) + pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"request":{"http": {"method": "GET"}}}`) 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 630eac33..ede157d4 100644 --- a/tests/v1beta2/authconfig.yaml +++ b/tests/v1beta2/authconfig.yaml @@ -74,7 +74,7 @@ spec: anonymous: {} priority: 1 when: - - predicate: context.request.http.method == "GET" + - predicate: request.http.method == "GET" - selector: context.request.http.path operator: matches value: ^/$ From 3925889e8753a51592b169535c1e5bc36a630afc Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Fri, 18 Oct 2024 08:48:12 -0400 Subject: [PATCH 19/43] Update examples Signed-off-by: Alex Snaps --- api/v1beta3/auth_config_conversion.go | 4 -- api/v1beta3/auth_config_webhook.go | 11 ----- tests/v1beta2/authconfig.yaml | 4 +- tests/v1beta3/authconfig.yaml | 71 ++++++++++----------------- 4 files changed, 30 insertions(+), 60 deletions(-) delete mode 100644 api/v1beta3/auth_config_conversion.go delete mode 100644 api/v1beta3/auth_config_webhook.go diff --git a/api/v1beta3/auth_config_conversion.go b/api/v1beta3/auth_config_conversion.go deleted file mode 100644 index 418e7a5c..00000000 --- a/api/v1beta3/auth_config_conversion.go +++ /dev/null @@ -1,4 +0,0 @@ -package v1beta3 - -// Hub marks this version as a conversion hub. -func (a *AuthConfig) Hub() {} diff --git a/api/v1beta3/auth_config_webhook.go b/api/v1beta3/auth_config_webhook.go deleted file mode 100644 index 4946921f..00000000 --- a/api/v1beta3/auth_config_webhook.go +++ /dev/null @@ -1,11 +0,0 @@ -package v1beta3 - -import ( - ctrl "sigs.k8s.io/controller-runtime" -) - -func (a *AuthConfig) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(a). - Complete() -} diff --git a/tests/v1beta2/authconfig.yaml b/tests/v1beta2/authconfig.yaml index ede157d4..93114dc8 100644 --- a/tests/v1beta2/authconfig.yaml +++ b/tests/v1beta2/authconfig.yaml @@ -74,7 +74,9 @@ spec: anonymous: {} priority: 1 when: - - predicate: request.http.method == "GET" + - selector: context.request.http.method + operator: eq + value: GET - selector: context.request.http.path operator: matches value: ^/$ diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index 6a6c067a..c10fdc6d 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -8,13 +8,9 @@ spec: patterns: admin-path: - - selector: context.request.http.path - operator: matches - value: ^/admin(/.*)?$ + - predicate: request.http.path.matches("^/admin(/.*)?$") resource-path: - - selector: context.request.http.path - operator: matches - value: ^/greetings/\d+$ + - predicate: request.http.path.matches("^/greetings/\d+$") authentication: k8s-auth: @@ -25,8 +21,7 @@ spec: kubernetes-rbac: value: true username: - selector: auth.identity.user.username - value: null + expression: auth.identity.user.username api-key: apiKey: selector: @@ -39,7 +34,7 @@ spec: kubernetes-rbac: value: true username: - selector: auth.identity.metadata.annotations.username + expression: auth.identity.metadata.annotations.username keycloak: jwt: issuerUrl: http://keycloak.authorino.svc.cluster.local:8080/realms/kuadrant @@ -48,9 +43,9 @@ spec: jwt-rbac: value: true roles: - selector: auth.identity.realm_access.roles + expression: auth.identity.realm_access.roles username: - selector: auth.identity.preferred_username + expression: auth.identity.preferred_username oauth2-introspection: oauth2Introspection: credentialsRef: @@ -64,22 +59,18 @@ spec: jwt-rbac: value: true roles: - selector: auth.identity.realm_access.roles + expression: auth.identity.realm_access.roles username: - selector: auth.identity.preferred_username + expression: auth.identity.preferred_username cache: key: - selector: context.request.http.headers.authorization + expression: request.http.headers.authorization anonymous: anonymous: {} priority: 1 when: - - selector: context.request.http.method - operator: eq - value: GET - - selector: context.request.http.path - operator: matches - value: ^/$ + - predicate: request.http.method == "GET" + - predicate: request.http.path.matches("^/$") defaults: username: value: global @@ -95,13 +86,13 @@ spec: url: http://ip-location.authorino.svc.cluster.local:3000/{context.request.http.headers.x-forwarded-for.@extract:{"sep":","}} cache: key: - selector: context.request.http.headers.x-forwarded-for.@extract:{"sep":","} + selector: request.http.headers.x-forwarded-for.@extract:{"sep":","} user-info: userInfo: identitySource: keycloak cache: key: - selector: context.request.http.headers.authorization + expression: request.http.headers.authorization resource-info: when: - patternRef: resource-path @@ -111,7 +102,7 @@ spec: endpoint: http://keycloak.authorino.svc.cluster.local:8080/realms/kuadrant cache: key: - selector: context.request.http.path + expression: request.http.path authorization: allowed-methods: @@ -131,23 +122,17 @@ spec: admin-kubernetes-rbac: when: - patternRef: admin-path - - selector: auth.identity.kubernetes-rbac - operator: eq - value: 'true' + - predicate: auth.identity.kubernetes-rbac kubernetesSubjectAccessReview: user: - selector: auth.identity.username + expression: auth.identity.username admin-jwt-rbac: when: - patternRef: admin-path - - selector: auth.identity.jwt-rbac - operator: eq - value: 'true' + - predicate: auth.identity.jwt-rbac patternMatching: patterns: - - selector: auth.identity.roles - operator: incl - value: admin + - predicate: auth.identity.roles.exists("admin") resource-owner: when: - patternRef: resource-path @@ -180,40 +165,38 @@ spec: value: Authorino x-username: plain: - selector: auth.identity.username + expression: auth.identity.username x-auth-data: json: properties: username: - selector: auth.identity.username + expression: auth.identity.username geo: - selector: auth.metadata.geo-info + expression: auth.metadata.geo-info timestamp: - selector: auth.authorization.timestamp.now + expression: auth.authorization.timestamp.now wristband: wristband: issuer: https://authorino-authorino-oidc.authorino.svc.cluster.local:8083/authorino/e2e-test/wristband tokenDuration: 300 customClaims: username: - selector: auth.identity.username + expression: auth.identity.username uri: - selector: context.request.http.path + expression: request.http.path scope: - selector: context.request.http.method.@case:lower + selector: request.http.method.@case:lower signingKeyRefs: - name: wristband-signing-key algorithm: ES256 when: - - selector: auth.identity.anonymous - operator: neq - value: 'true' + - predicate: auth.identity.anonymous == false dynamicMetadata: rate-limit-data: json: properties: username: - selector: auth.identity.username + expression: auth.identity.username key: ext_auth_data --- apiVersion: v1 From 9e7168efff084a0ac2f9bd7aa88e9be9f9d803ae Mon Sep 17 00:00:00 2001 From: KevFan Date: Tue, 22 Oct 2024 13:41:16 +0100 Subject: [PATCH 20/43] makefile: use project bin controller-gen & bump kustomize Signed-off-by: KevFan --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d6a7013f..5efc0a73 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ controller-gen: ## Installs controller-gen in $PROJECT_DIR/bin KUSTOMIZE = $(PROJECT_DIR)/bin/kustomize kustomize: ## Installs kustomize in $PROJECT_DIR/bin - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@v4.5.5) + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5@v5.5.0) ENVTEST = $(PROJECT_DIR)/bin/setup-envtest envtest: ## Installs setup-envtest in $PROJECT_DIR/bin @@ -118,11 +118,11 @@ vet: ## Runs go vet against code go vet ./... generate: vendor controller-gen ## Generates types deepcopy code - controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." $(MAKE) fmt vet manifests: controller-gen kustomize ## Generates the manifests in $PROJECT_DIR/install - controller-gen crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=install/crd output:rbac:artifacts:config=install/rbac && $(KUSTOMIZE) build install > $(AUTHORINO_MANIFESTS) + $(CONTROLLER_GEN) crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=install/crd output:rbac:artifacts:config=install/rbac && $(KUSTOMIZE) build install > $(AUTHORINO_MANIFESTS) $(MAKE) patch-webhook run:git_sha=$(shell git rev-parse HEAD) From 9fce9641643943283e0b47d1511c2705193dcea0 Mon Sep 17 00:00:00 2001 From: KevFan Date: Tue, 22 Oct 2024 13:41:44 +0100 Subject: [PATCH 21/43] manifests: generate v1beta3 changes Signed-off-by: KevFan --- .../authorino.kuadrant.io_authconfigs.yaml | 2304 +-------------- install/manifests.yaml | 2510 +---------------- 2 files changed, 105 insertions(+), 4709 deletions(-) diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index d21dd4eb..6f03a31d 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2313,7 +2313,7 @@ spec: type: object type: object served: true - storage: true + storage: false subresources: status: {} - additionalPrinterColumns: @@ -4721,2308 +4721,6 @@ spec: type: object type: object served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Ready for all hosts - jsonPath: .status.summary.ready - name: Ready - type: string - - description: Number of hosts ready - jsonPath: .status.summary.numHostsReady - name: Hosts - type: string - - description: Number of trusted identity sources - jsonPath: .status.summary.numIdentitySources - name: Authentication - priority: 2 - type: integer - - description: Number of external metadata sources - jsonPath: .status.summary.numMetadataSources - name: Metadata - priority: 2 - type: integer - - description: Number of authorization policies - jsonPath: .status.summary.numAuthorizationPolicies - name: Authorization - priority: 2 - type: integer - - description: Number of items added to the authorization response - jsonPath: .status.summary.numResponseItems - name: Response - priority: 2 - type: integer - - description: Whether issuing Festival Wristbands - jsonPath: .status.summary.festivalWristbandEnabled - name: Wristband - priority: 2 - type: boolean - name: v1beta3 - schema: - openAPIV3Schema: - description: AuthConfig is the schema for Authorino's AuthConfig API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specifies the desired state of the AuthConfig resource, i.e. - the authencation/authorization scheme to be applied to protect the matching - service hosts. - properties: - authentication: - additionalProperties: - properties: - anonymous: - description: Anonymous access. - type: object - apiKey: - description: Authentication based on API keys stored in Kubernetes - secrets. - properties: - allNamespaces: - default: false - description: |- - Whether Authorino should look for API key secrets in all namespaces or only in the same namespace as the AuthConfig. - Enabling this option in namespaced Authorino instances has no effect. - type: boolean - selector: - description: Label selector used by Authorino to match secrets - from the cluster storing valid credentials to authenticate - to this service - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - selector - type: object - cache: - description: |- - Caching options for the resolved object returned when applying this config. - Omit it to avoid caching objects for this config. - properties: - key: - description: |- - Key used to store the entry in the cache. - The resolved key must be unique within the scope of this particular config. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - ttl: - default: 60 - description: Duration (in seconds) of the external data - in the cache before pulled again from the source. - type: integer - required: - - key - type: object - credentials: - description: |- - Defines where credentials are required to be passed in the request for authentication based on this config. - If omitted, it defaults to credentials passed in the HTTP Authorization header and the "Bearer" prefix prepended to the secret credential value. - properties: - authorizationHeader: - properties: - prefix: - type: string - type: object - cookie: - properties: - name: - type: string - required: - - name - type: object - customHeader: - properties: - name: - type: string - required: - - name - type: object - queryString: - properties: - name: - type: string - required: - - name - type: object - type: object - defaults: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: |- - Set default property values (claims) for the resolved identity object, that are set before appending the object to - the authorization JSON. If the property is already present in the resolved identity object, the default value is ignored. - It requires the resolved identity object to always be a JSON object. - Do not use this option with identity objects of other JSON types (array, string, etc). - type: object - jwt: - description: Authentication based on JWT tokens. - properties: - issuerUrl: - description: |- - URL of the issuer of the JWT. - If `jwksUrl` is omitted, Authorino will append the path to the OpenID Connect Well-Known Discovery endpoint - (i.e. "/.well-known/openid-configuration") to this URL, to discover the OIDC configuration where to obtain - the "jkws_uri" claim from. - The value must coincide with the value of the "iss" (issuer) claim of the discovered OpenID Connect configuration. - type: string - ttl: - description: |- - Decides how long to wait before refreshing the JWKS (in seconds). - If omitted, Authorino will never refresh the JWKS. - type: integer - type: object - kubernetesTokenReview: - description: Authentication by Kubernetes token review. - properties: - audiences: - description: |- - The list of audiences (scopes) that must be claimed in a Kubernetes authentication token supplied in the request, and reviewed by Authorino. - If omitted, Authorino will review tokens expecting the host name of the requested protected service amongst the audiences. - items: - type: string - type: array - type: object - metrics: - default: false - description: Whether this config should generate individual - observability metrics - type: boolean - oauth2Introspection: - description: Authentication by OAuth2 token introspection. - properties: - credentialsRef: - description: Reference to a Kubernetes secret in the same - namespace, that stores client credentials to the OAuth2 - server. - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - endpoint: - description: The full URL of the token introspection endpoint. - type: string - tokenTypeHint: - description: |- - The token type hint for the token introspection. - If omitted, it defaults to "access_token". - type: string - required: - - credentialsRef - - endpoint - type: object - overrides: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: |- - Overrides the resolved identity object by setting the additional properties (claims) specified in this config, - before appending the object to the authorization JSON. - It requires the resolved identity object to always be a JSON object. - Do not use this option with identity objects of other JSON types (array, string, etc). - type: object - plain: - description: |- - Identity object extracted from the context. - Use this method when authentication is performed beforehand by a proxy and the resulting object passed to Authorino as JSON in the auth request. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - required: - - selector - type: object - priority: - default: 0 - description: |- - Priority group of the config. - All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. - type: integer - when: - description: |- - Conditions for Authorino to enforce this config. - If omitted, the config will be enforced for all requests. - If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. - items: - properties: - all: - description: A list of pattern expressions to be evaluated - as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to be evaluated - as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - x509: - description: |- - Authentication based on client X.509 certificates. - The certificates presented by the clients must be signed by a trusted CA whose certificates are stored in Kubernetes secrets. - properties: - allNamespaces: - default: false - description: |- - Whether Authorino should look for TLS secrets in all namespaces or only in the same namespace as the AuthConfig. - Enabling this option in namespaced Authorino instances has no effect. - type: boolean - selector: - description: |- - Label selector used by Authorino to match secrets from the cluster storing trusted CA certificates to validate - clients trying to authenticate to this service - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - selector - type: object - type: object - description: |- - Authentication configs. - At least one config MUST evaluate to a valid identity object for the auth request to be successful. - type: object - authorization: - additionalProperties: - properties: - cache: - description: |- - Caching options for the resolved object returned when applying this config. - Omit it to avoid caching objects for this config. - properties: - key: - description: |- - Key used to store the entry in the cache. - The resolved key must be unique within the scope of this particular config. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - ttl: - default: 60 - description: Duration (in seconds) of the external data - in the cache before pulled again from the source. - type: integer - required: - - key - type: object - kubernetesSubjectAccessReview: - description: Authorization by Kubernetes SubjectAccessReview - properties: - groups: - description: Groups the user must be a member of or, if - `user` is omitted, the groups to check for authorization - in the Kubernetes RBAC. - items: - type: string - type: array - resourceAttributes: - description: |- - Use resourceAttributes to check permissions on Kubernetes resources. - If omitted, it performs a non-resource SubjectAccessReview, with verb and path inferred from the request. - properties: - group: - description: |- - API group of the resource. - Use '*' for all API groups. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - name: - description: |- - Resource name - Omit it to check for authorization on all resources of the specified kind. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - namespace: - description: Namespace where the user must have permissions - on the resource. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - resource: - description: |- - Resource kind - Use '*' for all resource kinds. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - subresource: - description: Subresource kind - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - verb: - description: |- - Verb to check for authorization on the resource. - Use '*' for all verbs. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - user: - description: |- - User to check for authorization in the Kubernetes RBAC. - Omit it to check for group authorization only. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - metrics: - default: false - description: Whether this config should generate individual - observability metrics - type: boolean - opa: - description: Open Policy Agent (OPA) Rego policy. - properties: - allValues: - default: false - description: |- - Returns the value of all Rego rules in the virtual document. Values can be read in subsequent evaluators/phases of the Auth Pipeline. - Otherwise, only the default `allow` rule will be exposed. - Returning all Rego rules can affect performance of OPA policies during reconciliation (policy precompile) and at runtime. - type: boolean - externalPolicy: - description: |- - Settings for fetching the OPA policy from an external registry. - Use it alternatively to 'rego'. - For the configurations of the HTTP request, the following options are not implemented: 'method', 'body', 'bodyParameters', - 'contentType', 'headers', 'oauth2'. Use it only with: 'url', 'sharedSecret', 'credentials'. - properties: - body: - description: |- - Raw body of the HTTP request. - 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: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - bodyParameters: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: |- - Custom parameters to encode in the body of the HTTP request. - Superseded by 'body'; 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). - type: object - contentType: - default: application/x-www-form-urlencoded - description: |- - Content-Type of the request body. Shapes how 'bodyParameters' are encoded. - Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. - enum: - - application/x-www-form-urlencoded - - application/json - type: string - credentials: - description: |- - Defines where client credentials will be passed in the request to the service. - If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. - properties: - authorizationHeader: - properties: - prefix: - type: string - type: object - cookie: - properties: - name: - type: string - required: - - name - type: object - customHeader: - properties: - name: - type: string - required: - - name - type: object - queryString: - properties: - name: - type: string - required: - - name - type: object - type: object - headers: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: Custom headers in the HTTP request. - type: object - method: - default: GET - description: |- - HTTP verb used in the request to the service. Accepted values: GET (default), POST. - When the request method is POST, the authorization JSON is passed in the body of the request. - enum: - - GET - - POST - - PUT - - PATCH - - DELETE - - HEAD - - OPTIONS - - CONNECT - - TRACE - type: string - oauth2: - description: Authentication with the HTTP service by - OAuth2 Client Credentials grant. - properties: - cache: - default: true - description: |- - Caches and reuses the token until expired. - Set it to false to force fetch the token at every authorization request regardless of expiration. - type: boolean - clientId: - description: OAuth2 Client ID. - type: string - clientSecretRef: - description: Reference to a Kuberentes Secret key - that stores that OAuth2 Client Secret. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - extraParams: - additionalProperties: - type: string - description: Optional extra parameters for the requests - to the token URL. - type: object - scopes: - description: Optional scopes for the client credentials - grant, if supported by he OAuth2 server. - items: - type: string - type: array - tokenUrl: - description: Token endpoint URL of the OAuth2 resource - server. - type: string - required: - - clientId - - clientSecretRef - - tokenUrl - type: object - sharedSecretRef: - description: |- - Reference to a Secret key whose value will be passed by Authorino in the request. - The HTTP service can use the shared secret to authenticate the origin of the request. - Ignored if used together with oauth2. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - ttl: - description: Duration (in seconds) of the external data - in the cache before pulled again from the source. - type: integer - url: - description: |- - Endpoint URL of the HTTP service. - The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported - by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. - E.g. https://ext-auth-server.io/metadata?p={request.path} - type: string - required: - - url - type: object - rego: - description: |- - Authorization policy as a Rego language document. - The Rego document must include the "allow" condition, set by Authorino to "false" by default (i.e. requests are unauthorized unless changed). - The Rego document must NOT include the "package" declaration in line 1. - type: string - type: object - patternMatching: - description: Pattern-matching authorization rules. - properties: - patterns: - items: - properties: - all: - description: A list of pattern expressions to be evaluated - as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to be evaluated - as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - required: - - patterns - type: object - priority: - default: 0 - description: |- - Priority group of the config. - All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. - type: integer - spicedb: - description: Authorization decision delegated to external Authzed/SpiceDB - server. - properties: - endpoint: - description: Hostname and port number to the GRPC interface - of the SpiceDB server (e.g. spicedb:50051). - type: string - insecure: - description: Insecure HTTP connection (i.e. disables TLS - verification) - type: boolean - permission: - description: The name of the permission (or relation) on - which to execute the check. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - resource: - description: The resource on which to check the permission - or relation. - properties: - kind: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - name: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - sharedSecretRef: - description: Reference to a Secret key whose value will - be used by Authorino to authenticate with the Authzed - service. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - subject: - description: The subject that will be checked for the permission - or relation. - properties: - kind: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - name: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - required: - - endpoint - type: object - when: - description: |- - Conditions for Authorino to enforce this config. - If omitted, the config will be enforced for all requests. - If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. - items: - properties: - all: - description: A list of pattern expressions to be evaluated - as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to be evaluated - as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - type: object - description: |- - Authorization policies. - All policies MUST evaluate to "allowed = true" for the auth request be successful. - type: object - callbacks: - additionalProperties: - properties: - cache: - description: |- - Caching options for the resolved object returned when applying this config. - Omit it to avoid caching objects for this config. - properties: - key: - description: |- - Key used to store the entry in the cache. - The resolved key must be unique within the scope of this particular config. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - ttl: - default: 60 - description: Duration (in seconds) of the external data - in the cache before pulled again from the source. - type: integer - required: - - key - type: object - http: - description: Settings of the external HTTP request - properties: - body: - description: |- - Raw body of the HTTP request. - 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: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - bodyParameters: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: |- - Custom parameters to encode in the body of the HTTP request. - Superseded by 'body'; 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). - type: object - contentType: - default: application/x-www-form-urlencoded - description: |- - Content-Type of the request body. Shapes how 'bodyParameters' are encoded. - Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. - enum: - - application/x-www-form-urlencoded - - application/json - type: string - credentials: - description: |- - Defines where client credentials will be passed in the request to the service. - If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. - properties: - authorizationHeader: - properties: - prefix: - type: string - type: object - cookie: - properties: - name: - type: string - required: - - name - type: object - customHeader: - properties: - name: - type: string - required: - - name - type: object - queryString: - properties: - name: - type: string - required: - - name - type: object - type: object - headers: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: Custom headers in the HTTP request. - type: object - method: - default: GET - description: |- - HTTP verb used in the request to the service. Accepted values: GET (default), POST. - When the request method is POST, the authorization JSON is passed in the body of the request. - enum: - - GET - - POST - - PUT - - PATCH - - DELETE - - HEAD - - OPTIONS - - CONNECT - - TRACE - type: string - oauth2: - description: Authentication with the HTTP service by OAuth2 - Client Credentials grant. - properties: - cache: - default: true - description: |- - Caches and reuses the token until expired. - Set it to false to force fetch the token at every authorization request regardless of expiration. - type: boolean - clientId: - description: OAuth2 Client ID. - type: string - clientSecretRef: - description: Reference to a Kuberentes Secret key that - stores that OAuth2 Client Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - extraParams: - additionalProperties: - type: string - description: Optional extra parameters for the requests - to the token URL. - type: object - scopes: - description: Optional scopes for the client credentials - grant, if supported by he OAuth2 server. - items: - type: string - type: array - tokenUrl: - description: Token endpoint URL of the OAuth2 resource - server. - type: string - required: - - clientId - - clientSecretRef - - tokenUrl - type: object - sharedSecretRef: - description: |- - Reference to a Secret key whose value will be passed by Authorino in the request. - The HTTP service can use the shared secret to authenticate the origin of the request. - Ignored if used together with oauth2. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - url: - description: |- - Endpoint URL of the HTTP service. - The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported - by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. - E.g. https://ext-auth-server.io/metadata?p={request.path} - type: string - required: - - url - type: object - metrics: - default: false - description: Whether this config should generate individual - observability metrics - type: boolean - priority: - default: 0 - description: |- - Priority group of the config. - All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. - type: integer - when: - description: |- - Conditions for Authorino to enforce this config. - If omitted, the config will be enforced for all requests. - If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. - items: - properties: - all: - description: A list of pattern expressions to be evaluated - as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to be evaluated - as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - required: - - http - type: object - description: |- - Callback functions. - Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. - type: object - hosts: - description: |- - The list of public host names of the services protected by this authentication/authorization scheme. - Authorino uses the requested host to lookup for the corresponding authentication/authorization configs to enforce. - items: - type: string - type: array - metadata: - additionalProperties: - properties: - cache: - description: |- - Caching options for the resolved object returned when applying this config. - Omit it to avoid caching objects for this config. - properties: - key: - description: |- - Key used to store the entry in the cache. - The resolved key must be unique within the scope of this particular config. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - ttl: - default: 60 - description: Duration (in seconds) of the external data - in the cache before pulled again from the source. - type: integer - required: - - key - type: object - http: - description: External source of auth metadata via HTTP request - properties: - body: - description: |- - Raw body of the HTTP request. - 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: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - bodyParameters: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: |- - Custom parameters to encode in the body of the HTTP request. - Superseded by 'body'; 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). - type: object - contentType: - default: application/x-www-form-urlencoded - description: |- - Content-Type of the request body. Shapes how 'bodyParameters' are encoded. - Use it with method=POST; for GET requests, Content-Type is automatically set to 'text/plain'. - enum: - - application/x-www-form-urlencoded - - application/json - type: string - credentials: - description: |- - Defines where client credentials will be passed in the request to the service. - If omitted, it defaults to client credentials passed in the HTTP Authorization header and the "Bearer" prefix expected prepended to the secret value. - properties: - authorizationHeader: - properties: - prefix: - type: string - type: object - cookie: - properties: - name: - type: string - required: - - name - type: object - customHeader: - properties: - name: - type: string - required: - - name - type: object - queryString: - properties: - name: - type: string - required: - - name - type: object - type: object - headers: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: Custom headers in the HTTP request. - type: object - method: - default: GET - description: |- - HTTP verb used in the request to the service. Accepted values: GET (default), POST. - When the request method is POST, the authorization JSON is passed in the body of the request. - enum: - - GET - - POST - - PUT - - PATCH - - DELETE - - HEAD - - OPTIONS - - CONNECT - - TRACE - type: string - oauth2: - description: Authentication with the HTTP service by OAuth2 - Client Credentials grant. - properties: - cache: - default: true - description: |- - Caches and reuses the token until expired. - Set it to false to force fetch the token at every authorization request regardless of expiration. - type: boolean - clientId: - description: OAuth2 Client ID. - type: string - clientSecretRef: - description: Reference to a Kuberentes Secret key that - stores that OAuth2 Client Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - extraParams: - additionalProperties: - type: string - description: Optional extra parameters for the requests - to the token URL. - type: object - scopes: - description: Optional scopes for the client credentials - grant, if supported by he OAuth2 server. - items: - type: string - type: array - tokenUrl: - description: Token endpoint URL of the OAuth2 resource - server. - type: string - required: - - clientId - - clientSecretRef - - tokenUrl - type: object - sharedSecretRef: - description: |- - Reference to a Secret key whose value will be passed by Authorino in the request. - The HTTP service can use the shared secret to authenticate the origin of the request. - Ignored if used together with oauth2. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the Authorino's - namespace to select from. - type: string - required: - - key - - name - type: object - url: - description: |- - Endpoint URL of the HTTP service. - The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported - by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. - E.g. https://ext-auth-server.io/metadata?p={request.path} - type: string - required: - - url - type: object - metrics: - default: false - description: Whether this config should generate individual - observability metrics - type: boolean - priority: - default: 0 - description: |- - Priority group of the config. - All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. - type: integer - uma: - description: User-Managed Access (UMA) source of resource data. - properties: - credentialsRef: - description: Reference to a Kubernetes secret in the same - namespace, that stores client credentials to the resource - registration API of the UMA server. - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - endpoint: - description: |- - The endpoint of the UMA server. - The value must coincide with the "issuer" claim of the UMA config discovered from the well-known uma configuration endpoint. - type: string - required: - - credentialsRef - - endpoint - type: object - userInfo: - description: OpendID Connect UserInfo linked to an OIDC authentication - config specified in this same AuthConfig. - properties: - identitySource: - description: The name of an OIDC-enabled JWT authentication - config whose OpenID Connect configuration discovered includes - the OIDC "userinfo_endpoint" claim. - type: string - required: - - identitySource - type: object - when: - description: |- - Conditions for Authorino to enforce this config. - If omitted, the config will be enforced for all requests. - If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. - items: - properties: - all: - description: A list of pattern expressions to be evaluated - as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to be evaluated - as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - type: object - description: |- - Metadata sources. - Authorino fetches auth metadata as JSON from sources specified in this config. - type: object - patterns: - additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - description: Named sets of patterns that can be referred in `when` - conditions and in pattern-matching authorization policy rules. - type: object - response: - description: |- - Response items. - Authorino builds custom responses to the client of the auth request. - properties: - success: - description: |- - Response items to be included in the auth response when the request is authenticated and authorized. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. - properties: - dynamicMetadata: - additionalProperties: - description: Settings of the success custom response item. - properties: - cache: - description: |- - Caching options for the resolved object returned when applying this config. - Omit it to avoid caching objects for this config. - properties: - key: - description: |- - Key used to store the entry in the cache. - The resolved key must be unique within the scope of this particular config. - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - ttl: - default: 60 - description: Duration (in seconds) of the external - data in the cache before pulled again from the - source. - type: integer - required: - - key - type: object - json: - description: |- - JSON object - Specify it as the list of properties of the object, whose values can combine static values and values selected from the authorization JSON. - properties: - properties: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - required: - - properties - type: object - key: - description: |- - The key used to add the custom response item (name of the HTTP header or root property of the Dynamic Metadata object). - If omitted, it will be set to the name of the response config. - type: string - metrics: - default: false - description: Whether this config should generate individual - observability metrics - type: boolean - plain: - description: Plain text content - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - priority: - default: 0 - description: |- - Priority group of the config. - All configs in the same priority group are evaluated concurrently; consecutive priority groups are evaluated sequentially. - type: integer - when: - description: |- - Conditions for Authorino to enforce this config. - If omitted, the config will be enforced for all requests. - If present, all conditions must match for the config to be enforced; otherwise, the config will be skipped. - items: - properties: - all: - description: A list of pattern expressions to - be evaluated as a logical AND. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - any: - description: A list of pattern expressions to - be evaluated as a logical OR. - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - patternRef: - description: Reference to a named set of pattern - expressions - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array - wristband: - description: Authorino Festival Wristband token - properties: - customClaims: - additionalProperties: - properties: - 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}!"). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - type: string - value: - description: Static value - x-kubernetes-preserve-unknown-fields: true - type: object - description: Any claims to be added to the wristband - token apart from the standard JWT claims (iss, - iat, exp) added by default. - type: object - issuer: - description: 'The endpoint to the Authorino service - that issues the wristband (format: ://:/, - where = /://:/, - where = /://:/, - where = /://:/, - where = / Date: Wed, 23 Oct 2024 05:50:45 -0400 Subject: [PATCH 22/43] Fix update examples Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index c10fdc6d..76d7a470 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -8,9 +8,13 @@ spec: patterns: admin-path: - - predicate: request.http.path.matches("^/admin(/.*)?$") + - selector: context.request.http.path + operator: matches + value: ^/admin(/.*)?$ resource-path: - - predicate: request.http.path.matches("^/greetings/\d+$") + - selector: context.request.http.path + operator: matches + value: ^/greetings/\d+$ authentication: k8s-auth: From 49c9c422fe730ae8a2477a404ef21dcee88cb062 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 13:06:06 -0400 Subject: [PATCH 23/43] Trying to fix things Signed-off-by: Alex Snaps --- pkg/evaluators/identity/plain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/evaluators/identity/plain.go b/pkg/evaluators/identity/plain.go index c6bc5ee0..b44d7824 100644 --- a/pkg/evaluators/identity/plain.go +++ b/pkg/evaluators/identity/plain.go @@ -18,10 +18,10 @@ type Plain struct { } func (p *Plain) Call(pipeline auth.AuthPipeline, ctx context.Context) (interface{}, error) { - if object, err := p.Value.ResolveFor(pipeline.GetAuthorizationJSON()); object != nil { - return object, nil - } else if err != nil { + if object, err := p.Value.ResolveFor(pipeline.GetAuthorizationJSON()); err != nil { return nil, err + } else if object != nil { + return object, nil } return nil, fmt.Errorf("could not retrieve identity object or null") } From 613c9b598427bbdcf9b91eb7aad89a3e9f80cbe2 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 13:23:48 -0400 Subject: [PATCH 24/43] Deal with cacheKey creation failures Signed-off-by: Alex Snaps --- pkg/evaluators/authorization.go | 10 ++++++---- pkg/evaluators/identity.go | 10 ++++++---- pkg/evaluators/metadata.go | 10 ++++++---- pkg/evaluators/response.go | 10 ++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pkg/evaluators/authorization.go b/pkg/evaluators/authorization.go index a23f5059..765d0d2c 100644 --- a/pkg/evaluators/authorization.go +++ b/pkg/evaluators/authorization.go @@ -58,10 +58,12 @@ func (config *AuthorizationConfig) Call(pipeline auth.AuthPipeline, ctx context. if cache != nil { cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) - if cachedObj, err := cache.Get(cacheKey); err != nil { - logger.V(1).Error(err, "failed to retrieve data from the cache") - } else if cachedObj != nil { - return cachedObj, nil + if cacheKey != nil { + if cachedObj, err := cache.Get(cacheKey); err != nil { + logger.V(1).Error(err, "failed to retrieve data from the cache") + } else if cachedObj != nil { + return cachedObj, nil + } } } diff --git a/pkg/evaluators/identity.go b/pkg/evaluators/identity.go index 887b8a41..0bce9cbf 100644 --- a/pkg/evaluators/identity.go +++ b/pkg/evaluators/identity.go @@ -85,10 +85,12 @@ func (config *IdentityConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte if cache != nil { cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) - if cachedObj, err := cache.Get(cacheKey); err != nil { - logger.V(1).Error(err, "failed to retrieve data from the cache") - } else if cachedObj != nil { - return cachedObj, nil + if cacheKey != nil { + if cachedObj, err := cache.Get(cacheKey); err != nil { + logger.V(1).Error(err, "failed to retrieve data from the cache") + } else if cachedObj != nil { + return cachedObj, nil + } } } diff --git a/pkg/evaluators/metadata.go b/pkg/evaluators/metadata.go index f04d5695..02fc2602 100644 --- a/pkg/evaluators/metadata.go +++ b/pkg/evaluators/metadata.go @@ -54,10 +54,12 @@ func (config *MetadataConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte if cache != nil { cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) - if cachedObj, err := cache.Get(cacheKey); err != nil { - logger.V(1).Error(err, "failed to retrieve data from the cache") - } else if cachedObj != nil { - return cachedObj, nil + if cacheKey != nil { + if cachedObj, err := cache.Get(cacheKey); err != nil { + logger.V(1).Error(err, "failed to retrieve data from the cache") + } else if cachedObj != nil { + return cachedObj, nil + } } } diff --git a/pkg/evaluators/response.go b/pkg/evaluators/response.go index f116ad7e..b3a4af00 100644 --- a/pkg/evaluators/response.go +++ b/pkg/evaluators/response.go @@ -83,10 +83,12 @@ func (config *ResponseConfig) Call(pipeline auth.AuthPipeline, ctx context.Conte if cache != nil { cacheKey, _ = cache.ResolveKeyFor(pipeline.GetAuthorizationJSON()) - if cachedObj, err := cache.Get(cacheKey); err != nil { - logger.V(1).Error(err, "failed to retrieve data from the cache") - } else if cachedObj != nil { - return cachedObj, nil + if cacheKey != nil { + if cachedObj, err := cache.Get(cacheKey); err != nil { + logger.V(1).Error(err, "failed to retrieve data from the cache") + } else if cachedObj != nil { + return cachedObj, nil + } } } From b44543614b8b2c95c1b65842fe80139347acf607 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 15:12:27 -0400 Subject: [PATCH 25/43] Use v1beta2 for now Signed-off-by: Alex Snaps --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5efc0a73..6925a74a 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ report-benchmarks: cover: ## Shows test coverage go tool cover -html=cover.out -AUTHCONFIG_VERSION ?= v1beta3 +AUTHCONFIG_VERSION ?= v1beta2 VERBOSE ?= 0 e2e: ## Runs the end-to-end tests on a local environment setup $(MAKE) local-setup NAMESPACE=authorino KIND_CLUSTER_NAME=authorino-e2e AUTHORINO_IMAGE=$(AUTHORINO_IMAGE) TLS_ENABLED=$(TLS_ENABLED) OPERATOR_BRANCH=$(OPERATOR_BRANCH) AUTHORINO_MANIFESTS=$(AUTHORINO_MANIFESTS) AUTHORINO_INSTANCE=$(AUTHORINO_INSTANCE) ENVOY_OVERLAY=$(ENVOY_OVERLAY) DEPLOY_KEYCLOAK=1 FF=1 From a64198b704cecf80f6b1217c5a4aafadf5099ee0 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 18:05:48 -0400 Subject: [PATCH 26/43] I know it's halloween, but still... we should rewrite this in Ruby Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 4 ++-- pkg/evaluators/identity.go | 10 ++++++---- pkg/evaluators/identity_extension.go | 5 ++--- pkg/evaluators/identity_extension_test.go | 24 +++++++++++------------ pkg/evaluators/identity_test.go | 4 ++-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index e871c961..9140e322 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -183,13 +183,13 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf for identityCfgName, identity := range authConfigIdentityConfigs { extendedProperties := make([]evaluators.IdentityExtension, len(identity.Defaults)+len(identity.Overrides)) for propertyName, property := range identity.Defaults { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ Static: property.Value, Pattern: property.Selector, }, false)) } for propertyName, property := range identity.Overrides { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ Static: property.Value, Pattern: property.Selector, }, true)) diff --git a/pkg/evaluators/identity.go b/pkg/evaluators/identity.go index 0bce9cbf..01608d47 100644 --- a/pkg/evaluators/identity.go +++ b/pkg/evaluators/identity.go @@ -199,11 +199,13 @@ func (config *IdentityConfig) ResolveExtendedProperties(pipeline auth.AuthPipeli authJSON := pipeline.GetAuthorizationJSON() for _, extendedProperty := range config.ExtendedProperties { - resolved, err := extendedProperty.ResolveFor(extendedIdentityObject, authJSON) - if err != nil { - return nil, err + if extendedProperty.Value != nil { + resolved, err := extendedProperty.ResolveFor(extendedIdentityObject, authJSON) + if err != nil { + return nil, err + } + extendedIdentityObject[extendedProperty.Name] = resolved } - extendedIdentityObject[extendedProperty.Name] = resolved } return extendedIdentityObject, nil diff --git a/pkg/evaluators/identity_extension.go b/pkg/evaluators/identity_extension.go index 63dc87a9..bbea29fc 100644 --- a/pkg/evaluators/identity_extension.go +++ b/pkg/evaluators/identity_extension.go @@ -1,15 +1,14 @@ package evaluators import ( - "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/json" ) -func NewIdentityExtension(name string, value expressions.Value, overwrite bool) IdentityExtension { +func NewIdentityExtension(name string, value json.JSONValue, overwrite bool) IdentityExtension { return IdentityExtension{ JSONProperty: json.JSONProperty{ Name: name, - Value: value, + Value: &value, }, Overwrite: overwrite, } diff --git a/pkg/evaluators/identity_extension_test.go b/pkg/evaluators/identity_extension_test.go index 34ee0f2b..922c1765 100644 --- a/pkg/evaluators/identity_extension_test.go +++ b/pkg/evaluators/identity_extension_test.go @@ -23,62 +23,62 @@ func TestResolveIdentityExtension(t *testing.T) { }{ { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, false), expected: "beth", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, false), expected: "foo", }, { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "beth", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "1234567890", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "beth", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, } diff --git a/pkg/evaluators/identity_test.go b/pkg/evaluators/identity_test.go index 39620adf..8e16f6f2 100644 --- a/pkg/evaluators/identity_test.go +++ b/pkg/evaluators/identity_test.go @@ -41,8 +41,8 @@ func TestIdentityConfig_ResolveExtendedProperties(t *testing.T) { Name: "test", KubernetesAuth: &identity.KubernetesAuth{}, ExtendedProperties: []IdentityExtension{ - NewIdentityExtension("prop1", &json.JSONValue{Static: "value1"}, true), - NewIdentityExtension("prop2", &json.JSONValue{Pattern: "auth.identity.sub"}, true), + NewIdentityExtension("prop1", json.JSONValue{Static: "value1"}, true), + NewIdentityExtension("prop2", json.JSONValue{Pattern: "auth.identity.sub"}, true), }, } From adb07cff1e48d3759393e4f96455057063fb31de Mon Sep 17 00:00:00 2001 From: KevFan Date: Fri, 25 Oct 2024 12:23:01 +0100 Subject: [PATCH 27/43] manifests: update required oneOf conditions Signed-off-by: KevFan --- Makefile | 2 +- install/crd/patches/oneof_in_authconfigs.yaml | 21 ++++++++++++++ install/manifests.yaml | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6925a74a..5efc0a73 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ report-benchmarks: cover: ## Shows test coverage go tool cover -html=cover.out -AUTHCONFIG_VERSION ?= v1beta2 +AUTHCONFIG_VERSION ?= v1beta3 VERBOSE ?= 0 e2e: ## Runs the end-to-end tests on a local environment setup $(MAKE) local-setup NAMESPACE=authorino KIND_CLUSTER_NAME=authorino-e2e AUTHORINO_IMAGE=$(AUTHORINO_IMAGE) TLS_ENABLED=$(TLS_ENABLED) OPERATOR_BRANCH=$(OPERATOR_BRANCH) AUTHORINO_MANIFESTS=$(AUTHORINO_MANIFESTS) AUTHORINO_INSTANCE=$(AUTHORINO_INSTANCE) ENVOY_OVERLAY=$(ENVOY_OVERLAY) DEPLOY_KEYCLOAK=1 FF=1 diff --git a/install/crd/patches/oneof_in_authconfigs.yaml b/install/crd/patches/oneof_in_authconfigs.yaml index 919f6e83..f614394b 100644 --- a/install/crd/patches/oneof_in_authconfigs.yaml +++ b/install/crd/patches/oneof_in_authconfigs.yaml @@ -319,6 +319,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/when/items/oneOf @@ -337,6 +340,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authentication/additionalProperties/properties/when/items/oneOf @@ -355,6 +361,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/metadata/additionalProperties/properties/when/items/oneOf @@ -373,6 +382,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/authorization/additionalProperties/properties/when/items/oneOf @@ -391,6 +403,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/headers/additionalProperties/properties/when/items/oneOf @@ -409,6 +424,9 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] - op: add path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/response/properties/success/properties/dynamicMetadata/additionalProperties/properties/when/items/oneOf @@ -427,3 +445,6 @@ - properties: any: {} required: [any] + - properties: + predicate: {} + required: [predicate] diff --git a/install/manifests.yaml b/install/manifests.yaml index ece6c6bb..ef9eb525 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2922,6 +2922,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to be evaluated @@ -3464,6 +3468,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to be evaluated @@ -3657,6 +3665,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to be evaluated @@ -4324,6 +4336,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to be evaluated @@ -4543,6 +4559,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to @@ -4785,6 +4805,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to @@ -5052,6 +5076,10 @@ spec: any: {} required: - any + - properties: + predicate: {} + required: + - predicate properties: all: description: A list of pattern expressions to be evaluated as From 1bea62698b1e9a3485a87f17106cece0ee1ac03f Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Fri, 25 Oct 2024 07:46:46 -0400 Subject: [PATCH 28/43] Fix cel predicate Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index 76d7a470..eb767104 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -6,16 +6,6 @@ spec: hosts: - talker-api.127.0.0.1.nip.io - patterns: - admin-path: - - selector: context.request.http.path - operator: matches - value: ^/admin(/.*)?$ - resource-path: - - selector: context.request.http.path - operator: matches - value: ^/greetings/\d+$ - authentication: k8s-auth: kubernetesTokenReview: @@ -99,7 +89,7 @@ spec: expression: request.http.headers.authorization resource-info: when: - - patternRef: resource-path + - predicate: request.http.path.matches("^/greetings/\\d+$") uma: credentialsRef: name: talker-api-uma-credentials @@ -125,21 +115,21 @@ spec: } admin-kubernetes-rbac: when: - - patternRef: admin-path - - predicate: auth.identity.kubernetes-rbac + - predicate: request.http.path.matches("^/admin(/.*)?$") + - predicate: auth.identity["kubernetes-rbac"] kubernetesSubjectAccessReview: user: expression: auth.identity.username admin-jwt-rbac: when: - - patternRef: admin-path - - predicate: auth.identity.jwt-rbac + - predicate: request.http.path.matches("^/admin(/.*)?$") + - predicate: auth.identity["jwt-rbac"] patternMatching: patterns: - - predicate: auth.identity.roles.exists("admin") + - predicate: auth.identity.roles.contains("admin") resource-owner: when: - - patternRef: resource-path + - predicate: request.http.path.matches("^/greetings/\\d+$") opa: rego: | allow { From a51c18b9af7ab803858d656f44be183f8052106a Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 08:47:28 -0400 Subject: [PATCH 29/43] CEL in IdentityExtensions Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 32 +++++++++++++++++------ pkg/evaluators/identity_extension.go | 14 +++++----- pkg/evaluators/identity_extension_test.go | 24 ++++++++--------- pkg/evaluators/identity_test.go | 4 +-- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 9140e322..90977ba2 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -183,16 +183,32 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf for identityCfgName, identity := range authConfigIdentityConfigs { extendedProperties := make([]evaluators.IdentityExtension, len(identity.Defaults)+len(identity.Overrides)) for propertyName, property := range identity.Defaults { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ - Static: property.Value, - Pattern: property.Selector, - }, false)) + if property.Expression.Expression != "" { + if expression, err := cel.NewExpression(property.Expression.Expression); err == nil { + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, false)) + } else { + return nil, err + } + } else { + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ + Static: property.Value, + Pattern: property.Selector, + }, false)) + } } for propertyName, property := range identity.Overrides { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, json.JSONValue{ - Static: property.Value, - Pattern: property.Selector, - }, true)) + if property.Expression.Expression != "" { + if expression, err := cel.NewExpression(property.Expression.Expression); err == nil { + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, true)) + } else { + return nil, err + } + } else { + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ + Static: property.Value, + Pattern: property.Selector, + }, true)) + } } predicates, err := buildPredicates(authConfig, identity.Conditions, jsonexp.All) diff --git a/pkg/evaluators/identity_extension.go b/pkg/evaluators/identity_extension.go index bbea29fc..29c20f14 100644 --- a/pkg/evaluators/identity_extension.go +++ b/pkg/evaluators/identity_extension.go @@ -1,16 +1,18 @@ package evaluators import ( + "github.com/kuadrant/authorino/pkg/expressions" "github.com/kuadrant/authorino/pkg/json" ) -func NewIdentityExtension(name string, value json.JSONValue, overwrite bool) IdentityExtension { +func NewIdentityExtension(name string, value expressions.Value, overwrite bool) IdentityExtension { + property := json.JSONProperty{ + Name: name, + Value: value, + } return IdentityExtension{ - JSONProperty: json.JSONProperty{ - Name: name, - Value: &value, - }, - Overwrite: overwrite, + JSONProperty: property, + Overwrite: overwrite, } } diff --git a/pkg/evaluators/identity_extension_test.go b/pkg/evaluators/identity_extension_test.go index 922c1765..34ee0f2b 100644 --- a/pkg/evaluators/identity_extension_test.go +++ b/pkg/evaluators/identity_extension_test.go @@ -23,62 +23,62 @@ func TestResolveIdentityExtension(t *testing.T) { }{ { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, false), expected: "beth", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, false), expected: "foo", }, { name: "static value for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "static value for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Static: "foo"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Static: "foo"}, true), expected: "foo", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "beth", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, false), expected: "1234567890", }, { name: "existing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "existing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.sub"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.sub"}, true), expected: "1234567890", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "beth", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, false), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, false), expected: "", }, { name: "missing pattern for existing property without overwrite", - input: NewIdentityExtension("username", json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("username", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, { name: "missing pattern for missing property without overwrite", - input: NewIdentityExtension("uid", json.JSONValue{Pattern: "auth.identity.full_name"}, true), + input: NewIdentityExtension("uid", &json.JSONValue{Pattern: "auth.identity.full_name"}, true), expected: "", }, } diff --git a/pkg/evaluators/identity_test.go b/pkg/evaluators/identity_test.go index 8e16f6f2..39620adf 100644 --- a/pkg/evaluators/identity_test.go +++ b/pkg/evaluators/identity_test.go @@ -41,8 +41,8 @@ func TestIdentityConfig_ResolveExtendedProperties(t *testing.T) { Name: "test", KubernetesAuth: &identity.KubernetesAuth{}, ExtendedProperties: []IdentityExtension{ - NewIdentityExtension("prop1", json.JSONValue{Static: "value1"}, true), - NewIdentityExtension("prop2", json.JSONValue{Pattern: "auth.identity.sub"}, true), + NewIdentityExtension("prop1", &json.JSONValue{Static: "value1"}, true), + NewIdentityExtension("prop2", &json.JSONValue{Pattern: "auth.identity.sub"}, true), }, } From 86dc4955d359e814377a75173a0dc10c65e498a5 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 08:56:00 -0400 Subject: [PATCH 30/43] Fix null, not needed Signed-off-by: Alex Snaps --- pkg/evaluators/identity.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/evaluators/identity.go b/pkg/evaluators/identity.go index 01608d47..0bce9cbf 100644 --- a/pkg/evaluators/identity.go +++ b/pkg/evaluators/identity.go @@ -199,13 +199,11 @@ func (config *IdentityConfig) ResolveExtendedProperties(pipeline auth.AuthPipeli authJSON := pipeline.GetAuthorizationJSON() for _, extendedProperty := range config.ExtendedProperties { - if extendedProperty.Value != nil { - resolved, err := extendedProperty.ResolveFor(extendedIdentityObject, authJSON) - if err != nil { - return nil, err - } - extendedIdentityObject[extendedProperty.Name] = resolved + resolved, err := extendedProperty.ResolveFor(extendedIdentityObject, authJSON) + if err != nil { + return nil, err } + extendedIdentityObject[extendedProperty.Name] = resolved } return extendedIdentityObject, nil From 08cebd166667ee527e2e958eb5322083b628605d Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 09:22:51 -0400 Subject: [PATCH 31/43] Default to use v1beta3 in e2e tests Signed-off-by: Alex Snaps --- tests/e2e-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh index 3e502eed..fefb7f70 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -8,7 +8,7 @@ for cmd in realpath kubectl curl jq base64; do done namespace=${NAMESPACE:-"authorino"} -authconfig_version=${AUTHCONFIG_VERSION:-"v1beta2"} +authconfig_version=${AUTHCONFIG_VERSION:-"v1beta3"} authconfig=${AUTHCONFIG:-"$(dirname $(realpath $0))/${authconfig_version}/authconfig.yaml"} authconfig_invalid=${AUTHCONFIG_INVALID:-"$(dirname $(realpath $0))/${authconfig_version}/authconfig-invalid.yaml"} verbose=${VERBOSE} From eb153341d0af8e4d80d664ed79a64e50608fc47b Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 12:14:45 -0400 Subject: [PATCH 32/43] Updated predicates Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index eb767104..a45ed8d4 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -58,13 +58,13 @@ spec: expression: auth.identity.preferred_username cache: key: - expression: request.http.headers.authorization + expression: request.headers["authorization"] anonymous: anonymous: {} priority: 1 when: - - predicate: request.http.method == "GET" - - predicate: request.http.path.matches("^/$") + - predicate: request.method == "GET" + - predicate: request.path.matches("^/$") defaults: username: value: global @@ -86,17 +86,17 @@ spec: identitySource: keycloak cache: key: - expression: request.http.headers.authorization + expression: request.headers["authorization"] resource-info: when: - - predicate: request.http.path.matches("^/greetings/\\d+$") + - predicate: request.path.matches("^/greetings/\\d+$") uma: credentialsRef: name: talker-api-uma-credentials endpoint: http://keycloak.authorino.svc.cluster.local:8080/realms/kuadrant cache: key: - expression: request.http.path + expression: request.path authorization: allowed-methods: @@ -115,21 +115,21 @@ spec: } admin-kubernetes-rbac: when: - - predicate: request.http.path.matches("^/admin(/.*)?$") + - predicate: request.path.matches("^/admin(/.*)?$") - predicate: auth.identity["kubernetes-rbac"] kubernetesSubjectAccessReview: user: expression: auth.identity.username admin-jwt-rbac: when: - - predicate: request.http.path.matches("^/admin(/.*)?$") + - predicate: request.path.matches("^/admin(/.*)?$") - predicate: auth.identity["jwt-rbac"] patternMatching: patterns: - predicate: auth.identity.roles.contains("admin") resource-owner: when: - - predicate: request.http.path.matches("^/greetings/\\d+$") + - predicate: request.path.matches("^/greetings/\\d+$") opa: rego: | allow { @@ -166,7 +166,7 @@ spec: username: expression: auth.identity.username geo: - expression: auth.metadata.geo-info + expression: auth.metadata["geo-info"] timestamp: expression: auth.authorization.timestamp.now wristband: @@ -177,7 +177,7 @@ spec: username: expression: auth.identity.username uri: - expression: request.http.path + expression: request.path scope: selector: request.http.method.@case:lower signingKeyRefs: From 2f5d43ea5174516ead385e76571e2f4cbcc5df8f Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 15:11:45 -0400 Subject: [PATCH 33/43] Like I need to understand this crap Signed-off-by: Alex Snaps --- api/v1beta3/auth_config_types.go | 10 ++++---- api/v1beta3/zz_generated.deepcopy.go | 19 --------------- controllers/auth_config_controller.go | 24 +++++++++---------- .../authorino.kuadrant.io_authconfigs.yaml | 12 +++++----- install/manifests.yaml | 12 +++++----- 5 files changed, 28 insertions(+), 49 deletions(-) diff --git a/api/v1beta3/auth_config_types.go b/api/v1beta3/auth_config_types.go index db2bbb2d..31424185 100644 --- a/api/v1beta3/auth_config_types.go +++ b/api/v1beta3/auth_config_types.go @@ -164,9 +164,7 @@ type PatternExpression struct { Value string `json:"value,omitempty"` } -type CelExpression struct { - Expression string `json:"expression,omitempty"` -} +type CelExpression string type CelPredicate struct { Predicate string `json:"predicate,omitempty"` @@ -208,7 +206,7 @@ type ValueOrSelector struct { // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector,omitempty"` - Expression CelExpression `json:",omitempty"` + Expression CelExpression `json:"expression,omitempty"` } type CommonEvaluatorSpec struct { @@ -413,7 +411,7 @@ type PlainIdentitySpec struct { // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. Selector string `json:"selector"` - Expression CelExpression `json:",omitempty"` + Expression CelExpression `json:"expression,omitempty"` } type AnonymousAccessSpec struct{} @@ -451,7 +449,7 @@ type HttpEndpointSpec struct { // E.g. https://ext-auth-server.io/metadata?p={request.path} Url string `json:"url"` - UrlExpression CelExpression `json:",omitempty"` + UrlExpression CelExpression `json:"urlExpression,omitempty"` // HTTP verb used in the request to the service. Accepted values: GET (default), POST. // When the request method is POST, the authorization JSON is passed in the body of the request. diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index 1f89a632..cbc7f560 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -481,21 +481,6 @@ 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 @@ -697,7 +682,6 @@ func (in *HeaderSuccessResponseSpec) DeepCopy() *HeaderSuccessResponseSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HttpEndpointSpec) DeepCopyInto(out *HttpEndpointSpec) { *out = *in - out.UrlExpression = in.UrlExpression if in.Method != nil { in, out := &in.Method, &out.Method *out = new(HttpMethod) @@ -1116,7 +1100,6 @@ 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. @@ -1132,7 +1115,6 @@ func (in *PlainAuthResponseSpec) DeepCopy() *PlainAuthResponseSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PlainIdentitySpec) DeepCopyInto(out *PlainIdentitySpec) { *out = *in - out.Expression = in.Expression } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlainIdentitySpec. @@ -1321,7 +1303,6 @@ 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 90977ba2..eb358ce5 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -183,8 +183,8 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf for identityCfgName, identity := range authConfigIdentityConfigs { extendedProperties := make([]evaluators.IdentityExtension, len(identity.Defaults)+len(identity.Overrides)) for propertyName, property := range identity.Defaults { - if property.Expression.Expression != "" { - if expression, err := cel.NewExpression(property.Expression.Expression); err == nil { + if property.Expression != "" { + if expression, err := cel.NewExpression(string(property.Expression)); err == nil { extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, false)) } else { return nil, err @@ -197,8 +197,8 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf } } for propertyName, property := range identity.Overrides { - if property.Expression.Expression != "" { - if expression, err := cel.NewExpression(property.Expression.Expression); err == nil { + if property.Expression != "" { + if expression, err := cel.NewExpression(string(property.Expression)); err == nil { extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, true)) } else { return nil, err @@ -298,12 +298,12 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf } case api.PlainIdentityAuthentication: - if identity.Plain.Expression.Expression != "" { - expression, err := cel.NewStringExpression(identity.Plain.Expression.Expression) + if identity.Plain.Expression != "" { + expression, err := cel.NewStringExpression(string(identity.Plain.Expression)) if err != nil { return nil, err } - translatedIdentity.Plain = &identity_evaluators.Plain{Value: expression, Pattern: identity.Plain.Expression.Expression} + translatedIdentity.Plain = &identity_evaluators.Plain{Value: expression, Pattern: string(identity.Plain.Expression)} } else { translatedIdentity.Plain = &identity_evaluators.Plain{Value: &json.JSONValue{Pattern: identity.Plain.Selector}, Pattern: identity.Plain.Selector} } @@ -921,8 +921,8 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht } var dynamicEndpoint expressions.Value - if http.UrlExpression.Expression != "" { - endpoint, err := cel.NewStringExpression(http.UrlExpression.Expression) + if http.UrlExpression != "" { + endpoint, err := cel.NewStringExpression(string(http.UrlExpression)) if err != nil { return nil, err } else { @@ -1074,9 +1074,9 @@ func getJsonFromStaticDynamic(value *api.ValueOrSelector) (expressions.Value, er if value == nil { return nil, nil } - - if value.Expression.Expression != "" { - return cel.NewExpression(value.Expression.Expression) + expression := string(value.Expression) + if expression != "" { + return cel.NewExpression(expression) } return &json.JSONValue{ diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 6f03a31d..5d7f18c3 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -3011,8 +3011,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -3125,6 +3123,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object @@ -3477,8 +3477,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -3587,6 +3585,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object @@ -3780,8 +3780,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -3890,6 +3888,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object diff --git a/install/manifests.yaml b/install/manifests.yaml index ef9eb525..d8ec8689 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -3319,8 +3319,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -3433,6 +3431,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object @@ -3833,8 +3833,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -3943,6 +3941,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object @@ -4149,8 +4149,6 @@ spec: - name type: object type: object - expression: - type: string headers: additionalProperties: properties: @@ -4259,6 +4257,8 @@ spec: by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. E.g. https://ext-auth-server.io/metadata?p={request.path} type: string + urlExpression: + type: string required: - url type: object From 3d1e1b864e8bc67985c5d5e0b64597e9d4b0b51e Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 17:10:17 -0400 Subject: [PATCH 34/43] All the things... Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 184 +++++++++++++++++--------- pkg/expressions/cel/expressions.go | 9 +- 2 files changed, 125 insertions(+), 68 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index eb358ce5..394aa575 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -181,33 +181,19 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf } for identityCfgName, identity := range authConfigIdentityConfigs { - extendedProperties := make([]evaluators.IdentityExtension, len(identity.Defaults)+len(identity.Overrides)) + extendedProperties := make([]evaluators.IdentityExtension, 0) for propertyName, property := range identity.Defaults { - if property.Expression != "" { - if expression, err := cel.NewExpression(string(property.Expression)); err == nil { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, false)) - } else { - return nil, err - } + if value, err := stringValueFrom(&property); err != nil { + return nil, err } else { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ - Static: property.Value, - Pattern: property.Selector, - }, false)) + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, value, false)) } } for propertyName, property := range identity.Overrides { - if property.Expression != "" { - if expression, err := cel.NewExpression(string(property.Expression)); err == nil { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, expression, true)) - } else { - return nil, err - } + if value, err := stringValueFrom(&property); err != nil { + return nil, err } else { - extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, &json.JSONValue{ - Static: property.Value, - Pattern: property.Selector, - }, true)) + extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, value, true)) } } @@ -475,23 +461,49 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf case api.KubernetesSubjectAccessReviewAuthorization: user := authorization.KubernetesSubjectAccessReview.User - authorinoUser := json.JSONValue{Static: user.Value, Pattern: user.Selector} + authorinoUser, err := stringValueFrom(user) + if err != nil { + return nil, err + } var authorinoResourceAttributes *authorization_evaluators.KubernetesAuthzResourceAttributes resourceAttributes := authorization.KubernetesSubjectAccessReview.ResourceAttributes if resourceAttributes != nil { + namespace, err := stringValueFrom(&resourceAttributes.Namespace) + if err != nil { + return nil, err + } + group, err := stringValueFrom(&resourceAttributes.Group) + if err != nil { + return nil, err + } + resource, err := stringValueFrom(&resourceAttributes.Resource) + if err != nil { + return nil, err + } + name, err := stringValueFrom(&resourceAttributes.Name) + if err != nil { + return nil, err + } + subResource, err := stringValueFrom(&resourceAttributes.SubResource) + if err != nil { + return nil, err + } + verb, err := stringValueFrom(&resourceAttributes.Verb) + if err != nil { + return nil, err + } authorinoResourceAttributes = &authorization_evaluators.KubernetesAuthzResourceAttributes{ - Namespace: &json.JSONValue{Static: resourceAttributes.Namespace.Value, Pattern: resourceAttributes.Namespace.Selector}, - Group: &json.JSONValue{Static: resourceAttributes.Group.Value, Pattern: resourceAttributes.Group.Selector}, - Resource: &json.JSONValue{Static: resourceAttributes.Resource.Value, Pattern: resourceAttributes.Resource.Selector}, - Name: &json.JSONValue{Static: resourceAttributes.Name.Value, Pattern: resourceAttributes.Name.Selector}, - SubResource: &json.JSONValue{Static: resourceAttributes.SubResource.Value, Pattern: resourceAttributes.SubResource.Selector}, - Verb: &json.JSONValue{Static: resourceAttributes.Verb.Value, Pattern: resourceAttributes.Verb.Selector}, + Namespace: namespace, + Group: group, + Resource: resource, + Name: name, + SubResource: subResource, + Verb: verb, } } - var err error - translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(&authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoResourceAttributes) + translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoResourceAttributes) if err != nil { return nil, err } @@ -649,6 +661,32 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf return translatedAuthConfig, nil } +func stringValueFrom(user *api.ValueOrSelector) (expressions.Value, error) { + var strValue expressions.Value + var err error + if user.Expression != "" { + if strValue, err = cel.NewStringExpression(string(user.Expression)); err != nil { + return nil, err + } + } else { + strValue = &json.JSONValue{Static: user.Value, Pattern: user.Selector} + } + return strValue, nil +} + +func valueFrom(user *api.ValueOrSelector) (expressions.Value, error) { + var strValue expressions.Value + var err error + if user.Expression != "" { + if strValue, err = cel.NewExpression(string(user.Expression)); err != nil { + return nil, err + } + } else { + strValue = &json.JSONValue{Static: user.Value, Pattern: user.Selector} + } + return strValue, nil +} + func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, successResponse api.SuccessResponseSpec, r *AuthConfigReconciler, translatedResponse *evaluators.ResponseConfig) error { switch successResponse.GetMethod() { // wristband @@ -679,13 +717,14 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe customClaims := make([]json.JSONProperty, 0) for claimName, claim := range wristband.CustomClaims { - customClaims = append(customClaims, json.JSONProperty{ - Name: claimName, - Value: &json.JSONValue{ - Static: claim.Value, - Pattern: claim.Selector, - }, - }) + if value, err := stringValueFrom(&claim); err != nil { + return err + } else { + customClaims = append(customClaims, json.JSONProperty{ + Name: claimName, + Value: value, + }) + } } if authorinoWristband, err := response_evaluators.NewWristbandConfig( @@ -704,24 +743,27 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe jsonProperties := make([]json.JSONProperty, 0) for propertyName, property := range successResponse.Json.Properties { - jsonProperties = append(jsonProperties, json.JSONProperty{ - Name: propertyName, - Value: &json.JSONValue{ - Static: property.Value, - Pattern: property.Selector, - }, - }) + if value, err := valueFrom(&property); err != nil { + return err + } else { + jsonProperties = append(jsonProperties, json.JSONProperty{ + Name: propertyName, + Value: value, + }) + + } } translatedResponse.DynamicJSON = response_evaluators.NewDynamicJSONResponse(jsonProperties) // plain case api.PlainAuthResponse: - translatedResponse.Plain = &response_evaluators.Plain{ - Value: &json.JSONValue{ - Static: successResponse.Plain.Value, - Pattern: successResponse.Plain.Selector, - }, + if value, err := stringValueFrom((*api.ValueOrSelector)(successResponse.Plain)); err != nil { + return err + } else { + translatedResponse.Plain = &response_evaluators.Plain{ + Value: value, + } } case api.UnknownAuthResponseMethod: @@ -888,31 +930,37 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht oauth2TokenForceFetch = oauth2Config.Cache != nil && !*oauth2Config.Cache } - var body *json.JSONValue + var body expressions.Value if b := http.Body; b != nil { - body = &json.JSONValue{Static: b.Value, Pattern: b.Selector} + if value, err := stringValueFrom(b); err != nil { + return nil, err + } else { + body = value + } } params := make([]json.JSONProperty, 0, len(http.Parameters)) for name, param := range http.Parameters { - params = append(params, json.JSONProperty{ - Name: name, - Value: &json.JSONValue{ - Static: param.Value, - Pattern: param.Selector, - }, - }) + if value, err := valueFrom(¶m); err != nil { + return nil, err + } else { + params = append(params, json.JSONProperty{ + Name: name, + Value: value, + }) + } } headers := make([]json.JSONProperty, 0, len(http.Headers)) for name, header := range http.Headers { - headers = append(headers, json.JSONProperty{ - Name: name, - Value: &json.JSONValue{ - Static: header.Value, - Pattern: header.Selector, - }, - }) + if value, err := valueFrom(&header); err != nil { + return nil, err + } else { + headers = append(headers, json.JSONProperty{ + Name: name, + Value: value, + }) + } } method := "GET" @@ -1051,7 +1099,11 @@ func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) (*evaluators.D headers := make([]json.JSONProperty, 0, len(denyWithSpec.Headers)) for name, header := range denyWithSpec.Headers { - headers = append(headers, json.JSONProperty{Name: name, Value: &json.JSONValue{Static: header.Value, Pattern: header.Selector}}) + if value, err := stringValueFrom(&header); err != nil { + return nil, err + } else { + headers = append(headers, json.JSONProperty{Name: name, Value: value}) + } } message, err := getJsonFromStaticDynamic(denyWithSpec.Message) diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 32c9d50b..18ba5728 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -111,9 +111,14 @@ func (e *Expression) EvaluateStringValue(json string) (string, error) { if result, _, err := e.Evaluate(json); err != nil { return "", err } else if !reflect.DeepEqual(result.Type(), cel.StringType) { - return "", err + toJSON, err := ValueToJSON(result) + return toJSON, err } else { - return result.Value().(string), nil + str, err := result.ConvertToNative(reflect.TypeOf("")) + if err != nil { + return "", err + } + return str.(string), nil } } From 5f94c7cf77b1b4f7cedbd5395a9a56b23c0ee4ba Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 19:29:33 -0400 Subject: [PATCH 35/43] Dunno where this comes from Signed-off-by: Alex Snaps --- controllers/auth_config_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/auth_config_controller_test.go b/controllers/auth_config_controller_test.go index bd8bd803..c4a990d1 100644 --- a/controllers/auth_config_controller_test.go +++ b/controllers/auth_config_controller_test.go @@ -168,7 +168,7 @@ func TestReconcileAuthConfigOk(t *testing.T) { config := authConfigIndex.Get("echo-api") assert.Check(t, config != nil) idConfig, _ := config.IdentityConfigs[0].(*evaluators.IdentityConfig) - assert.Equal(t, idConfig.ExtendedProperties[1].Name, "source") + assert.Equal(t, idConfig.ExtendedProperties[0].Name, "source") // TODO(@guicassolato): assert other fields of the AuthConfig } From b7cc27f924acec8de96fe107277558b073a5d5c1 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 28 Oct 2024 19:30:49 -0400 Subject: [PATCH 36/43] Neither where this is supposed to come from Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index a45ed8d4..2c7e4430 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -39,7 +39,7 @@ spec: roles: expression: auth.identity.realm_access.roles username: - expression: auth.identity.preferred_username + expression: auth.identity["preferred_username"] oauth2-introspection: oauth2Introspection: credentialsRef: @@ -55,7 +55,7 @@ spec: roles: expression: auth.identity.realm_access.roles username: - expression: auth.identity.preferred_username + expression: auth.identity["preferred_username"] cache: key: expression: request.headers["authorization"] From 44052a621a3adc35df1a4f3d820d70a6c7220587 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 29 Oct 2024 10:37:47 -0400 Subject: [PATCH 37/43] Exploring while I can Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index 2c7e4430..509b93fe 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -39,7 +39,7 @@ spec: roles: expression: auth.identity.realm_access.roles username: - expression: auth.identity["preferred_username"] + expression: "has(auth.identity.preferred_username) ? auth.identity.preferred_username : 'unknown'" oauth2-introspection: oauth2Introspection: credentialsRef: @@ -55,7 +55,7 @@ spec: roles: expression: auth.identity.realm_access.roles username: - expression: auth.identity["preferred_username"] + expression: "has(auth.identity.preferred_username) ? auth.identity.preferred_username : 'unknown'" cache: key: expression: request.headers["authorization"] From 43241344e318e4829cc52b9418cff41c8cf219e9 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 29 Oct 2024 12:05:55 -0400 Subject: [PATCH 38/43] CEL Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 4 ++-- pkg/expressions/cel/expressions.go | 6 +----- tests/v1beta3/authconfig.yaml | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index 394aa575..a646598c 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -678,7 +678,7 @@ func valueFrom(user *api.ValueOrSelector) (expressions.Value, error) { var strValue expressions.Value var err error if user.Expression != "" { - if strValue, err = cel.NewExpression(string(user.Expression)); err != nil { + if strValue, err = cel.NewStringExpression(string(user.Expression)); err != nil { return nil, err } } else { @@ -1128,7 +1128,7 @@ func getJsonFromStaticDynamic(value *api.ValueOrSelector) (expressions.Value, er } expression := string(value.Expression) if expression != "" { - return cel.NewExpression(expression) + return cel.NewStringExpression(expression) } return &json.JSONValue{ diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 18ba5728..1fb22bf6 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -87,11 +87,7 @@ func (e *Expression) ResolveFor(json string) (interface{}, error) { return nil, err } - if jsonVal, err := ValueToJSON(result); err != nil { - return nil, err - } else { - return jsonVal, nil - } + return ValueToJSON(result) } func (e *StringExpression) ResolveFor(json string) (interface{}, error) { diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index 509b93fe..b4d86c14 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -37,7 +37,7 @@ spec: jwt-rbac: value: true roles: - expression: auth.identity.realm_access.roles + expression: "has(auth.identity.realm_access) ? auth.identity.realm_access.roles : []" username: expression: "has(auth.identity.preferred_username) ? auth.identity.preferred_username : 'unknown'" oauth2-introspection: @@ -53,7 +53,7 @@ spec: jwt-rbac: value: true roles: - expression: auth.identity.realm_access.roles + expression: "has(auth.identity.realm_access) ? auth.identity.realm_access.roles : []" username: expression: "has(auth.identity.preferred_username) ? auth.identity.preferred_username : 'unknown'" cache: From dae86834940f61dc94fafa45a86a31d49fb156ed Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Wed, 30 Oct 2024 13:36:37 +0100 Subject: [PATCH 39/43] optional selector field Signed-off-by: Guilherme Cassolato --- api/v1beta3/auth_config_types.go | 4 ++-- install/crd/authorino.kuadrant.io_authconfigs.yaml | 8 -------- install/manifests.yaml | 8 -------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/api/v1beta3/auth_config_types.go b/api/v1beta3/auth_config_types.go index 31424185..53a9eae7 100644 --- a/api/v1beta3/auth_config_types.go +++ b/api/v1beta3/auth_config_types.go @@ -409,7 +409,7 @@ type PlainIdentitySpec struct { // 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}!"). // Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. // The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. - Selector string `json:"selector"` + Selector string `json:"selector,omitempty"` Expression CelExpression `json:"expression,omitempty"` } @@ -447,7 +447,7 @@ type HttpEndpointSpec struct { // The value can include variable placeholders in the format "{selector}", where "selector" is any pattern supported // by https://pkg.go.dev/github.com/tidwall/gjson and selects value from the authorization JSON. // E.g. https://ext-auth-server.io/metadata?p={request.path} - Url string `json:"url"` + Url string `json:"url,omitempty"` UrlExpression CelExpression `json:"urlExpression,omitempty"` diff --git a/install/crd/authorino.kuadrant.io_authconfigs.yaml b/install/crd/authorino.kuadrant.io_authconfigs.yaml index 5d7f18c3..c658c8f8 100644 --- a/install/crd/authorino.kuadrant.io_authconfigs.yaml +++ b/install/crd/authorino.kuadrant.io_authconfigs.yaml @@ -2620,8 +2620,6 @@ spec: Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. type: string - required: - - selector type: object priority: default: 0 @@ -3125,8 +3123,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object rego: description: |- @@ -3587,8 +3583,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object metrics: default: false @@ -3890,8 +3884,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object metrics: default: false diff --git a/install/manifests.yaml b/install/manifests.yaml index d8ec8689..a0ed239b 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -2887,8 +2887,6 @@ spec: Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip. type: string - required: - - selector type: object priority: default: 0 @@ -3433,8 +3431,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object rego: description: |- @@ -3943,8 +3939,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object metrics: default: false @@ -4259,8 +4253,6 @@ spec: type: string urlExpression: type: string - required: - - url type: object metrics: default: false From 4610145a7f9c516b4c5e5408536437954db7b6b1 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 30 Oct 2024 09:13:51 -0400 Subject: [PATCH 40/43] Store data as golang types Signed-off-by: Alex Snaps --- controllers/auth_config_controller.go | 47 +++------ install/manifests.yaml | 146 ++++++++++++++++++++++++++ install/rbac/role.yaml | 146 ++++++++++++++++++++++++++ pkg/expressions/cel/expressions.go | 40 ++----- 4 files changed, 316 insertions(+), 63 deletions(-) diff --git a/controllers/auth_config_controller.go b/controllers/auth_config_controller.go index a646598c..5dbce879 100644 --- a/controllers/auth_config_controller.go +++ b/controllers/auth_config_controller.go @@ -183,14 +183,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf for identityCfgName, identity := range authConfigIdentityConfigs { extendedProperties := make([]evaluators.IdentityExtension, 0) for propertyName, property := range identity.Defaults { - if value, err := stringValueFrom(&property); err != nil { + if value, err := valueFrom(&property); err != nil { return nil, err } else { extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, value, false)) } } for propertyName, property := range identity.Overrides { - if value, err := stringValueFrom(&property); err != nil { + if value, err := valueFrom(&property); err != nil { return nil, err } else { extendedProperties = append(extendedProperties, evaluators.NewIdentityExtension(propertyName, value, true)) @@ -285,7 +285,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf case api.PlainIdentityAuthentication: if identity.Plain.Expression != "" { - expression, err := cel.NewStringExpression(string(identity.Plain.Expression)) + expression, err := cel.NewExpression(string(identity.Plain.Expression)) if err != nil { return nil, err } @@ -461,7 +461,7 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf case api.KubernetesSubjectAccessReviewAuthorization: user := authorization.KubernetesSubjectAccessReview.User - authorinoUser, err := stringValueFrom(user) + authorinoUser, err := valueFrom(user) if err != nil { return nil, err } @@ -469,27 +469,27 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf var authorinoResourceAttributes *authorization_evaluators.KubernetesAuthzResourceAttributes resourceAttributes := authorization.KubernetesSubjectAccessReview.ResourceAttributes if resourceAttributes != nil { - namespace, err := stringValueFrom(&resourceAttributes.Namespace) + namespace, err := valueFrom(&resourceAttributes.Namespace) if err != nil { return nil, err } - group, err := stringValueFrom(&resourceAttributes.Group) + group, err := valueFrom(&resourceAttributes.Group) if err != nil { return nil, err } - resource, err := stringValueFrom(&resourceAttributes.Resource) + resource, err := valueFrom(&resourceAttributes.Resource) if err != nil { return nil, err } - name, err := stringValueFrom(&resourceAttributes.Name) + name, err := valueFrom(&resourceAttributes.Name) if err != nil { return nil, err } - subResource, err := stringValueFrom(&resourceAttributes.SubResource) + subResource, err := valueFrom(&resourceAttributes.SubResource) if err != nil { return nil, err } - verb, err := stringValueFrom(&resourceAttributes.Verb) + verb, err := valueFrom(&resourceAttributes.Verb) if err != nil { return nil, err } @@ -661,24 +661,11 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf return translatedAuthConfig, nil } -func stringValueFrom(user *api.ValueOrSelector) (expressions.Value, error) { - var strValue expressions.Value - var err error - if user.Expression != "" { - if strValue, err = cel.NewStringExpression(string(user.Expression)); err != nil { - return nil, err - } - } else { - strValue = &json.JSONValue{Static: user.Value, Pattern: user.Selector} - } - return strValue, nil -} - func valueFrom(user *api.ValueOrSelector) (expressions.Value, error) { var strValue expressions.Value var err error if user.Expression != "" { - if strValue, err = cel.NewStringExpression(string(user.Expression)); err != nil { + if strValue, err = cel.NewExpression(string(user.Expression)); err != nil { return nil, err } } else { @@ -717,7 +704,7 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe customClaims := make([]json.JSONProperty, 0) for claimName, claim := range wristband.CustomClaims { - if value, err := stringValueFrom(&claim); err != nil { + if value, err := valueFrom(&claim); err != nil { return err } else { customClaims = append(customClaims, json.JSONProperty{ @@ -758,7 +745,7 @@ func injectResponseConfig(ctx context.Context, authConfig *api.AuthConfig, succe // plain case api.PlainAuthResponse: - if value, err := stringValueFrom((*api.ValueOrSelector)(successResponse.Plain)); err != nil { + if value, err := valueFrom((*api.ValueOrSelector)(successResponse.Plain)); err != nil { return err } else { translatedResponse.Plain = &response_evaluators.Plain{ @@ -932,7 +919,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht var body expressions.Value if b := http.Body; b != nil { - if value, err := stringValueFrom(b); err != nil { + if value, err := valueFrom(b); err != nil { return nil, err } else { body = value @@ -970,7 +957,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht var dynamicEndpoint expressions.Value if http.UrlExpression != "" { - endpoint, err := cel.NewStringExpression(string(http.UrlExpression)) + endpoint, err := cel.NewExpression(string(http.UrlExpression)) if err != nil { return nil, err } else { @@ -1099,7 +1086,7 @@ func buildAuthorinoDenyWithValues(denyWithSpec *api.DenyWithSpec) (*evaluators.D headers := make([]json.JSONProperty, 0, len(denyWithSpec.Headers)) for name, header := range denyWithSpec.Headers { - if value, err := stringValueFrom(&header); err != nil { + if value, err := valueFrom(&header); err != nil { return nil, err } else { headers = append(headers, json.JSONProperty{Name: name, Value: value}) @@ -1128,7 +1115,7 @@ func getJsonFromStaticDynamic(value *api.ValueOrSelector) (expressions.Value, er } expression := string(value.Expression) if expression != "" { - return cel.NewStringExpression(expression) + return cel.NewExpression(expression) } return &json.JSONValue{ diff --git a/install/manifests.yaml b/install/manifests.yaml index a0ed239b..f3aa8e3c 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -5274,6 +5274,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: @@ -5294,6 +5368,12 @@ rules: - get - patch - update +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create - apiGroups: - coordination.k8s.io resources: @@ -5311,3 +5391,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/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index 1fb22bf6..fc1941a5 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -2,6 +2,7 @@ package cel import ( "fmt" + "github.com/tidwall/gjson" "reflect" "strings" @@ -68,30 +69,18 @@ func NewExpression(source string) (*Expression, error) { }, nil } -func NewStringExpression(source string) (*StringExpression, error) { - program, err := Compile(source, cel.StringType) - if err != nil { - return nil, err - } - return &StringExpression{ - expression: Expression{ - program: program, - source: source, - }, - }, nil -} - func (e *Expression) ResolveFor(json string) (interface{}, error) { result, _, err := e.Evaluate(json) if err != nil { return nil, err } - return ValueToJSON(result) -} - -func (e *StringExpression) ResolveFor(json string) (interface{}, error) { - return e.expression.EvaluateStringValue(json) + // this is for backwards compatibility with JSONValue, these should interoperate seamlessly this way + if jsonLiteral, err := ValueToJSON(result); err != nil { + return nil, err + } else { + return gjson.Parse(jsonLiteral).Value(), nil + } } func (e *Expression) Evaluate(json string) (ref.Val, *cel.EvalDetails, error) { @@ -103,21 +92,6 @@ func (e *Expression) Evaluate(json string) (ref.Val, *cel.EvalDetails, error) { return e.program.Eval(input) } -func (e *Expression) EvaluateStringValue(json string) (string, error) { - if result, _, err := e.Evaluate(json); err != nil { - return "", err - } else if !reflect.DeepEqual(result.Type(), cel.StringType) { - toJSON, err := ValueToJSON(result) - return toJSON, err - } else { - str, err := result.ConvertToNative(reflect.TypeOf("")) - if err != nil { - return "", err - } - return str.(string), nil - } -} - func Compile(expression string, expectedType *cel.Type, opts ...cel.EnvOption) (cel.Program, error) { envOpts := append([]cel.EnvOption{cel.Declarations( decls.NewConst(RootMetadataBinding, decls.NewObjectType("google.protobuf.Struct"), nil), From 74ee28b0f000fad14c80020c15e51118f6a84f41 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 30 Oct 2024 09:17:52 -0400 Subject: [PATCH 41/43] Sort imports Signed-off-by: Alex Snaps --- pkg/expressions/cel/expressions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/expressions/cel/expressions.go b/pkg/expressions/cel/expressions.go index fc1941a5..c75959fe 100644 --- a/pkg/expressions/cel/expressions.go +++ b/pkg/expressions/cel/expressions.go @@ -2,7 +2,6 @@ package cel import ( "fmt" - "github.com/tidwall/gjson" "reflect" "strings" @@ -10,6 +9,7 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types/ref" + "github.com/tidwall/gjson" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" From c4a28985b2bdca9acb17baee883df2bf7057c90c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 30 Oct 2024 09:32:55 -0400 Subject: [PATCH 42/43] Test counters Signed-off-by: Alex Snaps --- install/manifests.yaml | 146 ----------------------------------------- install/rbac/role.yaml | 146 ----------------------------------------- tests/e2e-test.sh | 11 ++++ 3 files changed, 11 insertions(+), 292 deletions(-) diff --git a/install/manifests.yaml b/install/manifests.yaml index f3aa8e3c..a0ed239b 100644 --- a/install/manifests.yaml +++ b/install/manifests.yaml @@ -5274,80 +5274,6 @@ 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: @@ -5368,12 +5294,6 @@ rules: - get - patch - update -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - apiGroups: - coordination.k8s.io resources: @@ -5391,69 +5311,3 @@ 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 2328df39..69520e9e 100644 --- a/install/rbac/role.yaml +++ b/install/rbac/role.yaml @@ -4,80 +4,6 @@ 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: @@ -98,12 +24,6 @@ rules: - get - patch - update -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - apiGroups: - coordination.k8s.io resources: @@ -121,69 +41,3 @@ 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/tests/e2e-test.sh b/tests/e2e-test.sh index fefb7f70..fb071730 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -204,6 +204,7 @@ send_k8s_sa_requests $IP_IN "app-1-sa" " GET /admin => 200 GET /greetings/1 => 403" +# Test #5 done send_k8s_sa_requests $IP_IN "app-2-sa" " GET / => 200 POST / => 200 @@ -211,6 +212,7 @@ send_k8s_sa_requests $IP_IN "app-2-sa" " GET /admin => 403 GET /greetings/1 => 403" +# Test #10 done send_api_key_requests $IP_IN "ndyBzreUzF4zqDQsqSPMHkRhriEOtcRx" " GET / => 200 POST / => 200 @@ -218,6 +220,7 @@ send_api_key_requests $IP_IN "ndyBzreUzF4zqDQsqSPMHkRhriEOtcRx" " GET /admin => 200 GET /greetings/1 => 403" +# Test #15 done send_api_key_requests $IP_IN "pR2zLorYFIYOE4LLiQAWMPIRei1YgRBy" " GET / => 200 POST / => 200 @@ -225,14 +228,17 @@ send_api_key_requests $IP_IN "pR2zLorYFIYOE4LLiQAWMPIRei1YgRBy" " GET /admin => 403 GET /greetings/1 => 403" +# Test #20 done kubectl -n $namespace delete secret/alice-api-key 2>/dev/null >/dev/null && sleep 1 send_api_key_requests $IP_IN "pR2zLorYFIYOE4LLiQAWMPIRei1YgRBy" " POST / => 401" +# Test #21 done send_api_key_requests $IP_IN "ndyBzreUzF4zqDQsqSPMHkRhriEOtcRx" " POST / => 200" +# Test #22 done send_oidc_requests $IP_IN "john" "p" " GET / => 200 POST / => 200 @@ -240,6 +246,7 @@ send_oidc_requests $IP_IN "john" "p" " GET /admin => 403 GET /greetings/1 => 200" +# Test #27 done send_oidc_requests $IP_IN "jane" "p" " GET / => 200 POST / => 200 @@ -247,6 +254,7 @@ send_oidc_requests $IP_IN "jane" "p" " GET /admin => 200 GET /greetings/1 => 403" +# Test #32 done send_oauth_opaque_requests $IP_IN "peter" "p" " GET / => 200 POST / => 200 @@ -254,6 +262,7 @@ send_oauth_opaque_requests $IP_IN "peter" "p" " GET /admin => 403 GET /greetings/1 => 403" +# Test #37 done send_anonymous_requests $IP_IN " GET / => 200 POST / => 401 @@ -261,9 +270,11 @@ send_anonymous_requests $IP_IN " GET /admin => 401 GET /greetings/1 => 401" +# Test #42 done send_anonymous_requests $IP_OUT " GET / => 403" +# Test #43 done send_requests "https" "authorino-authorino-oidc" "8083" $IP_IN "" " GET /authorino/e2e-test/wristband/.well-known/openid-configuration => 200 GET /authorino/e2e-test/wristband/.well-known/openid-connect/certs => 200 From baa37c35121703835d969b4a662120c418bd8672 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 30 Oct 2024 09:58:27 -0400 Subject: [PATCH 43/43] Fix CEL Signed-off-by: Alex Snaps --- tests/v1beta3/authconfig.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v1beta3/authconfig.yaml b/tests/v1beta3/authconfig.yaml index b4d86c14..7e783002 100644 --- a/tests/v1beta3/authconfig.yaml +++ b/tests/v1beta3/authconfig.yaml @@ -126,7 +126,7 @@ spec: - predicate: auth.identity["jwt-rbac"] patternMatching: patterns: - - predicate: auth.identity.roles.contains("admin") + - predicate: auth.identity.roles.exists(r, r == "admin") resource-owner: when: - predicate: request.path.matches("^/greetings/\\d+$")