Skip to content

Commit

Permalink
First seam: DynamicCEL
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Snaps <alex@wcgw.dev>
  • Loading branch information
alexsnaps committed Oct 14, 2024
1 parent 210e894 commit 26918f8
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 0 deletions.
3 changes: 3 additions & 0 deletions api/v1beta2/auth_config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -733,6 +734,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"`
Expand Down
7 changes: 7 additions & 0 deletions controllers/auth_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Check failure on line 632 in controllers/auth_config_controller.go

View workflow job for this annotation

GitHub Actions / Unit Tests (1.21.x, ubuntu-latest)

invalid operation: cannot indirect successResponse.Expression (variable of type string)
return err
} else {
translatedResponse.DynamicCEL = exp
}

// plain
case api.PlainAuthResponse:
translatedResponse.Plain = &response_evaluators.Plain{
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand Down
1 change: 1 addition & 0 deletions pkg/evaluators/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down
89 changes: 89 additions & 0 deletions pkg/evaluators/response/dynamic_cel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package response

Check failure on line 1 in pkg/evaluators/response/dynamic_cel.go

View workflow job for this annotation

GitHub Actions / Auto-format and Check (goimports)

Please run goimports . diff --git a/pkg/evaluators/response/dynamic_cel.go b/pkg/evaluators/response/dynamic_cel.go index f22a379..e9192f4 100644 --- a/pkg/evaluators/response/dynamic_cel.go +++ b/pkg/evaluators/response/dynamic_cel.go @@ -2,14 +2,15 @@ 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" - "reflect" - "strings" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto"

import (
"context"
"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"
"reflect"
"strings"

"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

const root_binding = "auth"

func NewDynamicCelResponse(expression string) (*DynamicCEL, error) {

cel_exp := DynamicCEL{}

env, err := cel.NewEnv(cel.Declarations(
decls.NewConst(root_binding, 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"] // todo Or do we bind the whole GetAuthorizationJSON to data/json instead?
result, _, err := c.program.Eval(map[string]interface{}{
root_binding: 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
}
35 changes: 35 additions & 0 deletions pkg/evaluators/response/dynamic_cel_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
4 changes: 4 additions & 0 deletions pkg/evaluators/response/dynamic_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 26918f8

Please sign in to comment.