Skip to content

Commit

Permalink
Merge branch 'main' into allow-deny
Browse files Browse the repository at this point in the history
Signed-off-by: Charles-Edouard Brétéché <[email protected]>
  • Loading branch information
eddycharly committed Dec 19, 2024
2 parents 6f80d04 + f4c6489 commit 2678bd5
Show file tree
Hide file tree
Showing 31 changed files with 395 additions and 99 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
set -e
make tests
- name: Upload coverage
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: coverage.out
path: coverage.out
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
make ko-build
make docker-save-image
- name: Upload image archive
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: image.tar
path: image.tar
Expand Down
50 changes: 31 additions & 19 deletions pkg/authz/cel/libs/envoy/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,40 +193,41 @@ func (c *impl) header_keep_empty_value_bool(header ref.Val, flag ref.Val) ref.Va
}
}

func (c *impl) response_code(code ref.Val) ref.Val {
if code, err := utils.ConvertToNative[codes.Code](code); err != nil {
func (c *impl) response_ok(ok ref.Val) ref.Val {
if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil {
return types.WrapErr(err)
} else {
return c.NativeToValue(&authv3.CheckResponse{
Status: &status.Status{Code: int32(code)},
return c.NativeToValue(&OkResponse{
Status: &status.Status{Code: int32(codes.OK)},
OkHttpResponse: ok,
})
}
}

func (c *impl) response_ok(ok ref.Val) ref.Val {
if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil {
func (c *impl) response_denied(denied ref.Val) ref.Val {
if denied, err := utils.ConvertToNative[*authv3.DeniedHttpResponse](denied); err != nil {
return types.WrapErr(err)
} else {
return c.NativeToValue(&authv3.CheckResponse{
Status: &status.Status{Code: int32(codes.OK)},
HttpResponse: &authv3.CheckResponse_OkResponse{OkResponse: ok},
return c.NativeToValue(&DeniedResponse{
Status: &status.Status{Code: int32(codes.PermissionDenied)},
DeniedHttpResponse: denied,
})
}
}

func (c *impl) response_denied(denied ref.Val) ref.Val {
if denied, err := utils.ConvertToNative[*authv3.DeniedHttpResponse](denied); err != nil {
func (c *impl) response_ok_with_message(response ref.Val, message ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*OkResponse](response); err != nil {
return types.WrapErr(err)
} else if message, err := utils.ConvertToNative[string](message); err != nil {
return types.WrapErr(err)
} else {
return c.NativeToValue(&authv3.CheckResponse{
Status: &status.Status{Code: int32(codes.PermissionDenied)},
HttpResponse: &authv3.CheckResponse_DeniedResponse{DeniedResponse: denied},
})
response.Status.Message = message
return c.NativeToValue(response)
}
}

func (c *impl) response_with_message(response ref.Val, message ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*authv3.CheckResponse](response); err != nil {
func (c *impl) response_denied_with_message(response ref.Val, message ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*DeniedResponse](response); err != nil {
return types.WrapErr(err)
} else if message, err := utils.ConvertToNative[string](message); err != nil {
return types.WrapErr(err)
Expand All @@ -236,8 +237,19 @@ func (c *impl) response_with_message(response ref.Val, message ref.Val) ref.Val
}
}

func (c *impl) response_with_metadata(response ref.Val, metadata ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*authv3.CheckResponse](response); err != nil {
func (c *impl) response_ok_with_metadata(response ref.Val, metadata ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*OkResponse](response); err != nil {
return types.WrapErr(err)
} else if metadata, err := utils.ConvertToNative[*structpb.Struct](metadata); err != nil {
return types.WrapErr(err)
} else {
response.DynamicMetadata = metadata
return c.NativeToValue(response)
}
}

func (c *impl) response_denied_with_metadata(response ref.Val, metadata ref.Val) ref.Val {
if response, err := utils.ConvertToNative[*DeniedResponse](response); err != nil {
return types.WrapErr(err)
} else if metadata, err := utils.ConvertToNative[*structpb.Struct](metadata); err != nil {
return types.WrapErr(err)
Expand Down
39 changes: 25 additions & 14 deletions pkg/authz/cel/libs/envoy/lib.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package envoy

import (
"reflect"

authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/ext"
status "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/protobuf/types/known/structpb"
)

// envoy types
var (
// envoy auth types
CheckRequest = types.NewObjectType("envoy.service.auth.v3.CheckRequest")
CheckResponse = types.NewObjectType("envoy.service.auth.v3.CheckResponse")
OkHttpResponse = types.NewObjectType("envoy.service.auth.v3.OkHttpResponse")
DeniedHttpResponse = types.NewObjectType("envoy.service.auth.v3.DeniedHttpResponse")
Metadata = types.NewObjectType("google.protobuf.Struct")
HeaderValueOption = types.NewObjectType("envoy.config.core.v3.HeaderValueOption")
Metadata = types.NewObjectType("google.protobuf.Struct")
OkHttpResponse = types.NewObjectType("envoy.service.auth.v3.OkHttpResponse")
QueryParameter = types.NewObjectType("envoy.config.core.v3.QueryParameter")
// lib types
DeniedResponseType = types.NewObjectType("envoy.DeniedResponse")
OkResponseType = types.NewObjectType("envoy.OkResponse")
)

type lib struct{}
Expand All @@ -28,7 +35,14 @@ func Lib() cel.EnvOption {
func (c *lib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
// register envoy protobuf messages
cel.Types((*authv3.CheckRequest)(nil), (*authv3.CheckResponse)(nil)),
cel.Types(
(*authv3.CheckRequest)(nil),
(*authv3.DeniedHttpResponse)(nil),
(*authv3.OkHttpResponse)(nil),
(*status.Status)(nil),
(*structpb.Struct)(nil),
),
ext.NativeTypes(ext.ParseStructTags(true), reflect.TypeFor[DeniedResponse](), reflect.TypeFor[OkResponse]()),
// extend environment with function overloads
c.extendEnv,
}
Expand All @@ -51,11 +65,6 @@ func (*lib) extendEnv(env *cel.Env) (*cel.Env, error) {
"envoy.Denied": {
cel.Overload("denied", []*cel.Type{types.IntType}, DeniedHttpResponse, cel.UnaryBinding(impl.denied)),
},
"envoy.Response": {
cel.Overload("response_code", []*cel.Type{types.IntType}, CheckResponse, cel.UnaryBinding(impl.response_code)),
cel.Overload("response_ok", []*cel.Type{OkHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_ok)),
cel.Overload("response_denied", []*cel.Type{DeniedHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_denied)),
},
"envoy.Header": {
cel.Overload("header_key_value", []*cel.Type{types.StringType, types.StringType}, HeaderValueOption, cel.BinaryBinding(impl.header_key_value)),
},
Expand Down Expand Up @@ -90,14 +99,16 @@ func (*lib) extendEnv(env *cel.Env) (*cel.Env, error) {
cel.MemberOverload("header_keep_empty_value_bool", []*cel.Type{HeaderValueOption, types.BoolType}, HeaderValueOption, cel.BinaryBinding(impl.header_keep_empty_value_bool)),
},
"Response": {
cel.MemberOverload("ok_response", []*cel.Type{OkHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_ok)),
cel.MemberOverload("denied_response", []*cel.Type{DeniedHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_denied)),
cel.MemberOverload("ok_response", []*cel.Type{OkHttpResponse}, OkResponseType, cel.UnaryBinding(impl.response_ok)),
cel.MemberOverload("denied_response", []*cel.Type{DeniedHttpResponse}, DeniedResponseType, cel.UnaryBinding(impl.response_denied)),
},
"WithMessage": {
cel.MemberOverload("response_with_message", []*cel.Type{CheckResponse, types.StringType}, CheckResponse, cel.BinaryBinding(impl.response_with_message)),
cel.MemberOverload("response_ok_with_message", []*cel.Type{OkResponseType, types.StringType}, OkResponseType, cel.BinaryBinding(impl.response_ok_with_message)),
cel.MemberOverload("response_denied_with_message", []*cel.Type{DeniedResponseType, types.StringType}, DeniedResponseType, cel.BinaryBinding(impl.response_denied_with_message)),
},
"WithMetadata": {
cel.MemberOverload("response_with_metadata", []*cel.Type{CheckResponse, Metadata}, CheckResponse, cel.BinaryBinding(impl.response_with_metadata)),
cel.MemberOverload("response_ok_with_metadata", []*cel.Type{OkResponseType, Metadata}, OkResponseType, cel.BinaryBinding(impl.response_ok_with_metadata)),
cel.MemberOverload("response_denied_with_metadata", []*cel.Type{DeniedResponseType, Metadata}, DeniedResponseType, cel.BinaryBinding(impl.response_denied_with_metadata)),
},
}
// create env options corresponding to our function overloads
Expand Down
103 changes: 80 additions & 23 deletions pkg/authz/cel/libs/envoy/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,86 @@ import (
"github.com/google/cel-go/interpreter"
"github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/envoy"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
)

func TestNewEnv(t *testing.T) {
source := `
envoy
.Denied(401)
.WithBody("Authentication Failed")
.WithHeader(envoy.Header("foo", "bar").KeepEmptyValue())
.Response()
.WithMetadata({"my-new-metadata": "my-new-value"})
.WithMessage("hello")
`
env, err := cel.NewEnv(envoy.Lib())
assert.NoError(t, err)
ast, issues := env.Compile(source)
assert.Nil(t, issues)
prog, err := env.Program(ast)
assert.NoError(t, err)
assert.NotNil(t, prog)
out, _, err := prog.Eval(interpreter.EmptyActivation())
assert.NoError(t, err)
assert.NotNil(t, out)
a, err := out.ConvertToNative(reflect.TypeFor[*authv3.CheckResponse]())
assert.NoError(t, err)
assert.NotNil(t, a)
func TestOkResponse(t *testing.T) {
tests := []struct {
name string
source string
want envoy.OkResponse
}{{
// source: `
// envoy
// .Denied(401)
// .WithBody("Authentication Failed")
// .WithHeader(envoy.Header("foo", "bar").KeepEmptyValue())
// .Response()
// .WithMetadata({"my-new-metadata": "my-new-value"})
// .WithMessage("hello")
// `,
// }, {
// name: "empty",
// want: envoy.OkResponse{},
// source: `
// envoy.OkResponse{}
// `,
// }, {
// name: "with status",
// want: envoy.OkResponse{
// Status: &status.Status{
// Code: 0,
// },
// },
// source: `
// envoy.OkResponse{
// status: google.rpc.Status{
// code: 0
// }
// }
// `,
// }, {
name: "with metadata",
want: envoy.OkResponse{
DynamicMetadata: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": structpb.NewStringValue("bar"),
},
},
},
source: `
envoy.OkResponse{
dynamic_metadata: {
"foo": "bar"
}
}
`,
}, {
name: "with response",
want: envoy.OkResponse{
OkHttpResponse: &authv3.OkHttpResponse{},
},
source: `
envoy.OkResponse{
http_response: envoy.service.auth.v3.OkHttpResponse{}
}
`,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env, err := cel.NewEnv(envoy.Lib())
assert.NoError(t, err)
ast, issues := env.Compile(tt.source)
assert.Nil(t, issues)
prog, err := env.Program(ast)
assert.NoError(t, err)
assert.NotNil(t, prog)
out, _, err := prog.Eval(interpreter.EmptyActivation())
assert.NoError(t, err)
assert.NotNil(t, out)
got, err := out.ConvertToNative(reflect.TypeFor[envoy.OkResponse]())
assert.NoError(t, err)
assert.EqualExportedValues(t, tt.want, got)
})
}
}
53 changes: 53 additions & 0 deletions pkg/authz/cel/libs/envoy/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package envoy

import (
authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
status "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/protobuf/types/known/structpb"
)

type OkResponse struct {
// Status “OK“ allows the request. Any other status indicates the request should be denied, and
// for HTTP filter, if not overridden by :ref:`denied HTTP response status <envoy_v3_api_field_service.auth.v3.DeniedHttpResponse.status>`
// Envoy sends “403 Forbidden“ HTTP status code by default.
Status *status.Status `cel:"status"`
// An message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
//
// Types that are assignable to HttpResponse:
//
// *CheckResponse_DeniedResponse
// *CheckResponse_OkResponse
OkHttpResponse *authv3.OkHttpResponse `cel:"http_response"`
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
// that requires it:
//
// - :ref:`envoy.filters.http.ext_authz <config_http_filters_ext_authz_dynamic_metadata>` for HTTP filter.
// - :ref:`envoy.filters.network.ext_authz <config_network_filters_ext_authz_dynamic_metadata>` for network filter.
DynamicMetadata *structpb.Struct `cel:"dynamic_metadata"`
}

type DeniedResponse struct {
// Status “OK“ allows the request. Any other status indicates the request should be denied, and
// for HTTP filter, if not overridden by :ref:`denied HTTP response status <envoy_v3_api_field_service.auth.v3.DeniedHttpResponse.status>`
// Envoy sends “403 Forbidden“ HTTP status code by default.
Status *status.Status `cel:"status"`
// An message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
//
// Types that are assignable to HttpResponse:
//
// *CheckResponse_DeniedResponse
// *CheckResponse_OkResponse
DeniedHttpResponse *authv3.DeniedHttpResponse `cel:"http_response"`
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
// that requires it:
//
// - :ref:`envoy.filters.http.ext_authz <config_http_filters_ext_authz_dynamic_metadata>` for HTTP filter.
// - :ref:`envoy.filters.network.ext_authz <config_network_filters_ext_authz_dynamic_metadata>` for network filter.
DynamicMetadata *structpb.Struct `cel:"dynamic_metadata"`
}
3 changes: 1 addition & 2 deletions pkg/authz/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,5 @@ func (s *service) check(ctx context.Context, r *authv3.CheckRequest) (*authv3.Ch
}
}
// we didn't have a response
// TODO: default response
return nil, nil
return &authv3.CheckResponse{}, nil
}
Loading

0 comments on commit 2678bd5

Please sign in to comment.