From 00c8793af4c881075bcee5d3f7ec2a7799c9b8f3 Mon Sep 17 00:00:00 2001 From: Jesse Haka Date: Tue, 20 Feb 2024 21:22:44 +0200 Subject: [PATCH] add authorisation api spec Signed-off-by: Jesse Haka --- api/v1alpha1/authorization_types.go | 47 +++++++ api/v1alpha1/securitypolicy_types.go | 5 + .../validation/securitypolicy_validate.go | 34 ++++- .../securitypolicy_validate_test.go | 118 +++++++++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 69 ++++++++++ ...ateway.envoyproxy.io_securitypolicies.yaml | 38 ++++++ site/content/en/latest/api/extension_types.md | 55 ++++++++ 7 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 api/v1alpha1/authorization_types.go diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go new file mode 100644 index 000000000000..81146c50be22 --- /dev/null +++ b/api/v1alpha1/authorization_types.go @@ -0,0 +1,47 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package v1alpha1 + +// Authorization defines the authorization configuration. +type Authorization struct { + // Rules contains all the authorization rules. + // + // +kubebuilder:validation:MinItems=1 + Rules []Rule `json:"rules,omitempty"` +} + +// Rule defines the single authorization rule. +type Rule struct { + // ClientSelectors contains the client selector configuration. + // + // +kubebuilder:validation:MinItems=1 + ClientSelectors []ClientSelector `json:"clientSelector,omitempty"` + + // Action defines the action to be taken if the rule matches. + Action RuleActionType `json:"action"` +} + +// ClientSelector contains the client selector configuration. +type ClientSelector struct { + // ClientCIDRs is a list of CIDRs. + // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // + // +optional + ClientCIDRs []string `json:"clientCIDR,omitempty"` +} + +// RuleActionType specifies the types of authorization rule action. +// +kubebuilder:validation:Enum=AllowRuleType;DenyRuleType;LogRuleType +type RuleActionType string + +const ( + // AllowRuleType is the action to allow the request. + AllowRuleType RuleActionType = "Allow" + // DenyRuleType is the action to deny the request. + DenyRuleType RuleActionType = "Deny" + // LogRuleType is the action to log the request. + LogRuleType RuleActionType = "Log" +) diff --git a/api/v1alpha1/securitypolicy_types.go b/api/v1alpha1/securitypolicy_types.go index 6d90536ae918..832731f7a40e 100644 --- a/api/v1alpha1/securitypolicy_types.go +++ b/api/v1alpha1/securitypolicy_types.go @@ -70,6 +70,11 @@ type SecurityPolicySpec struct { // // +optional ExtAuth *ExtAuth `json:"extAuth,omitempty"` + + // Authorization defines the authorization configuration. + // + // +optional + Authorization *Authorization `json:"authorization,omitempty"` } // SecurityPolicyStatus defines the state of SecurityPolicy diff --git a/api/v1alpha1/validation/securitypolicy_validate.go b/api/v1alpha1/validation/securitypolicy_validate.go index 628d3f801730..61446ee79789 100644 --- a/api/v1alpha1/validation/securitypolicy_validate.go +++ b/api/v1alpha1/validation/securitypolicy_validate.go @@ -8,6 +8,7 @@ package validation import ( "errors" "fmt" + "net" "net/mail" "net/url" @@ -24,7 +25,7 @@ func ValidateSecurityPolicy(policy *egv1a1.SecurityPolicy) error { return errors.New("policy is nil") } if err := validateSecurityPolicySpec(&policy.Spec); err != nil { - errs = append(errs, errors.New("policy is nil")) + errs = append(errs, err) } return utilerrors.NewAggregate(errs) @@ -42,6 +43,8 @@ func validateSecurityPolicySpec(spec *egv1a1.SecurityPolicySpec) error { sum++ case spec.JWT != nil: sum++ + case spec.Authorization != nil: + sum++ } if sum == 0 { errs = append(errs, errors.New("no security policy is specified")) @@ -52,13 +55,40 @@ func validateSecurityPolicySpec(spec *egv1a1.SecurityPolicySpec) error { return utilerrors.NewAggregate(errs) } - if err := ValidateJWTProvider(spec.JWT.Providers); err != nil { + if spec.JWT != nil { + if err := ValidateJWTProvider(spec.JWT.Providers); err != nil { + errs = append(errs, err) + } + } + + if err := ValidateAuthorisation(spec.Authorization); err != nil { errs = append(errs, err) } return utilerrors.NewAggregate(errs) } +// ValidateAuthorisation validates the provided Authorisation configuration. +func ValidateAuthorisation(as *egv1a1.Authorization) error { + var errs []error + if as == nil { + return nil + } + + for _, rule := range as.Rules { + for _, selector := range rule.ClientSelectors { + for _, cidr := range selector.ClientCIDRs { + _, _, err := net.ParseCIDR(cidr) + if err != nil { + errs = append(errs, fmt.Errorf("invalid CIDR: %s", cidr)) + } + } + } + } + + return utilerrors.NewAggregate(errs) +} + // ValidateJWTProvider validates the provided JWT authentication configuration. func ValidateJWTProvider(providers []egv1a1.JWTProvider) error { var errs []error diff --git a/api/v1alpha1/validation/securitypolicy_validate_test.go b/api/v1alpha1/validation/securitypolicy_validate_test.go index 489c7644f8b1..dba5c9d383b9 100644 --- a/api/v1alpha1/validation/securitypolicy_validate_test.go +++ b/api/v1alpha1/validation/securitypolicy_validate_test.go @@ -8,7 +8,7 @@ package validation import ( "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -463,6 +463,118 @@ func TestValidateSecurityPolicy(t *testing.T) { }, expected: true, }, + { + name: "authorisation with valid ipv4 cidr", + policy: &egv1a1.SecurityPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1a1.KindSecurityPolicy, + APIVersion: egv1a1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.SecurityPolicySpec{ + Authorization: &egv1a1.Authorization{ + Rules: []egv1a1.Rule{ + { + ClientSelectors: []egv1a1.ClientSelector{ + { + ClientCIDRs: []string{"192.168.1.0/24"}, + }, + }, + Action: egv1a1.AllowRuleType, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "authorisation with valid ipv6 cidr", + policy: &egv1a1.SecurityPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1a1.KindSecurityPolicy, + APIVersion: egv1a1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.SecurityPolicySpec{ + Authorization: &egv1a1.Authorization{ + Rules: []egv1a1.Rule{ + { + ClientSelectors: []egv1a1.ClientSelector{ + { + ClientCIDRs: []string{"2001:db8::/64"}, + }, + }, + Action: egv1a1.AllowRuleType, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "authorisation with invalid ipv4 cidr", + policy: &egv1a1.SecurityPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1a1.KindSecurityPolicy, + APIVersion: egv1a1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.SecurityPolicySpec{ + Authorization: &egv1a1.Authorization{ + Rules: []egv1a1.Rule{ + { + ClientSelectors: []egv1a1.ClientSelector{ + { + ClientCIDRs: []string{"192.168.1.001/24"}, + }, + }, + Action: egv1a1.AllowRuleType, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "authorisation with invalid ipv6 cidr", + policy: &egv1a1.SecurityPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1a1.KindSecurityPolicy, + APIVersion: egv1a1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.SecurityPolicySpec{ + Authorization: &egv1a1.Authorization{ + Rules: []egv1a1.Rule{ + { + ClientSelectors: []egv1a1.ClientSelector{ + { + ClientCIDRs: []string{"2001:dffoob8::/64"}, + }, + }, + Action: egv1a1.AllowRuleType, + }, + }, + }, + }, + }, + expected: false, + }, } for i := range testCases { @@ -470,9 +582,9 @@ func TestValidateSecurityPolicy(t *testing.T) { t.Run(tc.name, func(t *testing.T) { err := ValidateSecurityPolicy(tc.policy) if tc.expected { - require.NoError(t, err) + assert.NoError(t, err) } else { - require.Error(t, err) + assert.Error(t, err) } }) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d38d710885d3..f2c3a6a3acc7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -89,6 +89,28 @@ func (in *ActiveHealthCheckPayload) DeepCopy() *ActiveHealthCheckPayload { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Authorization) DeepCopyInto(out *Authorization) { + *out = *in + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]Rule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorization. +func (in *Authorization) DeepCopy() *Authorization { + if in == nil { + return nil + } + out := new(Authorization) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) { *out = *in @@ -403,6 +425,26 @@ func (in *ClientIPDetectionSettings) DeepCopy() *ClientIPDetectionSettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientSelector) DeepCopyInto(out *ClientSelector) { + *out = *in + if in.ClientCIDRs != nil { + in, out := &in.ClientCIDRs, &out.ClientCIDRs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientSelector. +func (in *ClientSelector) DeepCopy() *ClientSelector { + if in == nil { + return nil + } + out := new(ClientSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientTimeout) DeepCopyInto(out *ClientTimeout) { *out = *in @@ -3123,6 +3165,28 @@ func (in *RetryOn) DeepCopy() *RetryOn { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rule) DeepCopyInto(out *Rule) { + *out = *in + if in.ClientSelectors != nil { + in, out := &in.ClientSelectors, &out.ClientSelectors + *out = make([]ClientSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. +func (in *Rule) DeepCopy() *Rule { + if in == nil { + return nil + } + out := new(Rule) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecurityPolicy) DeepCopyInto(out *SecurityPolicy) { *out = *in @@ -3211,6 +3275,11 @@ func (in *SecurityPolicySpec) DeepCopyInto(out *SecurityPolicySpec) { *out = new(ExtAuth) (*in).DeepCopyInto(*out) } + if in.Authorization != nil { + in, out := &in.Authorization, &out.Authorization + *out = new(Authorization) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicySpec. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 7feb835b9387..b9081bfaec5f 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -44,6 +44,44 @@ spec: spec: description: Spec defines the desired state of SecurityPolicy. properties: + authorization: + description: Authorization defines the authorization configuration. + properties: + rules: + description: Rules contains all the authorization rules. + items: + description: Rule defines the single authorization rule. + properties: + action: + description: Action defines the action to be taken if the + rule matches. + enum: + - AllowRuleType + - DenyRuleType + - LogRuleType + type: string + clientSelector: + description: ClientSelectors contains the client selector + configuration. + items: + description: ClientSelector contains the client selector + configuration. + properties: + clientCIDR: + description: ClientCIDRs is a list of CIDRs. Valid + examples are "192.168.1.0/24" or "2001:db8::/64" + items: + type: string + type: array + type: object + minItems: 1 + type: array + required: + - action + type: object + minItems: 1 + type: array + type: object basicAuth: description: BasicAuth defines the configuration for the HTTP Basic Authentication. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 07cfc0dc8ba1..2fbdb756d586 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -97,6 +97,20 @@ _Appears in:_ +#### Authorization + + + +Authorization defines the authorization configuration. + +_Appears in:_ +- [SecurityPolicySpec](#securitypolicyspec) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `rules` | _[Rule](#rule) array_ | true | Rules contains all the authorization rules. | + + #### BackOffPolicy @@ -262,6 +276,20 @@ _Appears in:_ | `customHeader` | _[CustomHeaderExtensionSettings](#customheaderextensionsettings)_ | false | CustomHeader provides configuration for determining the client IP address for a request based on a trusted custom HTTP header. This uses the the custom_header original IP detection extension. Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto for more details. | +#### ClientSelector + + + +ClientSelector contains the client selector configuration. + +_Appears in:_ +- [Rule](#rule) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `clientCIDR` | _string array_ | false | ClientCIDRs is a list of CIDRs. Valid examples are "192.168.1.0/24" or "2001:db8::/64" | + + #### ClientTimeout @@ -2196,6 +2224,32 @@ _Appears in:_ | `httpStatusCodes` | _[HTTPStatus](#httpstatus) array_ | false | HttpStatusCodes specifies the http status codes to be retried. | +#### Rule + + + +Rule defines the single authorization rule. + +_Appears in:_ +- [Authorization](#authorization) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `clientSelector` | _[ClientSelector](#clientselector) array_ | true | ClientSelectors contains the client selector configuration. | +| `action` | _[RuleActionType](#ruleactiontype)_ | true | Action defines the action to be taken if the rule matches. | + + +#### RuleActionType + +_Underlying type:_ _string_ + +RuleActionType specifies the types of authorization rule action. + +_Appears in:_ +- [Rule](#rule) + + + #### SecurityPolicy @@ -2246,6 +2300,7 @@ _Appears in:_ | `jwt` | _[JWT](#jwt)_ | false | JWT defines the configuration for JSON Web Token (JWT) authentication. | | `oidc` | _[OIDC](#oidc)_ | false | OIDC defines the configuration for the OpenID Connect (OIDC) authentication. | | `extAuth` | _[ExtAuth](#extauth)_ | false | ExtAuth defines the configuration for External Authorization. | +| `authorization` | _[Authorization](#authorization)_ | false | Authorization defines the authorization configuration. |