Skip to content

Commit

Permalink
feat: separate deny and allow rules (#272)
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 authored Dec 30, 2024
1 parent 2500b79 commit dc82d97
Show file tree
Hide file tree
Showing 44 changed files with 869 additions and 297 deletions.
31 changes: 28 additions & 3 deletions .crds/envoy.kyverno.io_authorizationpolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,34 @@ spec:
description: AuthorizationPolicySpec defines the spec of an authorization
policy
properties:
authorizations:
description: Authorizations contain CEL expressions which is used
to apply the authorization.
allow:
description: Allow contain CEL expressions which is used to allow
a request.
items:
description: Authorization defines an authorization policy rule
properties:
match:
description: Match represents the match condition which will
be evaluated by CEL. Must evaluate to bool.
type: string
response:
description: |-
Response represents the response expression which will be evaluated by CEL.
ref: https://github.com/google/cel-spec
CEL expressions have access to CEL variables as well as some other useful variables:
- 'object' - The object from the incoming request. (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest)
CEL expressions are expected to return an envoy CheckResponse (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse).
type: string
required:
- response
type: object
type: array
x-kubernetes-list-type: atomic
deny:
description: Deny contain CEL expressions which is used to deny a
request.
items:
description: Authorization defines an authorization policy rule
properties:
Expand Down
26 changes: 14 additions & 12 deletions .manifests/policies/demo-policy.example.com.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/.schemas/json/authorizationpolicy-envoy-v1alpha1.json
# yaml-language-server: $schema=../../.schemas/json/authorizationpolicy-envoy-v1alpha1.json
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
Expand All @@ -11,28 +11,30 @@ spec:
expression: object.attributes.request.http.headers[?"x-force-unauthenticated"].orValue("") in ["enabled", "true"]
- name: metadata
expression: '{"my-new-metadata": "my-new-value"}'
authorizations:
deny:
# if force_unauthenticated -> 401
- match: variables.force_unauthenticated
- match: >
variables.force_unauthenticated
response: >
envoy
.Denied(401)
.WithBody("Authentication Failed")
.Response()
# if force_authorized -> 200
- match: variables.force_authorized
# 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)
# else -> 403
- match: 'true'
response: >
envoy
.Denied(403)
.WithBody("Unauthorized Request")
.Response()
36 changes: 34 additions & 2 deletions .schemas/json/authorizationpolicy-envoy-v1alpha1.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,40 @@
"null"
],
"properties": {
"authorizations": {
"description": "Authorizations contain CEL expressions which is used to apply the authorization.",
"allow": {
"description": "Allow contain CEL expressions which is used to allow a request.",
"type": [
"array",
"null"
],
"items": {
"description": "Authorization defines an authorization policy rule",
"type": [
"object",
"null"
],
"required": [
"response"
],
"properties": {
"match": {
"description": "Match represents the match condition which will be evaluated by CEL. Must evaluate to bool.",
"type": [
"string",
"null"
]
},
"response": {
"description": "Response represents the response expression which will be evaluated by CEL.\nref: https://github.com/google/cel-spec\nCEL expressions have access to CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest)\n\nCEL expressions are expected to return an envoy CheckResponse (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse).",
"type": "string"
}
},
"additionalProperties": false
},
"x-kubernetes-list-type": "atomic"
},
"deny": {
"description": "Deny contain CEL expressions which is used to deny a request.",
"type": [
"array",
"null"
Expand Down
9 changes: 7 additions & 2 deletions apis/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ type AuthorizationPolicySpec struct {
// +optional
Variables []admissionregistrationv1.Variable `json:"variables,omitempty" patchStrategy:"merge" patchMergeKey:"name"`

// Authorizations contain CEL expressions which is used to apply the authorization.
// Deny contain CEL expressions which is used to deny a request.
// +listType=atomic
// +optional
Authorizations []Authorization `json:"authorizations,omitempty"`
Deny []Authorization `json:"deny,omitempty"`

// Allow contain CEL expressions which is used to allow a request.
// +listType=atomic
// +optional
Allow []Authorization `json:"allow,omitempty"`
}

func (s *AuthorizationPolicySpec) GetFailurePolicy() admissionregistrationv1.FailurePolicyType {
Expand Down
9 changes: 7 additions & 2 deletions apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 28 additions & 3 deletions charts/kyverno-authz-server/templates/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,34 @@ spec:
description: AuthorizationPolicySpec defines the spec of an authorization
policy
properties:
authorizations:
description: Authorizations contain CEL expressions which is used
to apply the authorization.
allow:
description: Allow contain CEL expressions which is used to allow
a request.
items:
description: Authorization defines an authorization policy rule
properties:
match:
description: Match represents the match condition which will
be evaluated by CEL. Must evaluate to bool.
type: string
response:
description: |-
Response represents the response expression which will be evaluated by CEL.
ref: https://github.com/google/cel-spec
CEL expressions have access to CEL variables as well as some other useful variables:
- 'object' - The object from the incoming request. (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest)
CEL expressions are expected to return an envoy CheckResponse (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse).
type: string
required:
- response
type: object
type: array
x-kubernetes-list-type: atomic
deny:
description: Deny contain CEL expressions which is used to deny a
request.
items:
description: Authorization defines an authorization policy rule
properties:
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
Loading

0 comments on commit dc82d97

Please sign in to comment.