From d53caf5deeb0fe099b26ff9ca44aac24a502fd4b Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Tue, 14 May 2024 19:41:47 -0700 Subject: [PATCH 01/10] authorization impl Signed-off-by: huabing zhao --- api/v1alpha1/authorization_types.go | 42 +- api/v1alpha1/envoyproxy_types.go | 7 +- api/v1alpha1/zz_generated.deepcopy.go | 45 +- .../gateway.envoyproxy.io_envoyproxies.yaml | 6 + ...ateway.envoyproxy.io_securitypolicies.yaml | 11 +- internal/gatewayapi/backendtrafficpolicy.go | 15 +- internal/gatewayapi/helpers.go | 16 + internal/gatewayapi/securitypolicy.go | 90 +++- ...telimit-default-route-level-limit.out.yaml | 3 +- ...rafficpolicy-with-local-ratelimit.out.yaml | 3 +- ...ckendtrafficpolicy-with-ratelimit.out.yaml | 3 +- .../securitypolicy-with-authoriztion.in.yaml | 131 ++++++ .../securitypolicy-with-authoriztion.out.yaml | 399 ++++++++++++++++++ internal/ir/xds.go | 37 +- internal/ir/zz_generated.deepcopy.go | 73 ++++ internal/xds/translator/authorization.go | 266 ++++++++++++ internal/xds/translator/extauth.go | 1 - internal/xds/translator/httpfilters.go | 13 +- internal/xds/translator/httpfilters_test.go | 86 ++-- internal/xds/translator/local_ratelimit.go | 4 +- internal/xds/translator/ratelimit.go | 4 +- .../testdata/in/xds-ir/authorization.yaml | 109 +++++ .../out/xds-ir/authorization.clusters.yaml | 51 +++ .../out/xds-ir/authorization.endpoints.yaml | 36 ++ .../out/xds-ir/authorization.listeners.yaml | 37 ++ .../out/xds-ir/authorization.routes.yaml | 135 ++++++ site/content/en/latest/api/extension_types.md | 77 ++-- .../e2e/testdata/authorization-client-ip.yaml | 98 +++++ .../authorization-default-action.yaml | 58 +++ test/e2e/testdata/basic-auth.yaml | 2 - test/e2e/tests/authorization-client-ip.go | 174 ++++++++ .../e2e/tests/authorization-default-action.go | 92 ++++ 32 files changed, 1957 insertions(+), 167 deletions(-) create mode 100644 internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml create mode 100755 internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml create mode 100644 internal/xds/translator/authorization.go create mode 100644 internal/xds/translator/testdata/in/xds-ir/authorization.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml create mode 100644 test/e2e/testdata/authorization-client-ip.yaml create mode 100644 test/e2e/testdata/authorization-default-action.yaml create mode 100644 test/e2e/tests/authorization-client-ip.go create mode 100644 test/e2e/tests/authorization-default-action.go diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index c52a2063b36..c2c6c78d99f 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -6,7 +6,8 @@ package v1alpha1 // Authorization defines the authorization configuration. -// +notImplementedHide +// +// Note: if neither `Rules` nor `DefaultAction` is specified, the default action is to deny all requests. type Authorization struct { // Rules defines a list of authorization rules. // These rules are evaluated in order, the first matching rule will be applied, @@ -16,50 +17,45 @@ type Authorization struct { // and the second rule denies it, when a request matches both rules, it will be allowed. // // +optional - Rules []Rule `json:"rules,omitempty"` + Rules []AuthorizationRule `json:"rules,omitempty"` // DefaultAction defines the default action to be taken if no rules match. // If not specified, the default action is Deny. // +optional - DefaultAction *RuleActionType `json:"defaultAction"` + DefaultAction *AuthorizationAction `json:"defaultAction"` } -// Rule defines the single authorization rule. -// +notImplementedHide -type Rule struct { +// AuthorizationRule defines the single authorization rule. +type AuthorizationRule struct { + // Name is a user-friendly name for the rule. It's just for display purposes. + // +optional + Name *string `json:"name"` + // Action defines the action to be taken if the rule matches. - Action RuleActionType `json:"action"` + Action AuthorizationAction `json:"action"` // Principal specifies the client identity of a request. Principal Principal `json:"principal"` - - // Permissions contains allowed HTTP methods. - // If empty, all methods are matching. - // - // +optional - // Permissions []string `json:"permissions,omitempty"` } // Principal specifies the client identity of a request. -// +notImplementedHide type Principal struct { - // ClientCIDR is the IP CIDR range of the client. + // ClientCIDRs are the IP CIDR ranges of the client. // Valid examples are "192.168.1.0/24" or "2001:db8::/64" // // By default, the client IP is inferred from the x-forwarder-for header and proxy protocol. // You can use the `EnableProxyProtocol` and `ClientIPDetection` options in // the `ClientTrafficPolicy` to configure how the client IP is detected. - ClientCIDR []string `json:"clientCIDR,omitempty"` + ClientCIDRs []string `json:"clientCIDRs,omitempty"` } -// RuleActionType specifies the types of authorization rule action. +// AuthorizationAction defines the action to be taken if a rule matches. // +kubebuilder:validation:Enum=Allow;Deny -// +notImplementedHide -type RuleActionType string +type AuthorizationAction string const ( - // Allow is the action to allow the request. - Allow RuleActionType = "Allow" - // Deny is the action to deny the request. - Deny RuleActionType = "Deny" + // AuthorizationActionAllow is the action to allow the request. + AuthorizationActionAllow AuthorizationAction = "Allow" + // AuthorizationActionDeny is the action to deny the request. + AuthorizationActionDeny AuthorizationAction = "Deny" ) diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index d8afe76ca78..9f463ab1af0 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -109,6 +109,8 @@ type EnvoyProxySpec struct { // // - envoy.filters.http.wasm // + // - envoy.filters.http.rbac + // // - envoy.filters.http.local_ratelimit // // - envoy.filters.http.ratelimit @@ -150,7 +152,7 @@ type FilterPosition struct { } // EnvoyFilter defines the type of Envoy HTTP filter. -// +kubebuilder:validation:Enum=envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_authn;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.fault;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.wasm;envoy.filters.http.ext_proc +// +kubebuilder:validation:Enum=envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_authn;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.fault;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.wasm;envoy.filters.http.ext_proc;envoy.filters.http.rbac type EnvoyFilter string const ( @@ -183,6 +185,9 @@ const ( // EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter. EnvoyFilterRateLimit EnvoyFilter = "envoy.filters.http.ratelimit" + // EnvoyFilterRBAC defines the Envoy RBAC filter. + EnvoyFilterRBAC EnvoyFilter = "envoy.filters.http.rbac" + // EnvoyFilterRouter defines the Envoy HTTP router filter. EnvoyFilterRouter EnvoyFilter = "envoy.filters.http.router" ) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fd607c16af7..228e31987dd 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -156,14 +156,14 @@ func (in *Authorization) DeepCopyInto(out *Authorization) { *out = *in if in.Rules != nil { in, out := &in.Rules, &out.Rules - *out = make([]Rule, len(*in)) + *out = make([]AuthorizationRule, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.DefaultAction != nil { in, out := &in.DefaultAction, &out.DefaultAction - *out = new(RuleActionType) + *out = new(AuthorizationAction) **out = **in } } @@ -178,6 +178,27 @@ func (in *Authorization) DeepCopy() *Authorization { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationRule) DeepCopyInto(out *AuthorizationRule) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + in.Principal.DeepCopyInto(&out.Principal) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationRule. +func (in *AuthorizationRule) DeepCopy() *AuthorizationRule { + if in == nil { + return nil + } + out := new(AuthorizationRule) + 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 @@ -3330,8 +3351,8 @@ func (in *PerRetryPolicy) DeepCopy() *PerRetryPolicy { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Principal) DeepCopyInto(out *Principal) { *out = *in - if in.ClientCIDR != nil { - in, out := &in.ClientCIDR, &out.ClientCIDR + if in.ClientCIDRs != nil { + in, out := &in.ClientCIDRs, &out.ClientCIDRs *out = make([]string, len(*in)) copy(*out, *in) } @@ -4056,22 +4077,6 @@ 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 - in.Principal.DeepCopyInto(&out.Principal) -} - -// 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 diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 0282aed70d0..0fd7ab97314 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -253,6 +253,9 @@ spec: - envoy.filters.http.wasm + - envoy.filters.http.rbac + + - envoy.filters.http.local_ratelimit @@ -279,6 +282,7 @@ spec: - envoy.filters.http.ratelimit - envoy.filters.http.wasm - envoy.filters.http.ext_proc + - envoy.filters.http.rbac type: string before: description: |- @@ -295,6 +299,7 @@ spec: - envoy.filters.http.ratelimit - envoy.filters.http.wasm - envoy.filters.http.ext_proc + - envoy.filters.http.rbac type: string name: description: Name of the filter. @@ -309,6 +314,7 @@ spec: - envoy.filters.http.ratelimit - envoy.filters.http.wasm - envoy.filters.http.ext_proc + - envoy.filters.http.rbac type: string required: - name 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 919d272cf89..e19360829d2 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -70,7 +70,8 @@ spec: For example, if there are two rules: the first rule allows the request and the second rule denies it, when a request matches both rules, it will be allowed. items: - description: Rule defines the single authorization rule. + description: AuthorizationRule defines the single authorization + rule. properties: action: description: Action defines the action to be taken if the @@ -79,13 +80,17 @@ spec: - Allow - Deny type: string + name: + description: Name is a user-friendly name for the rule. + It's just for display purposes. + type: string principal: description: Principal specifies the client identity of a request. properties: - clientCIDR: + clientCIDRs: description: |- - ClientCIDR is the IP CIDR range of the client. + ClientCIDRs are the IP CIDR ranges of the client. Valid examples are "192.168.1.0/24" or "2001:db8::/64" diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index d29f7d10656..522693bbab4 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -8,7 +8,6 @@ package gatewayapi import ( "fmt" "math" - "net" "sort" "strings" "time" @@ -732,18 +731,12 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) { distinct = true } - ip, ipn, err := net.ParseCIDR(sourceCIDR) + cidrMatch, err := parseCIDR(sourceCIDR) if err != nil { - return nil, fmt.Errorf("unable to translate rateLimit") - } - - mask, _ := ipn.Mask.Size() - irRule.CIDRMatch = &ir.CIDRMatch{ - CIDR: ipn.String(), - IPv6: ip.To4() == nil, - MaskLen: mask, - Distinct: distinct, + return nil, fmt.Errorf("unable to translate rateLimit: %w", err) } + cidrMatch.Distinct = distinct + irRule.CIDRMatch = cidrMatch } } return irRule, nil diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index 2dfc04a1842..ff5f88c39f7 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -8,6 +8,7 @@ package gatewayapi import ( "errors" "fmt" + "net" "strings" v1 "k8s.io/api/core/v1" @@ -459,3 +460,18 @@ func listenersWithSameHTTPPort(xdsIR *ir.Xds, listener *ir.HTTPListener) []strin } return res } + +func parseCIDR(cidr string) (*ir.CIDRMatch, error) { + ip, ipn, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + + mask, _ := ipn.Mask.Size() + return &ir.CIDRMatch{ + CIDR: ipn.String(), + IP: ip.String(), + MaskLen: uint32(mask), + IsIPv6: ip.To4() == nil, + }, nil +} diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index e68cf3fabae..21da9836b9b 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -331,12 +331,13 @@ func (t *Translator) translateSecurityPolicyForRoute( ) error { // Build IR var ( - cors *ir.CORS - jwt *ir.JWT - oidc *ir.OIDC - basicAuth *ir.BasicAuth - extAuth *ir.ExtAuth - err, errs error + cors *ir.CORS + jwt *ir.JWT + oidc *ir.OIDC + basicAuth *ir.BasicAuth + extAuth *ir.ExtAuth + authorization *ir.Authorization + err, errs error ) if policy.Spec.CORS != nil { @@ -371,6 +372,12 @@ func (t *Translator) translateSecurityPolicyForRoute( } } + if policy.Spec.Authorization != nil { + if authorization, err = t.buildAuthorization(policy.Spec.Authorization); err != nil { + errs = errors.Join(errs, err) + } + } + // Apply IR to all relevant routes // Note: there are multiple features in a security policy, even if some of them // are invalid, we still want to apply the valid ones. @@ -384,11 +391,12 @@ func (t *Translator) translateSecurityPolicyForRoute( // This security policy matches the current route. // It should only be accepted if it doesn't match any other route r.Security = &ir.SecurityFeatures{ - CORS: cors, - JWT: jwt, - OIDC: oidc, - BasicAuth: basicAuth, - ExtAuth: extAuth, + CORS: cors, + JWT: jwt, + OIDC: oidc, + BasicAuth: basicAuth, + ExtAuth: extAuth, + Authorization: authorization, } } } @@ -403,12 +411,13 @@ func (t *Translator) translateSecurityPolicyForGateway( ) error { // Build IR var ( - cors *ir.CORS - jwt *ir.JWT - oidc *ir.OIDC - basicAuth *ir.BasicAuth - extAuth *ir.ExtAuth - err, errs error + cors *ir.CORS + jwt *ir.JWT + oidc *ir.OIDC + basicAuth *ir.BasicAuth + extAuth *ir.ExtAuth + authorization *ir.Authorization + err, errs error ) if policy.Spec.CORS != nil { @@ -443,6 +452,11 @@ func (t *Translator) translateSecurityPolicyForGateway( } } + if policy.Spec.Authorization != nil { + if authorization, err = t.buildAuthorization(policy.Spec.Authorization); err != nil { + errs = errors.Join(errs, err) + } + } // Apply IR to all the routes within the specific Gateway that originated // from the gateway to which this security policy was attached. // If the feature is already set, then skip it, since it must have be @@ -470,11 +484,12 @@ func (t *Translator) translateSecurityPolicyForGateway( } r.Security = &ir.SecurityFeatures{ - CORS: cors, - JWT: jwt, - OIDC: oidc, - BasicAuth: basicAuth, - ExtAuth: extAuth, + CORS: cors, + JWT: jwt, + OIDC: oidc, + BasicAuth: basicAuth, + ExtAuth: extAuth, + Authorization: authorization, } } } @@ -846,3 +861,34 @@ func irConfigName(policy *egv1a1.SecurityPolicy) string { strings.ToLower(KindSecurityPolicy), utils.NamespacedName(policy).String()) } + +func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*ir.Authorization, error) { + var ( + irAuth = &ir.Authorization{} + defaultAction = egv1a1.AuthorizationActionDeny + ) + + if authorization.DefaultAction != nil { + defaultAction = *authorization.DefaultAction + } + irAuth.DefaultAction = defaultAction + + for _, rule := range authorization.Rules { + principal := ir.Principal{} + + for _, cidr := range rule.Principal.ClientCIDRs { + cidrMatch, err := parseCIDR(cidr) + if err != nil { + return nil, fmt.Errorf("unable to translate authorization rule: %w", err) + } + + principal.ClientCIDRs = append(principal.ClientCIDRs, cidrMatch) + } + irAuth.Rules = append(irAuth.Rules, &ir.AuthorizationRule{ + Action: rule.Action, + Principal: principal, + }) + } + + return irAuth, nil +} diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml index 7e3d794b7c6..695ab6bd707 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml @@ -196,7 +196,8 @@ xdsIR: - cidrMatch: cidr: 192.168.0.0/16 distinct: false - ipv6: false + ip: 192.168.0.0 + isIPv6: false maskLen: 16 headerMatches: - distinct: false diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml index 9ebb100f64a..b7508bc79a7 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml @@ -199,7 +199,8 @@ xdsIR: - cidrMatch: cidr: 192.168.0.0/16 distinct: false - ipv6: false + ip: 192.168.0.0 + isIPv6: false maskLen: 16 headerMatches: - distinct: false diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml index 3907527966b..bdb547ffea1 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml @@ -338,7 +338,8 @@ xdsIR: - cidrMatch: cidr: 192.168.0.0/16 distinct: true - ipv6: false + ip: 192.168.0.0 + isIPv6: false maskLen: 16 headerMatches: [] limit: diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml new file mode 100644 index 00000000000..57ed9a378b2 --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml @@ -0,0 +1,131 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-3 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/test" + backendRefs: + - name: service-1 + port: 8080 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway # This policy should attach httproute-2 + uid: b8284d0f-de82-4c65-b204-96a0d3f258a1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - 10.0.1.0/24 + - 10.0.2.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-http-route-1 # This policy should attach httproute-1 + uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + authorization: + defaultAction: Allow + rules: + - name: "deny-location-1" + action: Deny + principal: + clientCIDRs: + - 192.168.1.0/24 + - 192.168.2.0/24 + - name: "deny-location-2" + action: Deny + principal: + clientCIDRs: + - 10.75.1.0/24 + - 10.75.2.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-http-route-3 # This policy should attach httproute-3 + uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-3 + authorization: {} diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml new file mode 100755 index 00000000000..6e14ce3e56b --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml @@ -0,0 +1,399 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 3 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-3 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /test + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route-1 + namespace: default + uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 + spec: + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-location-1 + principal: + clientCIDRs: + - 192.168.1.0/24 + - 192.168.2.0/24 + - action: Deny + name: deny-location-2 + principal: + clientCIDRs: + - 10.75.1.0/24 + - 10.75.2.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route-3 + namespace: default + uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 + spec: + authorization: + defaultAction: null + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-3 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: envoy-gateway + uid: b8284d0f-de82-4c65-b204-96a0d3f258a1 + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: null + principal: + clientCIDRs: + - 10.0.1.0/24 + - 10.0.2.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being overridden by other securityPolicies for these + routes: [default/httproute-1 default/httproute-3]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /test + security: + authorization: + defaultAction: Deny + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + security: + authorization: + defaultAction: Allow + rules: + - action: Deny + principal: + clientCIDRs: + - cidr: 192.168.1.0/24 + distinct: false + ip: 192.168.1.0 + isIPv6: false + maskLen: 24 + - cidr: 192.168.2.0/24 + distinct: false + ip: 192.168.2.0 + isIPv6: false + maskLen: 24 + - action: Deny + principal: + clientCIDRs: + - cidr: 10.75.1.0/24 + distinct: false + ip: 10.75.1.0 + isIPv6: false + maskLen: 24 + - cidr: 10.75.2.0/24 + distinct: false + ip: 10.75.2.0 + isIPv6: false + maskLen: 24 + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar + security: + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - cidr: 10.0.1.0/24 + distinct: false + ip: 10.0.1.0 + isIPv6: false + maskLen: 24 + - cidr: 10.0.2.0/24 + distinct: false + ip: 10.0.2.0 + isIPv6: false + maskLen: 24 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index b77d26cf759..f1e1d293d7d 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -544,6 +544,8 @@ type SecurityFeatures struct { BasicAuth *BasicAuth `json:"basicAuth,omitempty" yaml:"basicAuth,omitempty"` // ExtAuth defines the schema for the external authorization. ExtAuth *ExtAuth `json:"extAuth,omitempty" yaml:"extAuth,omitempty"` + // Authorization defines the schema for the authorization. + Authorization *Authorization `json:"authorization,omitempty" yaml:"authorization,omitempty"` } func (s *SecurityFeatures) Printable() *SecurityFeatures { @@ -753,6 +755,36 @@ type GRPCExtAuthService struct { Authority string `json:"authority"` } +// Authorization defines the schema for the authorization. +// +// +k8s:deepcopy-gen=true +type Authorization struct { + // Rules defines the authorization rules. + Rules []*AuthorizationRule `json:"rules,omitempty"` + + // DefaultAction defines the default action to be taken if no rules match. + DefaultAction egv1a1.AuthorizationAction `json:"defaultAction"` +} + +// AuthorizationRule defines the schema for the authorization rule. +// +// +k8s:deepcopy-gen=true +type AuthorizationRule struct { + // Action defines the action to be taken if the rule matches. + Action egv1a1.AuthorizationAction `json:"action"` + + // Principal defines the principal to be matched. + Principal Principal `json:"principal"` +} + +// Principal defines the schema for the principal. +// +// +k8s:deepcopy-gen=true +type Principal struct { + // ClientCIDRs defines the client CIDRs to be matched. + ClientCIDRs []*CIDRMatch `json:"clientCIDRs,omitempty"` +} + // FaultInjection defines the schema for injecting faults into requests. // // +k8s:deepcopy-gen=true @@ -1430,8 +1462,9 @@ type RateLimitRule struct { type CIDRMatch struct { CIDR string `json:"cidr" yaml:"cidr"` - IPv6 bool `json:"ipv6" yaml:"ipv6"` - MaskLen int `json:"maskLen" yaml:"maskLen"` + IP string `json:"ip" yaml:"ip"` + MaskLen uint32 `json:"maskLen" yaml:"maskLen"` + IsIPv6 bool `json:"isIPv6" yaml:"isIPv6"` // Distinct means that each IP Address within the specified Source IP CIDR is treated as a distinct client selector // and uses a separate rate limit bucket/counter. Distinct bool `json:"distinct" yaml:"distinct"` diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 0024414525f..b72f7f06498 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -124,6 +124,48 @@ func (in *AddHeader) DeepCopy() *AddHeader { 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([]*AuthorizationRule, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(AuthorizationRule) + (*in).DeepCopyInto(*out) + } + } + } +} + +// 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 *AuthorizationRule) DeepCopyInto(out *AuthorizationRule) { + *out = *in + in.Principal.DeepCopyInto(&out.Principal) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationRule. +func (in *AuthorizationRule) DeepCopy() *AuthorizationRule { + if in == nil { + return nil + } + out := new(AuthorizationRule) + 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 @@ -1573,6 +1615,32 @@ func (in *PerRetryPolicy) DeepCopy() *PerRetryPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Principal) DeepCopyInto(out *Principal) { + *out = *in + if in.ClientCIDRs != nil { + in, out := &in.ClientCIDRs, &out.ClientCIDRs + *out = make([]*CIDRMatch, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(CIDRMatch) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Principal. +func (in *Principal) DeepCopy() *Principal { + if in == nil { + return nil + } + out := new(Principal) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyInfra) DeepCopyInto(out *ProxyInfra) { *out = *in @@ -1915,6 +1983,11 @@ func (in *SecurityFeatures) DeepCopyInto(out *SecurityFeatures) { *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 SecurityFeatures. diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go new file mode 100644 index 00000000000..4a2629cf177 --- /dev/null +++ b/internal/xds/translator/authorization.go @@ -0,0 +1,266 @@ +// 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 translator + +import ( + "errors" + "fmt" + + cncfv3 "github.com/cncf/xds/go/xds/core/v3" + matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3" + configv3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + rbacconfigv3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" + routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + rbacv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3" + hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + networkinput "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/network/v3" + ipmatcherv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/ip/v3" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/wrapperspb" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/xds/types" +) + +func init() { + registerHTTPFilter(&rbac{}) +} + +type rbac struct{} + +var _ httpFilter = &rbac{} + +// patchHCM builds and appends the RBAC Filter to the HTTP Connection Manager if +// applicable. +func (*rbac) patchHCM( + mgr *hcmv3.HttpConnectionManager, + irListener *ir.HTTPListener, +) error { + if mgr == nil { + return errors.New("hcm is nil") + } + + if irListener == nil { + return errors.New("ir listener is nil") + } + + if !listenerContainsRBAC(irListener) { + return nil + } + + // Return early if filter already exists. + for _, f := range mgr.HttpFilters { + if f.Name == string(egv1a1.EnvoyFilterRBAC) { + return nil + } + } + + rbacFilter, err := buildHCMRBACFilter() + if err != nil { + return err + } + + // Ensure the RBAC filter is the first one in the filter chain. + mgr.HttpFilters = append([]*hcmv3.HttpFilter{rbacFilter}, mgr.HttpFilters...) + + return nil +} + +// buildHCMRBACFilter returns a RBAC filter from the provided IR listener. +func buildHCMRBACFilter() (*hcmv3.HttpFilter, error) { + rbacProto := &rbacv3.RBAC{} + rbacAny, err := anypb.New(rbacProto) + if err != nil { + return nil, err + } + + return &hcmv3.HttpFilter{ + Name: string(egv1a1.EnvoyFilterRBAC), + ConfigType: &hcmv3.HttpFilter_TypedConfig{ + TypedConfig: rbacAny, + }, + }, nil +} + +// listenerContainsRBAC returns true if the provided listener has RBAC +// policies attached to its routes. +func listenerContainsRBAC(irListener *ir.HTTPListener) bool { + if irListener == nil { + return false + } + + for _, route := range irListener.Routes { + if route.Security != nil && route.Security.Authorization != nil { + return true + } + } + + return false +} + +// patchRoute patches the provided route with the RBAC config if applicable. +func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { + if route == nil { + return errors.New("xds route is nil") + } + if irRoute == nil { + return errors.New("ir route is nil") + } + if irRoute.Security == nil || irRoute.Security.Authorization == nil { + return nil + } + + filterCfg := route.GetTypedPerFilterConfig() + if _, ok := filterCfg[string(egv1a1.EnvoyFilterRBAC)]; ok { + // This should not happen since this is the only place where the RBAC + // filter is added in a route. + return fmt.Errorf("route already contains rbac config: %+v", route) + } + + var ( + authorization = irRoute.Security.Authorization + allowAction *anypb.Any + denyAction *anypb.Any + sourceIPInput *anypb.Any + ipMatcher *anypb.Any + matcherList []*matcherv3.Matcher_MatcherList_FieldMatcher + err error + ) + + allow := &rbacconfigv3.Action{ + Name: "ALLOW", + Action: rbacconfigv3.RBAC_ALLOW, + } + if allowAction, err = anypb.New(allow); err != nil { + return err + } + + deny := &rbacconfigv3.Action{ + Name: "DENY", + Action: rbacconfigv3.RBAC_DENY, + } + if denyAction, err = anypb.New(deny); err != nil { + return err + } + + // Build a list of matchers based on the rules. + // The matchers will be evaluated in order, and the first one that matches + // will be used to determine the action, the rest of the matchers will be + // skipped. + // If no matcher matches, the default action will be used. + for _, rule := range authorization.Rules { + // Build the IPMatcher based on the client CIDRs. + ipRangeMatcher := &ipmatcherv3.Ip{ + StatPrefix: "source_ip", + } + + for _, cidr := range rule.Principal.ClientCIDRs { + ipRangeMatcher.CidrRanges = append(ipRangeMatcher.CidrRanges, &configv3.CidrRange{ + AddressPrefix: cidr.IP, + PrefixLen: &wrapperspb.UInt32Value{ + Value: cidr.MaskLen, + }, + }) + } + + if ipMatcher, err = anypb.New(ipRangeMatcher); err != nil { + return err + } + + if sourceIPInput, err = anypb.New(&networkinput.SourceIPInput{}); err != nil { + return err + } + + // Determine the action for the current rule. + ruleAction := allowAction + if rule.Action == egv1a1.AuthorizationActionDeny { + ruleAction = denyAction + } + + // Add the matcher generated with the current rule to the matcher list. + matcherList = append(matcherList, &matcherv3.Matcher_MatcherList_FieldMatcher{ + Predicate: &matcherv3.Matcher_MatcherList_Predicate{ + MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{ + SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{ + Input: &cncfv3.TypedExtensionConfig{ + Name: "source_ip", + TypedConfig: sourceIPInput, + }, + Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{ + CustomMatch: &cncfv3.TypedExtensionConfig{ + Name: "ip_matcher", + TypedConfig: ipMatcher, + }, + }, + }, + }, + }, + OnMatch: &matcherv3.Matcher_OnMatch{ + OnMatch: &matcherv3.Matcher_OnMatch_Action{ + Action: &cncfv3.TypedExtensionConfig{ + Name: "action", + TypedConfig: ruleAction, + }, + }, + }, + }) + } + + // Set the default action. + defaultAction := denyAction + if authorization.DefaultAction == egv1a1.AuthorizationActionAllow { + defaultAction = allowAction + } + + routeCfgProto := &rbacv3.RBACPerRoute{ + Rbac: &rbacv3.RBAC{ + Matcher: &matcherv3.Matcher{ + MatcherType: &matcherv3.Matcher_MatcherList_{ + MatcherList: &matcherv3.Matcher_MatcherList{ + Matchers: matcherList, + }, + }, + OnNoMatch: &matcherv3.Matcher_OnMatch{ + OnMatch: &matcherv3.Matcher_OnMatch_Action{ + Action: &cncfv3.TypedExtensionConfig{ + Name: "default", + TypedConfig: defaultAction, + }, + }, + }, + }, + }, + } + + // If there are no matchers, the default action will be used for all requests. + // Setting the matcher type to nil since Proto validation will fail if the list + // is empty. + if len(matcherList) == 0 { + routeCfgProto.Rbac.Matcher.MatcherType = nil + } + + if err = routeCfgProto.ValidateAll(); err != nil { + return err + } + + routeCfgAny, err := anypb.New(routeCfgProto) + if err != nil { + return err + } + + if filterCfg == nil { + route.TypedPerFilterConfig = make(map[string]*anypb.Any) + } + + route.TypedPerFilterConfig[string(egv1a1.EnvoyFilterRBAC)] = routeCfgAny + + return nil +} + +func (c *rbac) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error { + return nil +} diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go index 0d8edfce242..3961a0b13b0 100644 --- a/internal/xds/translator/extauth.go +++ b/internal/xds/translator/extauth.go @@ -37,7 +37,6 @@ var _ httpFilter = &extAuth{} // if applicable, and it does not already exist. // Note: this method creates an ext_authz filter for each route that contains an ExtAuthz config. // The filter is disabled by default. It is enabled on the route level. -// TODO: zhaohuabing avoid duplicated HTTP filters func (*extAuth) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { var errs error diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go index 5738d621eb8..1c50b5b52a2 100644 --- a/internal/xds/translator/httpfilters.go +++ b/internal/xds/translator/httpfilters.go @@ -17,7 +17,7 @@ import ( "github.com/envoyproxy/go-control-plane/pkg/wellknown" "k8s.io/utils/ptr" - "github.com/envoyproxy/gateway/api/v1alpha1" + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/xds/filters" "github.com/envoyproxy/gateway/internal/xds/types" @@ -94,6 +94,7 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter { // Set a rational order for all the filters. // When the fault filter is configured to be at the first, the computation of // the remaining filters is skipped when rejected early + // TODO (zhaohuabing): remove duplicate filter type constants and replace them with the type constants in the api package switch { case isFilterType(filter, wellknown.Fault): order = 1 @@ -111,12 +112,14 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter { order = 7 + mustGetFilterIndex(filter.Name) case isFilterType(filter, wasmFilter): order = 100 + mustGetFilterIndex(filter.Name) - case isFilterType(filter, localRateLimitFilter): + case isFilterType(filter, string(egv1a1.EnvoyFilterRBAC)): order = 201 - case isFilterType(filter, wellknown.HTTPRateLimit): + case isFilterType(filter, localRateLimitFilter): order = 202 - case isFilterType(filter, wellknown.Router): + case isFilterType(filter, wellknown.HTTPRateLimit): order = 203 + case isFilterType(filter, wellknown.Router): + order = 204 } return &OrderedHTTPFilter{ @@ -144,7 +147,7 @@ func (o OrderedHTTPFilters) Swap(i, j int) { // For example, the cors filter should be put at the first to avoid unnecessary // processing of other filters for unauthorized cross-region access. // The router filter must be the last one since it's a terminal filter. -func sortHTTPFilters(filters []*hcmv3.HttpFilter, filterOrder []v1alpha1.FilterPosition) []*hcmv3.HttpFilter { +func sortHTTPFilters(filters []*hcmv3.HttpFilter, filterOrder []egv1a1.FilterPosition) []*hcmv3.HttpFilter { // Sort the filters in the default order. orderedFilters := make(OrderedHTTPFilters, len(filters)) for i := 0; i < len(filters); i++ { diff --git a/internal/xds/translator/httpfilters_test.go b/internal/xds/translator/httpfilters_test.go index 2f277aa969a..5516dc0d95b 100644 --- a/internal/xds/translator/httpfilters_test.go +++ b/internal/xds/translator/httpfilters_test.go @@ -13,14 +13,14 @@ import ( "github.com/stretchr/testify/assert" "k8s.io/utils/ptr" - "github.com/envoyproxy/gateway/api/v1alpha1" + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) func Test_sortHTTPFilters(t *testing.T) { tests := []struct { name string filters []*hcmv3.HttpFilter - filterOrder []v1alpha1.FilterPosition + filterOrder []egv1a1.FilterPosition want []*hcmv3.HttpFilter }{ { @@ -40,6 +40,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, want: []*hcmv3.HttpFilter{ httpFilterForTest(wellknown.Fault), @@ -53,6 +54,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), @@ -75,15 +77,16 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterFault, - After: ptr.To(v1alpha1.EnvoyFilterCORS), + Name: egv1a1.EnvoyFilterFault, + After: ptr.To(egv1a1.EnvoyFilterCORS), }, { - Name: v1alpha1.EnvoyFilterRateLimit, - Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn), + Name: egv1a1.EnvoyFilterRateLimit, + Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn), }, }, want: []*hcmv3.HttpFilter{ @@ -99,6 +102,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.Router), }, @@ -120,11 +124,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterRateLimit, - Before: ptr.To(v1alpha1.EnvoyFilterWasm), + Name: egv1a1.EnvoyFilterRateLimit, + Before: ptr.To(egv1a1.EnvoyFilterWasm), }, }, want: []*hcmv3.HttpFilter{ @@ -140,6 +145,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.Router), }, @@ -161,11 +167,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterJWTAuthn, - After: ptr.To(v1alpha1.EnvoyFilterWasm), + Name: egv1a1.EnvoyFilterJWTAuthn, + After: ptr.To(egv1a1.EnvoyFilterWasm), }, }, want: []*hcmv3.HttpFilter{ @@ -180,6 +187,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), httpFilterForTest(jwtAuthn), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), @@ -202,11 +210,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterWasm, - Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn), + Name: egv1a1.EnvoyFilterWasm, + Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn), }, }, want: []*hcmv3.HttpFilter{ @@ -221,6 +230,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(jwtAuthn), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), @@ -243,11 +253,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterWasm, - After: ptr.To(v1alpha1.EnvoyFilterRateLimit), + Name: egv1a1.EnvoyFilterWasm, + After: ptr.To(egv1a1.EnvoyFilterRateLimit), }, }, want: []*hcmv3.HttpFilter{ @@ -259,6 +270,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(jwtAuthn), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), @@ -284,11 +296,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterWasm, - Before: ptr.To(v1alpha1.EnvoyFilterExtProc), + Name: egv1a1.EnvoyFilterWasm, + Before: ptr.To(egv1a1.EnvoyFilterExtProc), }, }, want: []*hcmv3.HttpFilter{ @@ -303,6 +316,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), @@ -325,11 +339,12 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterExtProc, - After: ptr.To(v1alpha1.EnvoyFilterWasm), + Name: egv1a1.EnvoyFilterExtProc, + After: ptr.To(egv1a1.EnvoyFilterWasm), }, }, want: []*hcmv3.HttpFilter{ @@ -344,6 +359,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), @@ -366,23 +382,24 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"), httpFilterForTest(localRateLimitFilter), httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), }, - filterOrder: []v1alpha1.FilterPosition{ + filterOrder: []egv1a1.FilterPosition{ { - Name: v1alpha1.EnvoyFilterLocalRateLimit, - Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn), + Name: egv1a1.EnvoyFilterLocalRateLimit, + Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn), }, { - Name: v1alpha1.EnvoyFilterLocalRateLimit, - After: ptr.To(v1alpha1.EnvoyFilterCORS), + Name: egv1a1.EnvoyFilterLocalRateLimit, + After: ptr.To(egv1a1.EnvoyFilterCORS), }, { - Name: v1alpha1.EnvoyFilterWasm, - Before: ptr.To(v1alpha1.EnvoyFilterOAuth2), + Name: egv1a1.EnvoyFilterWasm, + Before: ptr.To(egv1a1.EnvoyFilterOAuth2), }, { - Name: v1alpha1.EnvoyFilterExtProc, - Before: ptr.To(v1alpha1.EnvoyFilterWasm), + Name: egv1a1.EnvoyFilterExtProc, + Before: ptr.To(egv1a1.EnvoyFilterWasm), }, }, want: []*hcmv3.HttpFilter{ @@ -398,6 +415,7 @@ func Test_sortHTTPFilters(t *testing.T) { httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"), httpFilterForTest(oauth2Filter + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(jwtAuthn), + httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"), httpFilterForTest(wellknown.HTTPRateLimit), httpFilterForTest(wellknown.Router), }, diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go index 1a0d88d5da0..231bda44f60 100644 --- a/internal/xds/translator/local_ratelimit.go +++ b/internal/xds/translator/local_ratelimit.go @@ -249,8 +249,8 @@ func buildRouteLocalRateLimits(local *ir.LocalRateLimit) ( // Setup MaskedRemoteAddress action mra := &routev3.RateLimit_Action_MaskedRemoteAddress{} - maskLen := &wrapperspb.UInt32Value{Value: uint32(rule.CIDRMatch.MaskLen)} - if rule.CIDRMatch.IPv6 { + maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen} + if rule.CIDRMatch.IsIPv6 { mra.V6PrefixMaskLen = maskLen } else { mra.V4PrefixMaskLen = maskLen diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 4534f303fe2..1167b6e0c71 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -225,8 +225,8 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [ if rule.CIDRMatch != nil { // Setup MaskedRemoteAddress action mra := &routev3.RateLimit_Action_MaskedRemoteAddress{} - maskLen := &wrapperspb.UInt32Value{Value: uint32(rule.CIDRMatch.MaskLen)} - if rule.CIDRMatch.IPv6 { + maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen} + if rule.CIDRMatch.IsIPv6 { mra.V6PrefixMaskLen = maskLen } else { mra.V4PrefixMaskLen = maskLen diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml new file mode 100644 index 00000000000..738b757d112 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml @@ -0,0 +1,109 @@ +http: +- address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /test + security: + authorization: + defaultAction: Deny + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + security: + authorization: + defaultAction: Allow + rules: + - action: Deny + principal: + clientCIDRs: + - cidr: 192.168.1.0/24 + distinct: false + ip: 192.168.1.0 + isIPv6: false + maskLen: 24 + - cidr: 192.168.2.0/24 + distinct: false + ip: 192.168.2.0 + isIPv6: false + maskLen: 24 + - action: Deny + principal: + clientCIDRs: + - cidr: 10.75.1.0/24 + distinct: false + ip: 10.75.1.0 + isIPv6: false + maskLen: 24 + - cidr: 10.75.2.0/24 + distinct: false + ip: 10.75.2.0 + isIPv6: false + maskLen: 24 + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar + security: + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - cidr: 10.0.1.0/24 + distinct: false + ip: 10.0.1.0 + isIPv6: false + maskLen: 24 + - cidr: 10.0.2.0/24 + distinct: false + ip: 10.0.2.0 + isIPv6: false + maskLen: 24 diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml new file mode 100644 index 00000000000..b3f75f0e04e --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml @@ -0,0 +1,51 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-3/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-3/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-1/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-1/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-2/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-2/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml new file mode 100644 index 00000000000..24596d841a3 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml @@ -0,0 +1,36 @@ +- clusterName: httproute/default/httproute-3/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-3/rule/0/backend/0 +- clusterName: httproute/default/httproute-1/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-1/rule/0/backend/0 +- clusterName: httproute/default/httproute-2/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-2/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml new file mode 100644 index 00000000000..7469999607f --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml @@ -0,0 +1,37 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.rbac + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: envoy-gateway/gateway-1/http + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + drainType: MODIFY_ONLY + name: envoy-gateway/gateway-1/http + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml new file mode 100644 index 00000000000..9d0cc127036 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml @@ -0,0 +1,135 @@ +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http/www_example_com + routes: + - match: + pathSeparatedPrefix: /test + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-3/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.rbac: + '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute + rbac: + matcher: + onNoMatch: + action: + name: default + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + action: DENY + name: DENY + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-1/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.rbac: + '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute + rbac: + matcher: + matcherList: + matchers: + - onMatch: + action: + name: action + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + action: DENY + name: DENY + predicate: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 192.168.1.0 + prefixLen: 24 + - addressPrefix: 192.168.2.0 + prefixLen: 24 + statPrefix: source_ip + input: + name: source_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + - onMatch: + action: + name: action + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + action: DENY + name: DENY + predicate: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.75.1.0 + prefixLen: 24 + - addressPrefix: 10.75.2.0 + prefixLen: 24 + statPrefix: source_ip + input: + name: source_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + onNoMatch: + action: + name: default + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + name: ALLOW + - match: + pathSeparatedPrefix: /bar + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-2/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.rbac: + '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute + rbac: + matcher: + matcherList: + matchers: + - onMatch: + action: + name: action + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + name: ALLOW + predicate: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.0.1.0 + prefixLen: 24 + - addressPrefix: 10.0.2.0 + prefixLen: 24 + statPrefix: source_ip + input: + name: source_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + onNoMatch: + action: + name: default + typedConfig: + '@type': type.googleapis.com/envoy.config.rbac.v3.Action + action: DENY + name: DENY diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 6ac4fe6b123..618b78267ad 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -195,13 +195,48 @@ _Appears in:_ Authorization defines the authorization configuration. + +Note: if neither `Rules` nor `DefaultAction` is specified, the default action is to deny all requests. + _Appears in:_ - [SecurityPolicySpec](#securitypolicyspec) | Field | Type | Required | Description | | --- | --- | --- | --- | -| `rules` | _[Rule](#rule) array_ | false | Rules defines a list of authorization rules.
These rules are evaluated in order, the first matching rule will be applied,
and the rest will be skipped.

For example, if there are two rules: the first rule allows the request
and the second rule denies it, when a request matches both rules, it will be allowed. | -| `defaultAction` | _[RuleActionType](#ruleactiontype)_ | false | DefaultAction defines the default action to be taken if no rules match.
If not specified, the default action is Deny. | +| `rules` | _[AuthorizationRule](#authorizationrule) array_ | false | Rules defines a list of authorization rules.
These rules are evaluated in order, the first matching rule will be applied,
and the rest will be skipped.

For example, if there are two rules: the first rule allows the request
and the second rule denies it, when a request matches both rules, it will be allowed. | +| `defaultAction` | _[AuthorizationAction](#authorizationaction)_ | false | DefaultAction defines the default action to be taken if no rules match.
If not specified, the default action is Deny. | + + +#### AuthorizationAction + +_Underlying type:_ _string_ + +AuthorizationAction defines the action to be taken if a rule matches. + +_Appears in:_ +- [Authorization](#authorization) +- [AuthorizationRule](#authorizationrule) + +| Value | Description | +| ----- | ----------- | +| `Allow` | AuthorizationActionAllow is the action to allow the request.
| +| `Deny` | AuthorizationActionDeny is the action to deny the request.
| + + +#### AuthorizationRule + + + +AuthorizationRule defines the single authorization rule. + +_Appears in:_ +- [Authorization](#authorization) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | _string_ | false | Name is a user-friendly name for the rule. It's just for display purposes. | +| `action` | _[AuthorizationAction](#authorizationaction)_ | true | Action defines the action to be taken if the rule matches. | +| `principal` | _[Principal](#principal)_ | true | Principal specifies the client identity of a request. | #### BackOffPolicy @@ -823,6 +858,7 @@ _Appears in:_ | `envoy.filters.http.wasm` | EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.
| | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.
| | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.
| +| `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.
| | `envoy.filters.http.router` | EnvoyFilterRouter defines the Envoy HTTP router filter.
| @@ -1269,7 +1305,7 @@ _Appears in:_ | `extraArgs` | _string array_ | false | ExtraArgs defines additional command line options that are provided to Envoy.
More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options
Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. | | `mergeGateways` | _boolean_ | false | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.
Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.
This means that the port, protocol and hostname tuple must be unique for every listener.
If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. | | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ | false | Shutdown defines configuration for graceful envoy shutdown process. | -| `filterOrder` | _[FilterPosition](#filterposition) array_ | false | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.
The FilterPosition in the list will be applied in the order they are defined.
If unspecified, the default filter order is applied.
Default filter order is:

- envoy.filters.http.fault

- envoy.filters.http.cors

- envoy.filters.http.ext_authz

- envoy.filters.http.basic_authn

- envoy.filters.http.oauth2

- envoy.filters.http.jwt_authn

- envoy.filters.http.ext_proc

- envoy.filters.http.wasm

- envoy.filters.http.local_ratelimit

- envoy.filters.http.ratelimit

- envoy.filters.http.router | +| `filterOrder` | _[FilterPosition](#filterposition) array_ | false | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.
The FilterPosition in the list will be applied in the order they are defined.
If unspecified, the default filter order is applied.
Default filter order is:

- envoy.filters.http.fault

- envoy.filters.http.cors

- envoy.filters.http.ext_authz

- envoy.filters.http.basic_authn

- envoy.filters.http.oauth2

- envoy.filters.http.jwt_authn

- envoy.filters.http.ext_proc

- envoy.filters.http.wasm

- envoy.filters.http.rbac

- envoy.filters.http.local_ratelimit

- envoy.filters.http.ratelimit

- envoy.filters.http.router | | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ | false | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.
These settings are applied on backends for which TLS policies are specified. | @@ -2399,11 +2435,11 @@ _Appears in:_ Principal specifies the client identity of a request. _Appears in:_ -- [Rule](#rule) +- [AuthorizationRule](#authorizationrule) | Field | Type | Required | Description | | --- | --- | --- | --- | -| `clientCIDR` | _string array_ | true | ClientCIDR is the IP CIDR range of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.
You can use the `EnableProxyProtocol` and `ClientIPDetection` options in
the `ClientTrafficPolicy` to configure how the client IP is detected. | +| `clientCIDRs` | _string array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.
You can use the `EnableProxyProtocol` and `ClientIPDetection` options in
the `ClientTrafficPolicy` to configure how the client IP is detected. | #### ProcessingModeOptions @@ -3037,37 +3073,6 @@ _Appears in:_ | `httpStatusCodes` | _[HTTPStatus](#httpstatus) array_ | false | HttpStatusCodes specifies the http status codes to be retried.
The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. | -#### Rule - - - -Rule defines the single authorization rule. - -_Appears in:_ -- [Authorization](#authorization) - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `action` | _[RuleActionType](#ruleactiontype)_ | true | Action defines the action to be taken if the rule matches. | -| `principal` | _[Principal](#principal)_ | true | Principal specifies the client identity of a request. | - - -#### RuleActionType - -_Underlying type:_ _string_ - -RuleActionType specifies the types of authorization rule action. - -_Appears in:_ -- [Authorization](#authorization) -- [Rule](#rule) - -| Value | Description | -| ----- | ----------- | -| `Allow` | Allow is the action to allow the request.
| -| `Deny` | Deny is the action to deny the request.
| - - #### SecurityPolicy diff --git a/test/e2e/testdata/authorization-client-ip.yaml b/test/e2e/testdata/authorization-client-ip.yaml new file mode 100644 index 00000000000..dde54a832cd --- /dev/null +++ b/test/e2e/testdata/authorization-client-ip.yaml @@ -0,0 +1,98 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-authorization-client-ip-1 + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: Exact + value: /protected1 + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-authorization-client-ip-2 + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: Exact + value: /protected2 + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-client-ip-1 + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-authorization-client-ip-1 + authorization: + defaultAction: Allow + rules: + - name: "deny-location-1" # First matching rule is applied, so 192.168.1.0/24 will be denied + action: Deny + principal: + clientCIDRs: + - 192.168.1.0/24 + - name: "allow-location-1" + action: Allow + principal: + clientCIDRs: + - 192.168.1.0/24 + - 192.168.2.0/24 # First matching rule is applied, so 12.168.2.0/24 will be allowed + - name: "deny-location-2" + action: Allow + principal: + clientCIDRs: + - 192.168.2.0/24 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-client-ip-2 + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-authorization-client-ip-2 + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - 10.0.1.0/24 + - 10.0.2.0/24 +--- +# This is a client traffic policy that enables client IP detection using a custom header. +# So, the client IP can be detected from the custom header `client-ip` and used for authorization. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-client-ip-detection + namespace: gateway-conformance-infra +spec: + clientIPDetection: + customHeader: + name: client-ip + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace diff --git a/test/e2e/testdata/authorization-default-action.yaml b/test/e2e/testdata/authorization-default-action.yaml new file mode 100644 index 00000000000..bc35b2ba80b --- /dev/null +++ b/test/e2e/testdata/authorization-default-action.yaml @@ -0,0 +1,58 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-authorization-empty + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: Exact + value: /empty-authorization + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-authorization-allow-all + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: Exact + value: /allow-all + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-empty + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-authorization-empty + authorization: {} # An empty authorization policy means deny all since default action is deny +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-allow-all + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-authorization-allow-all + authorization: # Allow all since default action is allow and no rules are defined + defaultAction: Allow diff --git a/test/e2e/testdata/basic-auth.yaml b/test/e2e/testdata/basic-auth.yaml index 036bb788eaa..7bf3e0f6716 100644 --- a/test/e2e/testdata/basic-auth.yaml +++ b/test/e2e/testdata/basic-auth.yaml @@ -59,7 +59,6 @@ spec: group: gateway.networking.k8s.io kind: HTTPRoute name: http-with-basic-auth-1 - namespace: gateway-conformance-infra basicAuth: users: name: "basic-auth-users-secret-1" @@ -74,7 +73,6 @@ spec: group: gateway.networking.k8s.io kind: HTTPRoute name: http-with-basic-auth-2 - namespace: gateway-conformance-infra basicAuth: users: name: "basic-auth-users-secret-2" diff --git a/test/e2e/tests/authorization-client-ip.go b/test/e2e/tests/authorization-client-ip.go new file mode 100644 index 00000000000..1adc1aa25ed --- /dev/null +++ b/test/e2e/tests/authorization-client-ip.go @@ -0,0 +1,174 @@ +// 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. + +//go:build e2e +// +build e2e + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + gwv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi" +) + +func init() { + ConformanceTests = append(ConformanceTests, AuthorizationClientIPTest) +} + +var AuthorizationClientIPTest = suite.ConformanceTest{ + ShortName: "Authorization with client IP", + Description: "Authorization with client IP Allow/Deny list", + Manifests: []string{"testdata/authorization-client-ip.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + route1NN := types.NamespacedName{Name: "http-with-authorization-client-ip-1", Namespace: ns} + route2NN := types.NamespacedName{Name: "http-with-authorization-client-ip-2", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route1NN, route2NN) + + ancestorRef := gwv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwv1.GroupName), + Kind: gatewayapi.KindPtr(gatewayapi.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwv1.ObjectName(gwNN.Name), + } + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-client-ip-1", Namespace: ns}, suite.ControllerName, ancestorRef) + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-client-ip-2", Namespace: ns}, suite.ControllerName, ancestorRef) + + t.Run("first route-denied IP", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/protected1", + Headers: map[string]string{ + "client-ip": "192.168.1.1", // in the denied list + }, + }, + Response: http.Response{ + StatusCode: 403, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + t.Run("first route-allowed IP", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/protected1", + Headers: map[string]string{ + "client-ip": "192.168.2.1", // in the allowed list + }, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + t.Run("first route-default action: allow", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/protected1", + Headers: map[string]string{ + "client-ip": "192.168.3.1", // not in the denied list + }, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + // Test the second route + t.Run("second route-allowed IP", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/protected2", + Headers: map[string]string{ + "client-ip": "10.0.1.1", // in the allowed list + }, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + t.Run("second route-default action: deny", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/protected2", + Headers: map[string]string{ + "client-ip": "192.168.3.1", // not in the allowed list + }, + }, + Response: http.Response{ + StatusCode: 403, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + }, +} diff --git a/test/e2e/tests/authorization-default-action.go b/test/e2e/tests/authorization-default-action.go new file mode 100644 index 00000000000..1a58c32e381 --- /dev/null +++ b/test/e2e/tests/authorization-default-action.go @@ -0,0 +1,92 @@ +// 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. + +//go:build e2e +// +build e2e + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + gwv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi" +) + +func init() { + ConformanceTests = append(ConformanceTests, AuthorizationDefaultActionTest) +} + +var AuthorizationDefaultActionTest = suite.ConformanceTest{ + ShortName: "Authorization with default actions", + Description: "Authorization with default actions", + Manifests: []string{"testdata/authorization-default-action.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + route1NN := types.NamespacedName{Name: "http-with-authorization-empty", Namespace: ns} + route2NN := types.NamespacedName{Name: "http-with-authorization-allow-all", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route1NN, route2NN) + + ancestorRef := gwv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwv1.GroupName), + Kind: gatewayapi.KindPtr(gatewayapi.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwv1.ObjectName(gwNN.Name), + } + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-empty", Namespace: ns}, suite.ControllerName, ancestorRef) + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-allow-all", Namespace: ns}, suite.ControllerName, ancestorRef) + + t.Run("Empty Authorization should deny all traffic", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/empty-authorization", + }, + Response: http.Response{ + StatusCode: 403, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + t.Run("Authorization with empty rules and Allow default action should allow all traffic", func(t *testing.T) { + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/allow-all", + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + }, +} From cf67b782c05de27a4eb4979670acd820c300258e Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Fri, 17 May 2024 19:17:52 -0700 Subject: [PATCH 02/10] use x-forward-for for testing Signed-off-by: huabing zhao --- test/e2e/testdata/authorization-client-ip.yaml | 4 ++-- test/e2e/tests/authorization-client-ip.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/e2e/testdata/authorization-client-ip.yaml b/test/e2e/testdata/authorization-client-ip.yaml index dde54a832cd..e2e731c05e6 100644 --- a/test/e2e/testdata/authorization-client-ip.yaml +++ b/test/e2e/testdata/authorization-client-ip.yaml @@ -90,8 +90,8 @@ metadata: namespace: gateway-conformance-infra spec: clientIPDetection: - customHeader: - name: client-ip + xForwardedFor: + numTrustedHops: 1 targetRef: group: gateway.networking.k8s.io kind: Gateway diff --git a/test/e2e/tests/authorization-client-ip.go b/test/e2e/tests/authorization-client-ip.go index 1adc1aa25ed..e03e07a76a6 100644 --- a/test/e2e/tests/authorization-client-ip.go +++ b/test/e2e/tests/authorization-client-ip.go @@ -50,7 +50,7 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ Request: http.Request{ Path: "/protected1", Headers: map[string]string{ - "client-ip": "192.168.1.1", // in the denied list + "X-Forwarded-For": "192.168.1.1", // in the denied list }, }, Response: http.Response{ @@ -75,7 +75,7 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ Request: http.Request{ Path: "/protected1", Headers: map[string]string{ - "client-ip": "192.168.2.1", // in the allowed list + "X-Forwarded-For": "192.168.2.1", // in the allowed list }, }, Response: http.Response{ @@ -100,7 +100,7 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ Request: http.Request{ Path: "/protected1", Headers: map[string]string{ - "client-ip": "192.168.3.1", // not in the denied list + "X-Forwarded-For": "192.168.3.1", // not in the denied list }, }, Response: http.Response{ @@ -126,7 +126,7 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ Request: http.Request{ Path: "/protected2", Headers: map[string]string{ - "client-ip": "10.0.1.1", // in the allowed list + "X-Forwarded-For": "10.0.1.1", // in the allowed list }, }, Response: http.Response{ @@ -151,7 +151,7 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ Request: http.Request{ Path: "/protected2", Headers: map[string]string{ - "client-ip": "192.168.3.1", // not in the allowed list + "X-Forwarded-For": "192.168.3.1", // not in the allowed list }, }, Response: http.Response{ From 27254e0c5f1f9d7de5a4fa75a8eeb88053bb77e3 Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Sun, 19 May 2024 23:25:05 -0700 Subject: [PATCH 03/10] fix e2e test Signed-off-by: huabing zhao --- api/v1alpha1/authorization_types.go | 10 ++++++--- ...ateway.envoyproxy.io_securitypolicies.yaml | 7 ++++--- internal/gatewayapi/securitypolicy.go | 3 ++- site/content/en/latest/api/extension_types.md | 7 +++++-- test/e2e/tests/authorization-client-ip.go | 21 ++++++++++++++++--- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index c2c6c78d99f..7176b08d7ea 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -25,7 +25,7 @@ type Authorization struct { DefaultAction *AuthorizationAction `json:"defaultAction"` } -// AuthorizationRule defines the single authorization rule. +// AuthorizationRule defines a single authorization rule. type AuthorizationRule struct { // Name is a user-friendly name for the rule. It's just for display purposes. // +optional @@ -39,12 +39,16 @@ type AuthorizationRule struct { } // Principal specifies the client identity of a request. +// A client identity can be a client IP, a JWT claim, username from the Authorization header, +// or any other identity that can be extracted from a custom header. +// Currently, only the client IP is supported. type Principal struct { // ClientCIDRs are the IP CIDR ranges of the client. // Valid examples are "192.168.1.0/24" or "2001:db8::/64" // - // By default, the client IP is inferred from the x-forwarder-for header and proxy protocol. - // You can use the `EnableProxyProtocol` and `ClientIPDetection` options in + // The client IP is inferred from the x-forwarder-for header, a custom header, + // or the proxy protocol. + // You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in // the `ClientTrafficPolicy` to configure how the client IP is detected. ClientCIDRs []string `json:"clientCIDRs,omitempty"` } 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 e19360829d2..1041c204c0d 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -70,7 +70,7 @@ spec: For example, if there are two rules: the first rule allows the request and the second rule denies it, when a request matches both rules, it will be allowed. items: - description: AuthorizationRule defines the single authorization + description: AuthorizationRule defines a single authorization rule. properties: action: @@ -94,8 +94,9 @@ spec: Valid examples are "192.168.1.0/24" or "2001:db8::/64" - By default, the client IP is inferred from the x-forwarder-for header and proxy protocol. - You can use the `EnableProxyProtocol` and `ClientIPDetection` options in + The client IP is inferred from the x-forwarder-for header, a custom header, + or the proxy protocol. + You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in the `ClientTrafficPolicy` to configure how the client IP is detected. items: type: string diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 21da9836b9b..966d4eb1d7b 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -864,7 +864,8 @@ func irConfigName(policy *egv1a1.SecurityPolicy) string { func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*ir.Authorization, error) { var ( - irAuth = &ir.Authorization{} + irAuth = &ir.Authorization{} + // The default action is Deny if not specified defaultAction = egv1a1.AuthorizationActionDeny ) diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 618b78267ad..cc073e25248 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -227,7 +227,7 @@ _Appears in:_ -AuthorizationRule defines the single authorization rule. +AuthorizationRule defines a single authorization rule. _Appears in:_ - [Authorization](#authorization) @@ -2433,13 +2433,16 @@ _Appears in:_ Principal specifies the client identity of a request. +A client identity can be a client IP, a JWT claim, username from the Authorization header, +or any other identity that can be extracted from a custom header. +Currently, only the client IP is supported. _Appears in:_ - [AuthorizationRule](#authorizationrule) | Field | Type | Required | Description | | --- | --- | --- | --- | -| `clientCIDRs` | _string array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.
You can use the `EnableProxyProtocol` and `ClientIPDetection` options in
the `ClientTrafficPolicy` to configure how the client IP is detected. | +| `clientCIDRs` | _string array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

The client IP is inferred from the x-forwarder-for header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
the `ClientTrafficPolicy` to configure how the client IP is detected. | #### ProcessingModeOptions diff --git a/test/e2e/tests/authorization-client-ip.go b/test/e2e/tests/authorization-client-ip.go index 94ee22c6a9f..0a0d2056ab2 100644 --- a/test/e2e/tests/authorization-client-ip.go +++ b/test/e2e/tests/authorization-client-ip.go @@ -78,9 +78,14 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ "X-Forwarded-For": "192.168.2.1", // in the allowed list }, }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/protected1", + Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header + }, + }, Response: http.Response{ StatusCode: 200, - Headers: nil, }, Namespace: ns, } @@ -104,9 +109,14 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ "X-Forwarded-For": "192.168.3.1", // not in the denied list }, }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/protected1", + Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header + }, + }, Response: http.Response{ StatusCode: 200, - Headers: nil, }, Namespace: ns, } @@ -131,9 +141,14 @@ var AuthorizationClientIPTest = suite.ConformanceTest{ "X-Forwarded-For": "10.0.1.1", // in the allowed list }, }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/protected2", + Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header + }, + }, Response: http.Response{ StatusCode: 200, - Headers: nil, }, Namespace: ns, } From a262bd47ec221b8b396e2a101242c17687325802 Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Tue, 21 May 2024 11:37:47 -0700 Subject: [PATCH 04/10] address comments Signed-off-by: huabing zhao --- internal/xds/translator/authorization.go | 1 - internal/xds/translator/cors.go | 1 - internal/xds/translator/jwt.go | 1 - internal/xds/translator/ratelimit.go | 1 - 4 files changed, 4 deletions(-) diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go index 4a2629cf177..d4bc5e46be4 100644 --- a/internal/xds/translator/authorization.go +++ b/internal/xds/translator/authorization.go @@ -64,7 +64,6 @@ func (*rbac) patchHCM( return err } - // Ensure the RBAC filter is the first one in the filter chain. mgr.HttpFilters = append([]*hcmv3.HttpFilter{rbacFilter}, mgr.HttpFilters...) return nil diff --git a/internal/xds/translator/cors.go b/internal/xds/translator/cors.go index f5a83308722..cda5ae8a40a 100644 --- a/internal/xds/translator/cors.go +++ b/internal/xds/translator/cors.go @@ -61,7 +61,6 @@ func (*cors) patchHCM( return err } - // Ensure the CORS filter is the first one in the filter chain. mgr.HttpFilters = append([]*hcmv3.HttpFilter{corsFilter}, mgr.HttpFilters...) return nil diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go index 27697043a46..5a2b35ca696 100644 --- a/internal/xds/translator/jwt.go +++ b/internal/xds/translator/jwt.go @@ -65,7 +65,6 @@ func (*jwt) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen return err } - // Ensure the authn filter is the first and the terminal filter is the last in the chain. mgr.HttpFilters = append([]*hcmv3.HttpFilter{jwtFilter}, mgr.HttpFilters...) return nil diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 1167b6e0c71..86a558b291d 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -69,7 +69,6 @@ func (t *Translator) patchHCMWithRateLimit(mgr *hcmv3.HttpConnectionManager, irL } rateLimitFilter := t.buildRateLimitFilter(irListener) - // Make sure the router filter is the terminal filter in the chain. mgr.HttpFilters = append([]*hcmv3.HttpFilter{rateLimitFilter}, mgr.HttpFilters...) } From ba7ddbe4055ee057181ba56d176edcb876d9cee6 Mon Sep 17 00:00:00 2001 From: Huabing Zhao Date: Thu, 23 May 2024 13:25:55 -0700 Subject: [PATCH 05/10] Update api/v1alpha1/authorization_types.go Co-authored-by: Arko Dasgupta Signed-off-by: Huabing Zhao --- api/v1alpha1/authorization_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index 7176b08d7ea..e49c7410b24 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -29,7 +29,7 @@ type Authorization struct { type AuthorizationRule struct { // Name is a user-friendly name for the rule. It's just for display purposes. // +optional - Name *string `json:"name"` + Name *string `json:"name,omitempty"` // Action defines the action to be taken if the rule matches. Action AuthorizationAction `json:"action"` From 471a4d336ec32042c9bdb64ec81547abbece32e4 Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Thu, 23 May 2024 14:38:29 -0700 Subject: [PATCH 06/10] address comments Signed-off-by: huabing zhao --- internal/gatewayapi/securitypolicy.go | 26 +++++++++++++++---- .../securitypolicy-with-authoriztion.out.yaml | 4 ++- internal/ir/xds.go | 4 +++ internal/xds/translator/authorization.go | 8 +++--- .../testdata/in/xds-ir/authorization.yaml | 3 +++ .../out/xds-ir/authorization.routes.yaml | 18 ++++++------- .../e2e/testdata/authorization-client-ip.yaml | 4 +-- test/e2e/testdata/basic-auth.yaml | 1 + 8 files changed, 48 insertions(+), 20 deletions(-) diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 966d4eb1d7b..972dc039bd9 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -373,7 +373,7 @@ func (t *Translator) translateSecurityPolicyForRoute( } if policy.Spec.Authorization != nil { - if authorization, err = t.buildAuthorization(policy.Spec.Authorization); err != nil { + if authorization, err = t.buildAuthorization(policy); err != nil { errs = errors.Join(errs, err) } } @@ -453,7 +453,7 @@ func (t *Translator) translateSecurityPolicyForGateway( } if policy.Spec.Authorization != nil { - if authorization, err = t.buildAuthorization(policy.Spec.Authorization); err != nil { + if authorization, err = t.buildAuthorization(policy); err != nil { errs = errors.Join(errs, err) } } @@ -862,9 +862,10 @@ func irConfigName(policy *egv1a1.SecurityPolicy) string { utils.NamespacedName(policy).String()) } -func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*ir.Authorization, error) { +func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Authorization, error) { var ( - irAuth = &ir.Authorization{} + authorization = policy.Spec.Authorization + irAuth = &ir.Authorization{} // The default action is Deny if not specified defaultAction = egv1a1.AuthorizationActionDeny ) @@ -874,7 +875,7 @@ func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*i } irAuth.DefaultAction = defaultAction - for _, rule := range authorization.Rules { + for i, rule := range authorization.Rules { principal := ir.Principal{} for _, cidr := range rule.Principal.ClientCIDRs { @@ -885,7 +886,15 @@ func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*i principal.ClientCIDRs = append(principal.ClientCIDRs, cidrMatch) } + + var name string + if rule.Name != nil && *rule.Name != "" { + name = *rule.Name + } else { + name = defaultAuthorizationRuleName(policy, i) + } irAuth.Rules = append(irAuth.Rules, &ir.AuthorizationRule{ + Name: name, Action: rule.Action, Principal: principal, }) @@ -893,3 +902,10 @@ func (t *Translator) buildAuthorization(authorization *egv1a1.Authorization) (*i return irAuth, nil } + +func defaultAuthorizationRuleName(policy *egv1a1.SecurityPolicy, index int) string { + return fmt.Sprintf( + "%s/authorization/rule/%s", + irConfigName(policy), + strconv.Itoa(index)) +} diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml index 6e14ce3e56b..b8f3876609c 100755 --- a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml @@ -254,7 +254,6 @@ securityPolicies: defaultAction: Deny rules: - action: Allow - name: null principal: clientCIDRs: - 10.0.1.0/24 @@ -339,6 +338,7 @@ xdsIR: defaultAction: Allow rules: - action: Deny + name: deny-location-1 principal: clientCIDRs: - cidr: 192.168.1.0/24 @@ -352,6 +352,7 @@ xdsIR: isIPv6: false maskLen: 24 - action: Deny + name: deny-location-2 principal: clientCIDRs: - cidr: 10.75.1.0/24 @@ -385,6 +386,7 @@ xdsIR: defaultAction: Deny rules: - action: Allow + name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0 principal: clientCIDRs: - cidr: 10.0.1.0/24 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 4a3a69e205b..997af358165 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -775,6 +775,10 @@ type Authorization struct { // // +k8s:deepcopy-gen=true type AuthorizationRule struct { + // Name is a user-defined name for the rule. + // If not specified, a name will be generated by EG. + Name string `json:"name"` + // Action defines the action to be taken if the rule matches. Action egv1a1.AuthorizationAction `json:"action"` diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go index d4bc5e46be4..cfafb272529 100644 --- a/internal/xds/translator/authorization.go +++ b/internal/xds/translator/authorization.go @@ -154,7 +154,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { for _, rule := range authorization.Rules { // Build the IPMatcher based on the client CIDRs. ipRangeMatcher := &ipmatcherv3.Ip{ - StatPrefix: "source_ip", + StatPrefix: "client_ip", } for _, cidr := range rule.Principal.ClientCIDRs { @@ -186,7 +186,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{ SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{ Input: &cncfv3.TypedExtensionConfig{ - Name: "source_ip", + Name: "client_ip", TypedConfig: sourceIPInput, }, Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{ @@ -201,7 +201,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { OnMatch: &matcherv3.Matcher_OnMatch{ OnMatch: &matcherv3.Matcher_OnMatch_Action{ Action: &cncfv3.TypedExtensionConfig{ - Name: "action", + Name: rule.Name, TypedConfig: ruleAction, }, }, @@ -223,6 +223,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { Matchers: matcherList, }, }, + // If no matcher matches, the default action will be used. OnNoMatch: &matcherv3.Matcher_OnMatch{ OnMatch: &matcherv3.Matcher_OnMatch_Action{ Action: &cncfv3.TypedExtensionConfig{ @@ -242,6 +243,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { routeCfgProto.Rbac.Matcher.MatcherType = nil } + // We need to validate the RBACPerRoute message before converting it to an Any. if err = routeCfgProto.ValidateAll(); err != nil { return err } diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml index 738b757d112..d36a738a757 100644 --- a/internal/xds/translator/testdata/in/xds-ir/authorization.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml @@ -49,6 +49,7 @@ http: defaultAction: Allow rules: - action: Deny + name: deny-location-1 principal: clientCIDRs: - cidr: 192.168.1.0/24 @@ -62,6 +63,7 @@ http: isIPv6: false maskLen: 24 - action: Deny + name: deny-location-2 principal: clientCIDRs: - cidr: 10.75.1.0/24 @@ -95,6 +97,7 @@ http: defaultAction: Deny rules: - action: Allow + name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0 principal: clientCIDRs: - cidr: 10.0.1.0/24 diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml index 9d0cc127036..141d60a15dd 100644 --- a/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml @@ -40,7 +40,7 @@ matchers: - onMatch: action: - name: action + name: deny-location-1 typedConfig: '@type': type.googleapis.com/envoy.config.rbac.v3.Action action: DENY @@ -56,14 +56,14 @@ prefixLen: 24 - addressPrefix: 192.168.2.0 prefixLen: 24 - statPrefix: source_ip + statPrefix: client_ip input: - name: source_ip + name: client_ip typedConfig: '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput - onMatch: action: - name: action + name: deny-location-2 typedConfig: '@type': type.googleapis.com/envoy.config.rbac.v3.Action action: DENY @@ -79,9 +79,9 @@ prefixLen: 24 - addressPrefix: 10.75.2.0 prefixLen: 24 - statPrefix: source_ip + statPrefix: client_ip input: - name: source_ip + name: client_ip typedConfig: '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput onNoMatch: @@ -106,7 +106,7 @@ matchers: - onMatch: action: - name: action + name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0 typedConfig: '@type': type.googleapis.com/envoy.config.rbac.v3.Action name: ALLOW @@ -121,9 +121,9 @@ prefixLen: 24 - addressPrefix: 10.0.2.0 prefixLen: 24 - statPrefix: source_ip + statPrefix: client_ip input: - name: source_ip + name: client_ip typedConfig: '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput onNoMatch: diff --git a/test/e2e/testdata/authorization-client-ip.yaml b/test/e2e/testdata/authorization-client-ip.yaml index e2e731c05e6..08ead703cca 100644 --- a/test/e2e/testdata/authorization-client-ip.yaml +++ b/test/e2e/testdata/authorization-client-ip.yaml @@ -81,8 +81,8 @@ spec: - 10.0.1.0/24 - 10.0.2.0/24 --- -# This is a client traffic policy that enables client IP detection using a custom header. -# So, the client IP can be detected from the custom header `client-ip` and used for authorization. +# This is a client traffic policy that enables client IP detection using the XFF header. +# So, the client IP can be detected from the XFF header and used for authorization. apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: diff --git a/test/e2e/testdata/basic-auth.yaml b/test/e2e/testdata/basic-auth.yaml index 7bf3e0f6716..db99cf2cd85 100644 --- a/test/e2e/testdata/basic-auth.yaml +++ b/test/e2e/testdata/basic-auth.yaml @@ -59,6 +59,7 @@ spec: group: gateway.networking.k8s.io kind: HTTPRoute name: http-with-basic-auth-1 + namespace: gateway-conformance-infra basicAuth: users: name: "basic-auth-users-secret-1" From 9a366b8ca146a248b33fcdc98b1d56345b3e36c6 Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Thu, 23 May 2024 15:34:44 -0700 Subject: [PATCH 07/10] address comments Signed-off-by: huabing zhao --- api/v1alpha1/authorization_types.go | 2 +- api/v1alpha1/shared_types.go | 5 +++++ api/v1alpha1/zz_generated.deepcopy.go | 2 +- .../gateway.envoyproxy.io_securitypolicies.yaml | 4 ++++ internal/gatewayapi/securitypolicy.go | 2 +- .../securitypolicy-with-authoriztion.in.yaml | 3 --- .../securitypolicy-with-authoriztion.out.yaml | 3 --- site/content/en/latest/api/extension_types.md | 14 +++++++++++++- 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index e49c7410b24..02db76e15f9 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -50,7 +50,7 @@ type Principal struct { // or the proxy protocol. // You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in // the `ClientTrafficPolicy` to configure how the client IP is detected. - ClientCIDRs []string `json:"clientCIDRs,omitempty"` + ClientCIDRs []CIDR `json:"clientCIDRs,omitempty"` } // AuthorizationAction defines the action to be taken if a rule matches. diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go index e07c8ec158e..7446ccf368d 100644 --- a/api/v1alpha1/shared_types.go +++ b/api/v1alpha1/shared_types.go @@ -446,3 +446,8 @@ type BackendRef struct { // Only service Kind is supported for now. gwapiv1.BackendObjectReference `json:",inline"` } + +// CIDR defines a CIDR Address range. +// A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". +// +kubebuilder:validation:Pattern=`((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))` +type CIDR string diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b4ab56fe21e..dc713cdf55e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3363,7 +3363,7 @@ func (in *Principal) DeepCopyInto(out *Principal) { *out = *in if in.ClientCIDRs != nil { in, out := &in.ClientCIDRs, &out.ClientCIDRs - *out = make([]string, len(*in)) + *out = make([]CIDR, len(*in)) copy(*out, *in) } } 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 1041c204c0d..289480e09eb 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -99,6 +99,10 @@ spec: You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in the `ClientTrafficPolicy` to configure how the client IP is detected. items: + description: |- + CIDR defines a CIDR Address range. + A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) type: string type: array type: object diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 972dc039bd9..abd5b20ecab 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -879,7 +879,7 @@ func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Auth principal := ir.Principal{} for _, cidr := range rule.Principal.ClientCIDRs { - cidrMatch, err := parseCIDR(cidr) + cidrMatch, err := parseCIDR(string(cidr)) if err != nil { return nil, fmt.Errorf("unable to translate authorization rule: %w", err) } diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml index 57ed9a378b2..fe89f0dd84a 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml @@ -77,7 +77,6 @@ securityPolicies: metadata: namespace: envoy-gateway name: policy-for-gateway # This policy should attach httproute-2 - uid: b8284d0f-de82-4c65-b204-96a0d3f258a1 spec: targetRef: group: gateway.networking.k8s.io @@ -96,7 +95,6 @@ securityPolicies: metadata: namespace: default name: policy-for-http-route-1 # This policy should attach httproute-1 - uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 spec: targetRef: group: gateway.networking.k8s.io @@ -122,7 +120,6 @@ securityPolicies: metadata: namespace: default name: policy-for-http-route-3 # This policy should attach httproute-3 - uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 spec: targetRef: group: gateway.networking.k8s.io diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml index b8f3876609c..1f6103bb2c2 100755 --- a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml @@ -177,7 +177,6 @@ securityPolicies: creationTimestamp: null name: policy-for-http-route-1 namespace: default - uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 spec: authorization: defaultAction: Allow @@ -219,7 +218,6 @@ securityPolicies: creationTimestamp: null name: policy-for-http-route-3 namespace: default - uid: 08335a80-83ba-4592-888f-6ac0bba44ce4 spec: authorization: defaultAction: null @@ -248,7 +246,6 @@ securityPolicies: creationTimestamp: null name: policy-for-gateway namespace: envoy-gateway - uid: b8284d0f-de82-4c65-b204-96a0d3f258a1 spec: authorization: defaultAction: Deny diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index fddee062e33..e44fa36a6cf 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -455,6 +455,18 @@ _Appears in:_ | `Replace` | Replace replaces the default bootstrap with the provided one.
| +#### CIDR + +_Underlying type:_ _string_ + +CIDR defines a CIDR Address range. +A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + +_Appears in:_ +- [Principal](#principal) + + + #### CORS @@ -2444,7 +2456,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | -| `clientCIDRs` | _string array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

The client IP is inferred from the x-forwarder-for header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
the `ClientTrafficPolicy` to configure how the client IP is detected. | +| `clientCIDRs` | _[CIDR](#cidr) array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

The client IP is inferred from the x-forwarder-for header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
the `ClientTrafficPolicy` to configure how the client IP is detected. | #### ProcessingModeOptions From 71e1f140e7d516264c0c230e0345644397d1dff5 Mon Sep 17 00:00:00 2001 From: huabing zhao Date: Thu, 23 May 2024 16:06:30 -0700 Subject: [PATCH 08/10] add validation for Client CIDR list Signed-off-by: huabing zhao --- api/v1alpha1/authorization_types.go | 8 ++++++-- .../generated/gateway.envoyproxy.io_securitypolicies.yaml | 5 ++++- site/content/en/latest/api/extension_types.md | 6 +++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index 02db76e15f9..e02ba4dafed 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -46,11 +46,15 @@ type Principal struct { // ClientCIDRs are the IP CIDR ranges of the client. // Valid examples are "192.168.1.0/24" or "2001:db8::/64" // - // The client IP is inferred from the x-forwarder-for header, a custom header, + // The client IP is inferred from the X-Forwarded-For header, a custom header, // or the proxy protocol. // You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in // the `ClientTrafficPolicy` to configure how the client IP is detected. - ClientCIDRs []CIDR `json:"clientCIDRs,omitempty"` + // +kubebuilder:validation:MinItems=1 + ClientCIDRs []CIDR `json:"clientCIDRs"` + + // TODO: Zhaohuabing the MinItems=1 validation can be relaxed to allow empty list + // after other principal types are supported. However, at least one principal is required } // AuthorizationAction defines the action to be taken if a rule matches. 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 289480e09eb..bae699830b3 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -94,7 +94,7 @@ spec: Valid examples are "192.168.1.0/24" or "2001:db8::/64" - The client IP is inferred from the x-forwarder-for header, a custom header, + The client IP is inferred from the X-Forwarded-For header, a custom header, or the proxy protocol. You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in the `ClientTrafficPolicy` to configure how the client IP is detected. @@ -104,7 +104,10 @@ spec: A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) type: string + minItems: 1 type: array + required: + - clientCIDRs type: object required: - action diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index b5a7f9ece9f..c02c6dca872 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1113,8 +1113,8 @@ _Appears in:_ | `host` | _string_ | true | Host define the sink service hostname. | | `protocol` | _string_ | true | Protocol define the sink service protocol. | | `port` | _integer_ | false | Port defines the port the sink service is exposed on. | -| `exportInterval` | _[Duration](#duration)_ | true | ExportInterval configures the intervening time between exports for a
Sink. This option overrides any value set for the
OTEL_METRIC_EXPORT_INTERVAL environment variable.
If ExportInterval is less than or equal to zero, 60 seconds
is used as the default. | -| `exportTimeout` | _[Duration](#duration)_ | true | ExportTimeout configures the time a Sink waits for an export to
complete before canceling it. This option overrides any value set for the
OTEL_METRIC_EXPORT_TIMEOUT environment variable.
If ExportTimeout is less than or equal to zero, 30 seconds
is used as the default. | +| `exportInterval` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ | true | ExportInterval configures the intervening time between exports for a
Sink. This option overrides any value set for the
OTEL_METRIC_EXPORT_INTERVAL environment variable.
If ExportInterval is less than or equal to zero, 60 seconds
is used as the default. | +| `exportTimeout` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ | true | ExportTimeout configures the time a Sink waits for an export to
complete before canceling it. This option overrides any value set for the
OTEL_METRIC_EXPORT_TIMEOUT environment variable.
If ExportTimeout is less than or equal to zero, 30 seconds
is used as the default. | #### EnvoyGatewayPrometheusProvider @@ -2502,7 +2502,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | -| `clientCIDRs` | _[CIDR](#cidr) array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

The client IP is inferred from the x-forwarder-for header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
the `ClientTrafficPolicy` to configure how the client IP is detected. | +| `clientCIDRs` | _[CIDR](#cidr) array_ | true | ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"

The client IP is inferred from the X-Forwarded-For header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
the `ClientTrafficPolicy` to configure how the client IP is detected. | #### ProcessingModeOptions From 8a1499466c00edf8202455e852097a41ef8a77ef Mon Sep 17 00:00:00 2001 From: Huabing Zhao Date: Tue, 28 May 2024 17:30:55 -0700 Subject: [PATCH 09/10] fix gen check Signed-off-by: Huabing Zhao --- internal/gatewayapi/backendtrafficpolicy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 01d3f3b94ca..b14545167f6 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "math" - "net" "sort" "strings" "time" From 5a37c9e418a5d8bb643aeb2108632a4f4a764a16 Mon Sep 17 00:00:00 2001 From: Huabing Zhao Date: Tue, 28 May 2024 17:41:37 -0700 Subject: [PATCH 10/10] fix lint Signed-off-by: Huabing Zhao --- internal/gatewayapi/backendtrafficpolicy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index b14545167f6..2c661ec5e81 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "math" + "math/big" "sort" "strings" "time"