diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 73e1385b..c84d3e8b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -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 @@ -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 diff --git a/pkg/authz/cel/libs/envoy/impl.go b/pkg/authz/cel/libs/envoy/impl.go index 12885213..874b2b2b 100644 --- a/pkg/authz/cel/libs/envoy/impl.go +++ b/pkg/authz/cel/libs/envoy/impl.go @@ -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) @@ -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) diff --git a/pkg/authz/cel/libs/envoy/lib.go b/pkg/authz/cel/libs/envoy/lib.go index 5ee6773e..5e15fa0c 100644 --- a/pkg/authz/cel/libs/envoy/lib.go +++ b/pkg/authz/cel/libs/envoy/lib.go @@ -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{} @@ -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, } @@ -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)), }, @@ -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 diff --git a/pkg/authz/cel/libs/envoy/lib_test.go b/pkg/authz/cel/libs/envoy/lib_test.go index 68792a07..dc64dba0 100644 --- a/pkg/authz/cel/libs/envoy/lib_test.go +++ b/pkg/authz/cel/libs/envoy/lib_test.go @@ -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) + }) + } } diff --git a/pkg/authz/cel/libs/envoy/response.go b/pkg/authz/cel/libs/envoy/response.go new file mode 100644 index 00000000..d0cf81ca --- /dev/null +++ b/pkg/authz/cel/libs/envoy/response.go @@ -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 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 ` for HTTP filter. + // - :ref:`envoy.filters.network.ext_authz ` 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 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 ` for HTTP filter. + // - :ref:`envoy.filters.network.ext_authz ` for network filter. + DynamicMetadata *structpb.Struct `cel:"dynamic_metadata"` +} diff --git a/pkg/policy/compiler.go b/pkg/policy/compiler.go index 38f8562a..c0a39d6b 100644 --- a/pkg/policy/compiler.go +++ b/pkg/policy/compiler.go @@ -9,7 +9,7 @@ import ( "github.com/google/cel-go/common/types/ref" "github.com/kyverno/kyverno-envoy-plugin/apis/v1alpha1" engine "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel" - "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/envoy" + envoy "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/envoy" "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/utils" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -118,22 +118,20 @@ func (p compiledPolicy) For(r *authv3.CheckRequest) (AllowFunc, DenyFunc) { if err != nil { return nil, err } - // evaluation result is nil, continue - if _, ok := out.(types.Null); ok { - continue - } // try to convert to a check response - response, err := utils.ConvertToNative[*authv3.CheckResponse](out) + response, err := utils.ConvertToNative[envoy.OkResponse](out) // check error if err != nil { return nil, err } - // evaluation result is nil, continue - if response == nil { - continue - } // no error and evaluation result is not nil, return - return response, nil + return &authv3.CheckResponse{ + Status: response.Status, + HttpResponse: &authv3.CheckResponse_OkResponse{ + OkResponse: response.OkHttpResponse, + }, + DynamicMetadata: response.DynamicMetadata, + }, nil } return nil, nil } @@ -167,34 +165,33 @@ func (p compiledPolicy) For(r *authv3.CheckRequest) (AllowFunc, DenyFunc) { if err != nil { return nil, err } - // evaluation result is nil, continue - if _, ok := out.(types.Null); ok { - continue - } // try to convert to a check response - response, err := utils.ConvertToNative[*authv3.CheckResponse](out) + response, err := utils.ConvertToNative[envoy.DeniedResponse](out) // check error if err != nil { return nil, err } - // evaluation result is nil, continue - if response == nil { - continue - } // no error and evaluation result is not nil, return - return response, nil + return &authv3.CheckResponse{ + Status: response.Status, + HttpResponse: &authv3.CheckResponse_DeniedResponse{ + DeniedResponse: response.DeniedHttpResponse, + }, + DynamicMetadata: response.DynamicMetadata, + }, nil } return nil, nil } - // TODO: failure policy - // return func(r *authv3.CheckRequest) (*authv3.CheckResponse, error) { - // response, err := eval(r) - // if err != nil && policy.Spec.GetFailurePolicy() == admissionregistrationv1.Fail { - // return nil, err - // } - // return response, nil - // }, nil - return allow, deny + failurePolicy := func(inner func() (*authv3.CheckResponse, error)) func() (*authv3.CheckResponse, error) { + return func() (*authv3.CheckResponse, error) { + response, err := inner() + if err != nil && p.failurePolicy == admissionregistrationv1.Fail { + return nil, err + } + return response, nil + } + } + return failurePolicy(allow), failurePolicy(deny) } type Compiler interface { @@ -286,8 +283,8 @@ func (c *compiler) Compile(policy *v1alpha1.AuthorizationPolicy) (CompiledPolicy if err := issues.Err(); err != nil { return nil, append(allErrs, field.Invalid(path, rule.Response, err.Error())) } - if !ast.OutputType().IsExactType(envoy.CheckResponse) { - return nil, append(allErrs, field.Invalid(path, rule.Response, "rule response output is expected to be of type envoy.service.auth.v3.CheckResponse")) + if !ast.OutputType().IsExactType(envoy.DeniedResponseType) { + return nil, append(allErrs, field.Invalid(path, rule.Response, "rule response output is expected to be of type envoy.DeniedResponse")) } prog, err := env.Program(ast) if err != nil { @@ -325,8 +322,8 @@ func (c *compiler) Compile(policy *v1alpha1.AuthorizationPolicy) (CompiledPolicy if err := issues.Err(); err != nil { return nil, append(allErrs, field.Invalid(path, rule.Response, err.Error())) } - if !ast.OutputType().IsExactType(envoy.CheckResponse) { - return nil, append(allErrs, field.Invalid(path, rule.Response, "rule response output is expected to be of type envoy.service.auth.v3.CheckResponse")) + if !ast.OutputType().IsExactType(envoy.OkResponseType) { + return nil, append(allErrs, field.Invalid(path, rule.Response, "rule response output is expected to be of type envoy.OkResponse")) } prog, err := env.Program(ast) if err != nil { diff --git a/tests/e2e/validation-webhook/allow/match/compilation-failure/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/match/compilation-failure/chainsaw-test.yaml new file mode 100644 index 00000000..8cbefce4 --- /dev/null +++ b/tests/e2e/validation-webhook/allow/match/compilation-failure/chainsaw-test.yaml @@ -0,0 +1,15 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: compilation-failure +spec: + steps: + - try: + - create: + file: ./policy.yaml + expect: + - check: + ($error): |- + admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "compilation-failure" is invalid: spec.allow[0].match: Invalid value: "'flop' + 2\n": ERROR: :1:8: found no matching overload for '_+_' applied to '(string, int)' + | 'flop' + 2 + | .......^ diff --git a/tests/e2e/validation-webhook/allow/match/compilation-failure/policy.yaml b/tests/e2e/validation-webhook/allow/match/compilation-failure/policy.yaml new file mode 100644 index 00000000..6864f235 --- /dev/null +++ b/tests/e2e/validation-webhook/allow/match/compilation-failure/policy.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: compilation-failure +spec: + allow: + - match: > + 'flop' + 2 + response: > + envoy + .Denied(403) + .WithBody("Unauthorized Request") + .Response() diff --git a/tests/e2e/validation-webhook/allow/match/invalid-output-type/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/match/invalid-output-type/chainsaw-test.yaml new file mode 100644 index 00000000..cc5d369a --- /dev/null +++ b/tests/e2e/validation-webhook/allow/match/invalid-output-type/chainsaw-test.yaml @@ -0,0 +1,13 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: invalid-output-type +spec: + steps: + - try: + - create: + file: ./policy.yaml + expect: + - check: + ($error): |- + admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "invalid-output-type" is invalid: spec.allow[0].match: Invalid value: "'flop'\n": rule match output is expected to be of type bool diff --git a/tests/e2e/validation-webhook/allow/match/invalid-output-type/policy.yaml b/tests/e2e/validation-webhook/allow/match/invalid-output-type/policy.yaml new file mode 100644 index 00000000..f33fb147 --- /dev/null +++ b/tests/e2e/validation-webhook/allow/match/invalid-output-type/policy.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: invalid-output-type +spec: + allow: + - match: > + 'flop' + response: > + envoy + .Denied(403) + .WithBody("Unauthorized Request") + .Response() diff --git a/tests/e2e/validation-webhook/authorizations/match/valid/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/match/valid/chainsaw-test.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/match/valid/chainsaw-test.yaml rename to tests/e2e/validation-webhook/allow/match/valid/chainsaw-test.yaml diff --git a/tests/e2e/validation-webhook/allow/match/valid/policy.yaml b/tests/e2e/validation-webhook/allow/match/valid/policy.yaml new file mode 100644 index 00000000..02bfb11e --- /dev/null +++ b/tests/e2e/validation-webhook/allow/match/valid/policy.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: valid +spec: + allow: + - match: > + false + response: > + envoy + .Denied(403) + .WithBody("Unauthorized Request") + .Response() diff --git a/tests/e2e/validation-webhook/allow/response/compilation-failure/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/response/compilation-failure/chainsaw-test.yaml new file mode 100644 index 00000000..e3fff4cc --- /dev/null +++ b/tests/e2e/validation-webhook/allow/response/compilation-failure/chainsaw-test.yaml @@ -0,0 +1,15 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: compilation-failure +spec: + steps: + - try: + - create: + file: ./policy.yaml + expect: + - check: + ($error): |- + admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "compilation-failure" is invalid: spec.allow[0].response: Invalid value: "envoy.Allowed() + 1\n": ERROR: :1:17: found no matching overload for '_+_' applied to '(envoy.service.auth.v3.OkHttpResponse, int)' + | envoy.Allowed() + 1 + | ................^ diff --git a/tests/e2e/validation-webhook/allow/response/compilation-failure/policy.yaml b/tests/e2e/validation-webhook/allow/response/compilation-failure/policy.yaml new file mode 100644 index 00000000..6115708a --- /dev/null +++ b/tests/e2e/validation-webhook/allow/response/compilation-failure/policy.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: compilation-failure +spec: + allow: + - response: > + envoy.Allowed() + 1 diff --git a/tests/e2e/validation-webhook/allow/response/invalid-output-type/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/response/invalid-output-type/chainsaw-test.yaml new file mode 100644 index 00000000..76f780ab --- /dev/null +++ b/tests/e2e/validation-webhook/allow/response/invalid-output-type/chainsaw-test.yaml @@ -0,0 +1,13 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: invalid-output-type +spec: + steps: + - try: + - create: + file: ./policy.yaml + expect: + - check: + ($error): |- + admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "invalid-output-type" is invalid: spec.allow[0].response: Invalid value: "'bye'\n": rule response output is expected to be of type envoy.OkResponse diff --git a/tests/e2e/validation-webhook/allow/response/invalid-output-type/policy.yaml b/tests/e2e/validation-webhook/allow/response/invalid-output-type/policy.yaml new file mode 100644 index 00000000..7bc4fe29 --- /dev/null +++ b/tests/e2e/validation-webhook/allow/response/invalid-output-type/policy.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: invalid-output-type +spec: + allow: + - response: > + 'bye' diff --git a/tests/e2e/validation-webhook/authorizations/response/valid/chainsaw-test.yaml b/tests/e2e/validation-webhook/allow/response/valid/chainsaw-test.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/response/valid/chainsaw-test.yaml rename to tests/e2e/validation-webhook/allow/response/valid/chainsaw-test.yaml diff --git a/tests/e2e/validation-webhook/authorizations/response/valid/policy.yaml b/tests/e2e/validation-webhook/allow/response/valid/policy.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/response/valid/policy.yaml rename to tests/e2e/validation-webhook/allow/response/valid/policy.yaml diff --git a/tests/e2e/validation-webhook/authorizations/match/compilation-failure/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/match/compilation-failure/chainsaw-test.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/match/compilation-failure/chainsaw-test.yaml rename to tests/e2e/validation-webhook/deny/match/compilation-failure/chainsaw-test.yaml diff --git a/tests/e2e/validation-webhook/authorizations/match/compilation-failure/policy.yaml b/tests/e2e/validation-webhook/deny/match/compilation-failure/policy.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/match/compilation-failure/policy.yaml rename to tests/e2e/validation-webhook/deny/match/compilation-failure/policy.yaml diff --git a/tests/e2e/validation-webhook/authorizations/match/invalid-output-type/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/match/invalid-output-type/chainsaw-test.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/match/invalid-output-type/chainsaw-test.yaml rename to tests/e2e/validation-webhook/deny/match/invalid-output-type/chainsaw-test.yaml diff --git a/tests/e2e/validation-webhook/authorizations/match/invalid-output-type/policy.yaml b/tests/e2e/validation-webhook/deny/match/invalid-output-type/policy.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/match/invalid-output-type/policy.yaml rename to tests/e2e/validation-webhook/deny/match/invalid-output-type/policy.yaml diff --git a/tests/e2e/validation-webhook/deny/match/valid/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/match/valid/chainsaw-test.yaml new file mode 100644 index 00000000..df1e970c --- /dev/null +++ b/tests/e2e/validation-webhook/deny/match/valid/chainsaw-test.yaml @@ -0,0 +1,9 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: valid +spec: + steps: + - try: + - create: + file: ./policy.yaml diff --git a/tests/e2e/validation-webhook/authorizations/match/valid/policy.yaml b/tests/e2e/validation-webhook/deny/match/valid/policy.yaml similarity index 83% rename from tests/e2e/validation-webhook/authorizations/match/valid/policy.yaml rename to tests/e2e/validation-webhook/deny/match/valid/policy.yaml index 31bbda43..7d9baceb 100644 --- a/tests/e2e/validation-webhook/authorizations/match/valid/policy.yaml +++ b/tests/e2e/validation-webhook/deny/match/valid/policy.yaml @@ -12,7 +12,3 @@ spec: .Denied(403) .WithBody("Unauthorized Request") .Response() - - response: > - envoy - .Allowed() - .Response() diff --git a/tests/e2e/validation-webhook/authorizations/response/compilation-failure/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/response/compilation-failure/chainsaw-test.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/response/compilation-failure/chainsaw-test.yaml rename to tests/e2e/validation-webhook/deny/response/compilation-failure/chainsaw-test.yaml diff --git a/tests/e2e/validation-webhook/authorizations/response/compilation-failure/policy.yaml b/tests/e2e/validation-webhook/deny/response/compilation-failure/policy.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/response/compilation-failure/policy.yaml rename to tests/e2e/validation-webhook/deny/response/compilation-failure/policy.yaml diff --git a/tests/e2e/validation-webhook/authorizations/response/invalid-output-type/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/response/invalid-output-type/chainsaw-test.yaml similarity index 91% rename from tests/e2e/validation-webhook/authorizations/response/invalid-output-type/chainsaw-test.yaml rename to tests/e2e/validation-webhook/deny/response/invalid-output-type/chainsaw-test.yaml index 2d5bdf34..d3844ca5 100644 --- a/tests/e2e/validation-webhook/authorizations/response/invalid-output-type/chainsaw-test.yaml +++ b/tests/e2e/validation-webhook/deny/response/invalid-output-type/chainsaw-test.yaml @@ -10,4 +10,4 @@ spec: expect: - check: ($error): |- - admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "invalid-output-type" is invalid: spec.deny[0].response: Invalid value: "'bye'\n": rule response output is expected to be of type envoy.service.auth.v3.CheckResponse + admission webhook "kyverno-authz-server-validation.kyverno.svc" denied the request: AuthorizationPolicy.envoy.kyverno.io "invalid-output-type" is invalid: spec.deny[0].response: Invalid value: "'bye'\n": rule response output is expected to be of type envoy.DeniedResponse diff --git a/tests/e2e/validation-webhook/authorizations/response/invalid-output-type/policy.yaml b/tests/e2e/validation-webhook/deny/response/invalid-output-type/policy.yaml similarity index 100% rename from tests/e2e/validation-webhook/authorizations/response/invalid-output-type/policy.yaml rename to tests/e2e/validation-webhook/deny/response/invalid-output-type/policy.yaml diff --git a/tests/e2e/validation-webhook/deny/response/valid/chainsaw-test.yaml b/tests/e2e/validation-webhook/deny/response/valid/chainsaw-test.yaml new file mode 100644 index 00000000..df1e970c --- /dev/null +++ b/tests/e2e/validation-webhook/deny/response/valid/chainsaw-test.yaml @@ -0,0 +1,9 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: valid +spec: + steps: + - try: + - create: + file: ./policy.yaml diff --git a/tests/e2e/validation-webhook/deny/response/valid/policy.yaml b/tests/e2e/validation-webhook/deny/response/valid/policy.yaml new file mode 100644 index 00000000..9ea4b339 --- /dev/null +++ b/tests/e2e/validation-webhook/deny/response/valid/policy.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=../../../../../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: valid +spec: + variables: + - name: force_authorized + expression: object.attributes.request.http.headers[?"x-force-authorized"].orValue("") in ["enabled", "true"] + - name: force_unauthenticated + expression: object.attributes.request.http.headers[?"x-force-unauthenticated"].orValue("") in ["enabled", "true"] + - name: metadata + expression: '{"my-new-metadata": "my-new-value"}' + deny: + # if force_unauthenticated -> 401 + - match: > + variables.force_unauthenticated + response: > + envoy + .Denied(401) + .WithBody("Authentication Failed") + .Response() + # if force_unauthenticated -> 403 + - match: > + !variables.force_authorized + response: > + envoy + .Denied(403) + .WithBody("Unauthorized Request") + .Response() + allow: + # else -> 200 + - response: > + envoy + .Allowed() + .WithHeader("x-validated-by", "my-security-checkpoint") + .WithoutHeader("x-force-authorized") + .WithResponseHeader("x-add-custom-response-header", "added") + .Response() + .WithMetadata(variables.metadata)