From b2db4c446a1939bb993332deb41859514077117b Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 25 Aug 2026 02:22:03 -0700 Subject: [PATCH 1/2] xds: support client-IP authorization on UDP listeners A UDP listener has no network filter chain, so the network RBAC filter used for TCPRoute authorization cannot be reused. Instead the authorization decision rides along with route selection in the matcher that udp_proxy already uses: a datagram whose source IP matches is routed to the cluster, and one that matches nothing is dropped by udp_proxy and counted as downstream_sess_no_route. Because a denial can only be expressed as the absence of a match, the ordered Allow/Deny rules are compiled into allow-only predicates. A Deny rule never becomes an entry of its own; it subtracts from the Allow rules that follow it and from a permissive default action. An allowlist therefore emits one entry per Allow rule with no on_no_match, and a denylist collapses to a single negated entry. Adds Authorization to ir.UDPRoute. Nothing populates it yet. Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Huabing (Robin) Zhao --- internal/ir/xds.go | 2 + internal/ir/zz_generated.deepcopy.go | 5 + internal/xds/translator/authorization_udp.go | 141 ++++++++++++ .../xds/translator/authorization_udp_test.go | 151 +++++++++++++ internal/xds/translator/listener.go | 27 ++- .../in/xds-ir/udp-route-authorization.yaml | 130 +++++++++++ .../udp-route-authorization.clusters.yaml | 115 ++++++++++ .../udp-route-authorization.endpoints.yaml | 60 +++++ .../udp-route-authorization.listeners.yaml | 207 ++++++++++++++++++ .../udp-route-authorization.routes.yaml | 1 + 10 files changed, 828 insertions(+), 11 deletions(-) create mode 100644 internal/xds/translator/authorization_udp.go create mode 100644 internal/xds/translator/authorization_udp_test.go create mode 100644 internal/xds/translator/testdata/in/xds-ir/udp-route-authorization.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.routes.yaml diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 07c493967fb..552fb1e0b9a 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -2815,6 +2815,8 @@ type UDPRoute struct { LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"` // DNS is used to configure how DNS resolution is handled by the Envoy Proxy cluster DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"` + // Authorization defines the schema for the authorization. + Authorization *Authorization `json:"authorization,omitempty" yaml:"authorization,omitempty"` } // Validate the fields within the UDPListener structure diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 141f3f81efc..c5c649c8953 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -5560,6 +5560,11 @@ func (in *UDPRoute) DeepCopyInto(out *UDPRoute) { *out = new(DNS) (*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 UDPRoute. diff --git a/internal/xds/translator/authorization_udp.go b/internal/xds/translator/authorization_udp.go new file mode 100644 index 00000000000..8cd8c168d6e --- /dev/null +++ b/internal/xds/translator/authorization_udp.go @@ -0,0 +1,141 @@ +// 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 ( + cncfv3 "github.com/cncf/xds/go/xds/core/v3" + matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" +) + +// buildUDPProxyMatcher builds the matcher that udp_proxy uses to pick a route, +// folding client-IP authorization into it. +// +// A UDP listener has no network filter chain, so the network RBAC filter used for +// TCPRoutes cannot be reused here. Instead the authorization decision rides along +// with route selection: a datagram whose source IP matches is routed to the +// cluster, and a datagram that matches nothing is dropped by udp_proxy, which +// counts it as downstream_sess_no_route. +// +// Since a denial can only be expressed as the absence of a match, the ordered +// Allow/Deny rules are compiled into allow-only predicates. A Deny rule never +// becomes an entry of its own; it subtracts from the Allow rules that follow it +// and from a permissive default action. +func buildUDPProxyMatcher(routeAction *cncfv3.TypedExtensionConfig, authorization *ir.Authorization) (*matcherv3.Matcher, error) { + onRoute := &matcherv3.Matcher_OnMatch{ + OnMatch: &matcherv3.Matcher_OnMatch_Action{Action: routeAction}, + } + + if authorization == nil { + return &matcherv3.Matcher{OnNoMatch: onRoute}, nil + } + + var ( + matchers []*matcherv3.Matcher_MatcherList_FieldMatcher + // Predicates of the Deny rules seen so far, in rule order. + denied []*matcherv3.Matcher_MatcherList_Predicate + ) + + for _, rule := range authorization.Rules { + // Only client CIDRs are enforceable on the UDP path. The Gateway API layer + // rejects every other principal for L4 targets, so anything else here is a + // rule that cannot match rather than one that matches everything. + if len(rule.Principal.ClientCIDRs) == 0 { + continue + } + + predicate, err := buildIPPredicate(rule.Principal.ClientCIDRs) + if err != nil { + return nil, err + } + + if rule.Action == egv1a1.AuthorizationActionDeny { + denied = append(denied, predicate) + continue + } + + // Rules are first-match-wins, so an Allow rule only covers the sources that + // no preceding Deny rule already claimed. Preceding Allow rules need no such + // exclusion: had one matched, the datagram would already have been routed. + conjuncts := []*matcherv3.Matcher_MatcherList_Predicate{predicate} + for _, d := range denied { + conjuncts = append(conjuncts, notPredicate(d)) + } + + matchers = append(matchers, &matcherv3.Matcher_MatcherList_FieldMatcher{ + Predicate: andPredicates(conjuncts), + OnMatch: onRoute, + }) + } + + if authorization.DefaultAction == egv1a1.AuthorizationActionAllow { + // Nothing is denied, so every datagram is routed either way and the Allow + // rules are redundant. Emit the same matcher as an unauthorized listener. + if len(denied) == 0 { + return &matcherv3.Matcher{OnNoMatch: onRoute}, nil + } + + // Route whatever no Deny rule claimed. This goes last so the Allow rules + // above keep their precedence. + matchers = append(matchers, &matcherv3.Matcher_MatcherList_FieldMatcher{ + Predicate: notPredicate(orPredicates(denied)), + OnMatch: onRoute, + }) + } + + matcher := &matcherv3.Matcher{} + // An empty matcher list fails proto validation, so the matcher type is left + // unset when nothing can be allowed. + if len(matchers) > 0 { + matcher.MatcherType = &matcherv3.Matcher_MatcherList_{ + MatcherList: &matcherv3.Matcher_MatcherList{Matchers: matchers}, + } + } + // on_no_match is deliberately left unset: that absence is what makes udp_proxy + // drop a datagram no Allow rule accounted for. + return matcher, nil +} + +// notPredicate negates a predicate. +func notPredicate(predicate *matcherv3.Matcher_MatcherList_Predicate) *matcherv3.Matcher_MatcherList_Predicate { + return &matcherv3.Matcher_MatcherList_Predicate{ + MatchType: &matcherv3.Matcher_MatcherList_Predicate_NotMatcher{ + NotMatcher: predicate, + }, + } +} + +// andPredicates conjoins predicates. A predicate list must hold at least two +// entries, so a lone predicate is returned as-is. +func andPredicates(predicates []*matcherv3.Matcher_MatcherList_Predicate) *matcherv3.Matcher_MatcherList_Predicate { + if len(predicates) == 1 { + return predicates[0] + } + return &matcherv3.Matcher_MatcherList_Predicate{ + MatchType: &matcherv3.Matcher_MatcherList_Predicate_AndMatcher{ + AndMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{ + Predicate: predicates, + }, + }, + } +} + +// orPredicates disjoins predicates. A predicate list must hold at least two +// entries, so a lone predicate is returned as-is. +func orPredicates(predicates []*matcherv3.Matcher_MatcherList_Predicate) *matcherv3.Matcher_MatcherList_Predicate { + if len(predicates) == 1 { + return predicates[0] + } + return &matcherv3.Matcher_MatcherList_Predicate{ + MatchType: &matcherv3.Matcher_MatcherList_Predicate_OrMatcher{ + OrMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{ + Predicate: predicates, + }, + }, + } +} diff --git a/internal/xds/translator/authorization_udp_test.go b/internal/xds/translator/authorization_udp_test.go new file mode 100644 index 00000000000..4bbef6b2fb2 --- /dev/null +++ b/internal/xds/translator/authorization_udp_test.go @@ -0,0 +1,151 @@ +// 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 ( + "testing" + + cncfv3 "github.com/cncf/xds/go/xds/core/v3" + matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3" + "github.com/stretchr/testify/require" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" +) + +func cidrRule(action egv1a1.AuthorizationAction, cidr string, maskLen uint32) *ir.AuthorizationRule { + return &ir.AuthorizationRule{ + Action: action, + Principal: ir.Principal{ + ClientCIDRs: []*ir.CIDRMatch{{CIDR: cidr, MaskLen: maskLen}}, + }, + } +} + +// udp_proxy can only route or not route, so a denial is expressed as the absence of a +// match. These cases pin down how the ordered Allow/Deny rules collapse into +// allow-only predicates, and in particular that on_no_match is set only when the +// default action routes unconditionally. +func TestBuildUDPProxyMatcher(t *testing.T) { + routeAction := &cncfv3.TypedExtensionConfig{Name: "route"} + + tests := []struct { + name string + authorization *ir.Authorization + wantMatchers int + wantOnNoMatch bool + }{ + { + name: "no authorization routes everything", + authorization: nil, + wantMatchers: 0, + wantOnNoMatch: true, + }, + { + name: "allowlist emits one matcher per allow rule and drops the rest", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionDeny, + Rules: []*ir.AuthorizationRule{ + cidrRule(egv1a1.AuthorizationActionAllow, "192.168.100.0/24", 24), + cidrRule(egv1a1.AuthorizationActionAllow, "10.1.0.0/16", 16), + }, + }, + wantMatchers: 2, + wantOnNoMatch: false, + }, + { + name: "denylist collapses to a single negated matcher", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionAllow, + Rules: []*ir.AuthorizationRule{ + cidrRule(egv1a1.AuthorizationActionDeny, "10.0.0.0/24", 24), + cidrRule(egv1a1.AuthorizationActionDeny, "172.16.0.0/12", 12), + }, + }, + wantMatchers: 1, + wantOnNoMatch: false, + }, + { + name: "a preceding deny rule narrows the allow rule and the default", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionAllow, + Rules: []*ir.AuthorizationRule{ + cidrRule(egv1a1.AuthorizationActionDeny, "10.0.0.0/8", 8), + cidrRule(egv1a1.AuthorizationActionAllow, "10.1.0.0/16", 16), + }, + }, + // The narrowed allow rule, plus the catch-all for the permissive default. + wantMatchers: 2, + wantOnNoMatch: false, + }, + { + name: "an allow default with nothing denied is the same as no authorization", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionAllow, + Rules: []*ir.AuthorizationRule{ + cidrRule(egv1a1.AuthorizationActionAllow, "192.168.100.0/24", 24), + }, + }, + wantMatchers: 0, + wantOnNoMatch: true, + }, + { + name: "a deny default with no allow rules drops everything", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionDeny, + }, + wantMatchers: 0, + wantOnNoMatch: false, + }, + { + name: "rules without client CIDRs cannot match and are skipped", + authorization: &ir.Authorization{ + DefaultAction: egv1a1.AuthorizationActionDeny, + Rules: []*ir.AuthorizationRule{ + {Action: egv1a1.AuthorizationActionAllow}, + cidrRule(egv1a1.AuthorizationActionAllow, "192.168.100.0/24", 24), + }, + }, + wantMatchers: 1, + wantOnNoMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m, err := buildUDPProxyMatcher(routeAction, tt.authorization) + require.NoError(t, err) + + require.Len(t, m.GetMatcherList().GetMatchers(), tt.wantMatchers) + require.Equal(t, tt.wantOnNoMatch, m.GetOnNoMatch() != nil) + + // An empty matcher list fails proto validation, so it must be left unset + // rather than emitted empty. + if tt.wantMatchers == 0 { + require.Nil(t, m.GetMatcherList()) + } + for _, fm := range m.GetMatcherList().GetMatchers() { + require.NotNil(t, fm.GetPredicate()) + require.NotNil(t, fm.GetOnMatch()) + } + }) + } +} + +// A predicate list must hold at least two entries, so a lone predicate has to be +// returned bare rather than wrapped. +func TestPredicateCombinatorsSkipSingletonLists(t *testing.T) { + single := &matcherv3.Matcher_MatcherList_Predicate{ + MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{}, + } + + require.Same(t, single, andPredicates([]*matcherv3.Matcher_MatcherList_Predicate{single})) + require.Same(t, single, orPredicates([]*matcherv3.Matcher_MatcherList_Predicate{single})) + + require.Len(t, andPredicates([]*matcherv3.Matcher_MatcherList_Predicate{single, single}).GetAndMatcher().GetPredicate(), 2) + require.Len(t, orPredicates([]*matcherv3.Matcher_MatcherList_Predicate{single, single}).GetOrMatcher().GetPredicate(), 2) + require.Same(t, single, notPredicate(single).GetNotMatcher()) +} diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index e1548e93fa2..e11fc23e64b 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -13,7 +13,6 @@ import ( "strings" xdscore "github.com/cncf/xds/go/xds/core/v3" - matcher "github.com/cncf/xds/go/xds/type/matcher/v3" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" tls_inspectorv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3" @@ -1183,6 +1182,21 @@ func buildXdsUDPListener( return nil, err } + var authorization *ir.Authorization + if udpListener.Route != nil { + authorization = udpListener.Route.Authorization + } + routeMatcher, err := buildUDPProxyMatcher( + &xdscore.TypedExtensionConfig{ + Name: "route", + TypedConfig: routeAny, + }, + authorization, + ) + if err != nil { + return nil, err + } + al, error := buildXdsAccessLog(accesslog, ir.ProxyAccessLogTypeRoute) if error != nil { return nil, error @@ -1191,16 +1205,7 @@ func buildXdsUDPListener( StatPrefix: statPrefix, AccessLog: al, RouteSpecifier: &udpv3.UdpProxyConfig_Matcher{ - Matcher: &matcher.Matcher{ - OnNoMatch: &matcher.Matcher_OnMatch{ - OnMatch: &matcher.Matcher_OnMatch_Action{ - Action: &xdscore.TypedExtensionConfig{ - Name: "route", - TypedConfig: routeAny, - }, - }, - }, - }, + Matcher: routeMatcher, }, } udpProxyAny, err := proto.ToAnyWithValidation(udpProxy) diff --git a/internal/xds/translator/testdata/in/xds-ir/udp-route-authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/udp-route-authorization.yaml new file mode 100644 index 00000000000..7db5a6ca1dd --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/udp-route-authorization.yaml @@ -0,0 +1,130 @@ +udp: +# Allowlist: a Deny default with Allow rules, so only the listed sources are routed +# and everything else falls through to no match and is dropped. +- name: "udp-listener-allowlist" + address: "::" + port: 10080 + route: + name: "udp-route-allowlist" + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-corp + principal: + clientCIDRs: + - cidr: 192.168.100.0/24 + maskLen: 24 + - action: Allow + name: allow-branch + principal: + clientCIDRs: + - cidr: 10.1.0.0/16 + maskLen: 16 + - cidr: 2001:db8::/64 + isIPv6: true + maskLen: 64 + destination: + name: "udp-route-allowlist-dest" + settings: + - endpoints: + - host: "10.2.3.4" + port: 50000 + name: "udp-route-allowlist-dest/backend/0" +# Denylist: an Allow default with Deny rules, which compiles to a single negated +# predicate covering every denied source. +- name: "udp-listener-denylist" + address: "::" + port: 10081 + route: + name: "udp-route-denylist" + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-office + principal: + clientCIDRs: + - cidr: 10.0.0.0/24 + maskLen: 24 + - action: Deny + name: deny-lab + principal: + clientCIDRs: + - cidr: 172.16.0.0/12 + maskLen: 12 + destination: + name: "udp-route-denylist-dest" + settings: + - endpoints: + - host: "10.2.3.5" + port: 50000 + name: "udp-route-denylist-dest/backend/0" +# Mixed ordering: the Deny rule precedes the Allow rule, so the Allow predicate is +# narrowed by it, and the Allow default is narrowed by every Deny rule. +- name: "udp-listener-mixed" + address: "::" + port: 10082 + route: + name: "udp-route-mixed" + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-office + principal: + clientCIDRs: + - cidr: 10.0.0.0/8 + maskLen: 8 + - action: Allow + name: allow-branch + principal: + clientCIDRs: + - cidr: 10.1.0.0/16 + maskLen: 16 + destination: + name: "udp-route-mixed-dest" + settings: + - endpoints: + - host: "10.2.3.6" + port: 50000 + name: "udp-route-mixed-dest/backend/0" +# Deny all: a Deny default with no Allow rules leaves nothing to match on, so the +# matcher has neither a matcher list nor an on_no_match and every datagram is dropped. +- name: "udp-listener-deny-all" + address: "::" + port: 10083 + route: + name: "udp-route-deny-all" + authorization: + defaultAction: Deny + destination: + name: "udp-route-deny-all-dest" + settings: + - endpoints: + - host: "10.2.3.7" + port: 50000 + name: "udp-route-deny-all-dest/backend/0" +# Allow all: an Allow default with no Deny rules routes everything, so the matcher +# is identical to a listener without authorization. +- name: "udp-listener-allow-all" + address: "::" + port: 10084 + route: + name: "udp-route-allow-all" + authorization: + defaultAction: Allow + rules: + - action: Allow + name: allow-corp + principal: + clientCIDRs: + - cidr: 192.168.100.0/24 + maskLen: 24 + destination: + name: "udp-route-allow-all-dest" + settings: + - endpoints: + - host: "10.2.3.8" + port: 50000 + name: "udp-route-allow-all-dest/backend/0" diff --git a/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.clusters.yaml new file mode 100644 index 00000000000..608368a5025 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.clusters.yaml @@ -0,0 +1,115 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-allowlist-dest + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: udp-route-allowlist-dest + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-denylist-dest + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: udp-route-denylist-dest + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-mixed-dest + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: udp-route-mixed-dest + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-deny-all-dest + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: udp-route-deny-all-dest + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-allow-all-dest + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: udp-route-allow-all-dest + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.endpoints.yaml new file mode 100644 index 00000000000..a05b26d41f5 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.endpoints.yaml @@ -0,0 +1,60 @@ +- clusterName: udp-route-allowlist-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-allowlist-dest/backend/0 +- clusterName: udp-route-denylist-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.2.3.5 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-denylist-dest/backend/0 +- clusterName: udp-route-mixed-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.2.3.6 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-mixed-dest/backend/0 +- clusterName: udp-route-deny-all-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.2.3.7 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-deny-all-dest/backend/0 +- clusterName: udp-route-allow-all-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.2.3.8 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-allow-all-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.listeners.yaml new file mode 100644 index 00000000000..28bb96bd81b --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.listeners.yaml @@ -0,0 +1,207 @@ +- address: + socketAddress: + address: '::' + portValue: 10080 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: + matcherList: + matchers: + - onMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-allowlist-dest + predicate: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 192.168.100.0 + prefixLen: 24 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + - onMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-allowlist-dest + predicate: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.1.0.0 + prefixLen: 16 + - addressPrefix: '2001:db8::' + prefixLen: 64 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + statPrefix: service + name: udp-listener-allowlist +- address: + socketAddress: + address: '::' + portValue: 10081 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: + matcherList: + matchers: + - onMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-denylist-dest + predicate: + notMatcher: + orMatcher: + predicate: + - singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.0.0.0 + prefixLen: 24 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + - singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 172.16.0.0 + prefixLen: 12 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + statPrefix: service + name: udp-listener-denylist +- address: + socketAddress: + address: '::' + portValue: 10082 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: + matcherList: + matchers: + - onMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-mixed-dest + predicate: + andMatcher: + predicate: + - singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.1.0.0 + prefixLen: 16 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + - notMatcher: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.0.0.0 + prefixLen: 8 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + - onMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-mixed-dest + predicate: + notMatcher: + singlePredicate: + customMatch: + name: ip_matcher + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip + cidrRanges: + - addressPrefix: 10.0.0.0 + prefixLen: 8 + statPrefix: client_ip + input: + name: client_ip + typedConfig: + '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput + statPrefix: service + name: udp-listener-mixed +- address: + socketAddress: + address: '::' + portValue: 10083 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: {} + statPrefix: service + name: udp-listener-deny-all +- address: + socketAddress: + address: '::' + portValue: 10084 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: + onNoMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-allow-all-dest + statPrefix: service + name: udp-listener-allow-all diff --git a/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/udp-route-authorization.routes.yaml @@ -0,0 +1 @@ +[] From 1a035d9f110242d4fb152c0a5f3455b93ed79d8c Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 25 Aug 2026 02:29:07 -0700 Subject: [PATCH 2/2] gatewayapi: apply SecurityPolicy authorization to UDP routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires client-IP CIDR authorization through to ir.UDPRoute, for SecurityPolicies targeting a UDPRoute as well as those targeting a Gateway, a Gateway listener, a ListenerSet or a ListenerSet listener — matching what TCP already supports. TCP and UDP accept and reject exactly the same fields, so validateSecurityPolicyForTCP becomes validateSecurityPolicyForL4, taking the protocol name for its error messages. A UDP listener holds at most one route: when several UDPRoutes name the same listener only the oldest is attached, so a policy targeting one of the others has nothing to apply to. The TCP case also gains the nil listener guard it was missing. Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Huabing (Robin) Zhao --- internal/gatewayapi/securitypolicy.go | 90 +- internal/gatewayapi/securitypolicy_test.go | 43 +- ...icy-with-authorization-client-cidr.in.yaml | 259 +++++ ...cy-with-authorization-client-cidr.out.yaml | 893 ++++++++++++++++++ ...curitypolicy-without-authorization.in.yaml | 41 + ...uritypolicy-without-authorization.out.yaml | 170 ++++ 6 files changed, 1467 insertions(+), 29 deletions(-) create mode 100644 internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.in.yaml create mode 100644 internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.out.yaml create mode 100644 internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.in.yaml create mode 100644 internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.out.yaml diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index c22bc65ffe8..7be4db24e29 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -512,9 +512,13 @@ func (t *Translator) processSecurityPolicyForRoute( // then run it once to keep the flow linear and easier to read. validator := validateSecurityPolicy errMsg := "invalid SecurityPolicy" - if currTarget.Kind == resource.KindTCPRoute { - validator = validateSecurityPolicyForTCP + switch currTarget.Kind { + case resource.KindTCPRoute: + validator = func(p *egv1a1.SecurityPolicy) error { return validateSecurityPolicyForL4(p, "TCP") } errMsg = "invalid SecurityPolicy for TCP route" + case resource.KindUDPRoute: + validator = func(p *egv1a1.SecurityPolicy) error { return validateSecurityPolicyForL4(p, "UDP") } + errMsg = "invalid SecurityPolicy for UDP route" } if err := validator(policy); err != nil { status.SetTranslationErrorForPolicyAncestors(&policy.Status, @@ -968,16 +972,18 @@ func validateSecurityPolicy(p *egv1a1.SecurityPolicy) error { return nil } -// validateSecurityPolicyForTCP ensures SecurityPolicy usage on TCP is compatible. +// validateSecurityPolicyForL4 ensures SecurityPolicy usage on an L4 protocol +// (TCP or UDP) is compatible. proto names the protocol in the returned errors. // -// TCP supports Authorization with ClientCIDRs ONLY. +// L4 supports Authorization with ClientCIDRs ONLY, because there is no HTTP +// request to inspect: // - Principals.JWT => invalid (HTTP-only) // - Principals.Headers => invalid (HTTP-only) -// - Empty/no Authorization is allowed and results in no-op on TCP. +// - Empty/no Authorization is allowed and results in no-op on L4. // Returns an error when any HTTP-only field is present or CIDRs are invalid. -func validateSecurityPolicyForTCP(p *egv1a1.SecurityPolicy) error { +func validateSecurityPolicyForL4(p *egv1a1.SecurityPolicy, proto string) error { if p.Spec.CORS != nil || p.Spec.CSRF != nil || p.Spec.JWT != nil || p.Spec.OIDC != nil || p.Spec.APIKeyAuth != nil || p.Spec.BasicAuth != nil || p.Spec.ExtAuth != nil { - return fmt.Errorf("only authorization is supported for TCP (routes/listeners)") + return fmt.Errorf("only authorization is supported for %s (routes/listeners)", proto) } if p.Spec.Authorization == nil || len(p.Spec.Authorization.Rules) == 0 { return nil @@ -985,19 +991,19 @@ func validateSecurityPolicyForTCP(p *egv1a1.SecurityPolicy) error { for i := range p.Spec.Authorization.Rules { rule := &p.Spec.Authorization.Rules[i] if rule.CEL != nil { - return fmt.Errorf("rule %d: CEL not supported for TCP", i) + return fmt.Errorf("rule %d: CEL not supported for %s", i, proto) } if rule.Principal == nil { continue } if rule.Principal.JWT != nil { - return fmt.Errorf("rule %d: JWT not supported for TCP", i) + return fmt.Errorf("rule %d: JWT not supported for %s", i, proto) } if len(rule.Principal.Headers) > 0 { - return fmt.Errorf("rule %d: headers not supported for TCP", i) + return fmt.Errorf("rule %d: headers not supported for %s", i, proto) } if len(rule.Principal.ClientIPGeoLocations) > 0 { - return fmt.Errorf("rule %d: clientIPGeoLocations not supported for TCP", i) + return fmt.Errorf("rule %d: clientIPGeoLocations not supported for %s", i, proto) } if err := validateCIDRs(rule.Principal.ClientCIDRs); err != nil { return fmt.Errorf("rule %d: %w", i, err) @@ -1006,7 +1012,7 @@ func validateSecurityPolicyForTCP(p *egv1a1.SecurityPolicy) error { return nil } -// validateCIDRs validates CIDR strings for TCP authorization rules. +// validateCIDRs validates CIDR strings for L4 authorization rules. func validateCIDRs(cidrs []egv1a1.CIDR) error { for _, c := range cidrs { if _, _, err := net.ParseCIDR(string(c)); err != nil { @@ -1385,6 +1391,9 @@ func (t *Translator) translateSecurityPolicyForRoute( continue } tl := xdsIR[irKey].GetTCPListener(irListenerName(listener)) + if tl == nil { + continue + } for _, r := range tl.Routes { // If target.SectionName is specified it must match the route-rule section name // in the IR. For HTTP/GRPC routes this is r.Metadata.SectionName; for TCP @@ -1403,6 +1412,34 @@ func (t *Translator) translateSecurityPolicyForRoute( } } } + case resource.KindUDPRoute: + for _, listener := range parentRefCtx.listeners { + // If targetListener is set, only apply to that exact listener. + if targetListener != nil && targetListenerName != irListenerName(listener) { + continue + } + ul := xdsIR[irKey].GetUDPListener(irListenerName(listener)) + // A UDP listener holds at most one route: when several UDPRoutes name the + // same listener only the oldest one is attached, so a policy targeting any + // of the others has nothing to apply to. + if ul == nil || ul.Route == nil { + continue + } + r := ul.Route + // As with TCP, the route-rule section name lives on the destination metadata. + if target.SectionName != nil && string(*target.SectionName) != r.Destination.Metadata.SectionName { + continue + } + + if r.Authorization != nil { + continue + } + // Only authorization for UDP + if authorization != nil { + authCopy := *authorization + r.Authorization = &authCopy + } + } case resource.KindHTTPRoute, resource.KindGRPCRoute: var ( hasBaseErrs = errs != nil @@ -1695,15 +1732,15 @@ func (t *Translator) translateSecurityPolicyForListeners( ) } - // Pre-create a TCP-only authorization object to avoid re-allocation - var tcpAuthorization *ir.Authorization + // Pre-create an L4-only authorization object to avoid re-allocation + var l4Authorization *ir.Authorization if authorization != nil { authCopy := *authorization - tcpAuthorization = &authCopy + l4Authorization = &authCopy } // Apply to TCP listeners (Authorization only). - if tcpAuthorization != nil { + if l4Authorization != nil { for _, tl := range x.TCP { if tl == nil || len(tl.Routes) == 0 { continue @@ -1718,8 +1755,27 @@ func (t *Translator) translateSecurityPolicyForListeners( if r.Authorization != nil { continue } - r.Authorization = tcpAuthorization + r.Authorization = l4Authorization + } + } + } + + // Apply to UDP listeners (Authorization only). + if l4Authorization != nil { + for _, ul := range x.UDP { + // A UDP listener holds at most one route. + if ul == nil || ul.Route == nil { + continue + } + if !listenerNames.Has(ul.Name) { + continue + } + // A Policy targeting the specific scope(xRoute rule, xRoute, Gateway listener) wins over a policy + // targeting a lesser specific scope(Gateway). + if ul.Route.Authorization != nil { + continue } + ul.Route.Authorization = l4Authorization } } diff --git a/internal/gatewayapi/securitypolicy_test.go b/internal/gatewayapi/securitypolicy_test.go index 5ff44c8d4c5..0a083a2619c 100644 --- a/internal/gatewayapi/securitypolicy_test.go +++ b/internal/gatewayapi/securitypolicy_test.go @@ -1056,7 +1056,7 @@ func SetRouteParentContext(route RouteContext, parentRef gwapiv1.ParentReference route.SetRouteParentContext(parentRef, ctx) } -// --- TCP branch: validateSecurityPolicyForTCP(...) returns err -> SetTranslationErrorForPolicyAncestors(...) + return +// --- L4 branch: validateSecurityPolicyForL4(...) returns err -> SetTranslationErrorForPolicyAncestors(...) + return func Test_SecurityPolicy_TCP_Invalid_setsStatus_and_returns(t *testing.T) { tr := &Translator{GatewayControllerName: "gateway.envoyproxy.io/gatewayclass-controller"} trContext := &TranslatorContext{} @@ -1208,7 +1208,7 @@ func Test_SecurityPolicy_HTTP_Invalid_setsStatus_and_returns(t *testing.T) { require.True(t, hasParentFalseCondition(policy)) } -func Test_validateSecurityPolicyForTCP_Table(t *testing.T) { +func Test_validateSecurityPolicyForL4_Table(t *testing.T) { tests := []struct { name string spec egv1a1.SecurityPolicySpec @@ -1375,17 +1375,36 @@ func Test_validateSecurityPolicyForTCP_Table(t *testing.T) { }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - p := &egv1a1.SecurityPolicy{Spec: tc.spec} - err := validateSecurityPolicyForTCP(p) - if tc.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) + // TCP and UDP share the same rules, so every case must hold for both. + for _, proto := range []string{"TCP", "UDP"} { + for _, tc := range tests { + t.Run(proto+"/"+tc.name, func(t *testing.T) { + p := &egv1a1.SecurityPolicy{Spec: tc.spec} + err := validateSecurityPolicyForL4(p, proto) + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } } + + // Rejections that are about the protocol rather than the input name it, so the + // status condition tells the user which listener protocol refused the field. + t.Run("protocol named in protocol-specific errors", func(t *testing.T) { + p := &egv1a1.SecurityPolicy{Spec: egv1a1.SecurityPolicySpec{ + Authorization: &egv1a1.Authorization{ + Rules: []egv1a1.AuthorizationRule{{ + Action: egv1a1.AuthorizationActionAllow, + Principal: &egv1a1.Principal{Headers: []egv1a1.AuthorizationHeaderMatch{{Name: "x-user", Values: []string{"foo"}}}}, + }}, + }, + }} + + require.ErrorContains(t, validateSecurityPolicyForL4(p, "TCP"), "headers not supported for TCP") + require.ErrorContains(t, validateSecurityPolicyForL4(p, "UDP"), "headers not supported for UDP") + }) } func Test_validateAuthorizationGeoIPForHTTP(t *testing.T) { diff --git a/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.in.yaml b/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.in.yaml new file mode 100644 index 00000000000..22a0e764896 --- /dev/null +++ b/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.in.yaml @@ -0,0 +1,259 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-udp + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: foo + protocol: UDP + port: 8088 + allowedRoutes: + namespaces: + from: All + - name: bar + protocol: UDP + port: 8089 + allowedRoutes: + namespaces: + from: All + - name: baz + protocol: UDP + port: 8090 + allowedRoutes: + namespaces: + from: All +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-mixed + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: foo + protocol: UDP + port: 8443 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: hr-app-mixed + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-mixed + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: service-1 + port: 8080 +udpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + namespace: default + name: udpr-app-foo + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-udp + sectionName: foo + rules: + - backendRefs: + - name: service-1 + port: 8163 +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + namespace: default + name: udpr-app-bar + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-udp + sectionName: bar + rules: + - backendRefs: + - name: service-1 + port: 8163 +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + namespace: default + name: udpr-app-baz + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-udp + sectionName: baz + rules: + - name: rule-baz + backendRefs: + - name: service-1 + port: 8163 +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + namespace: default + name: udpr-app-mixed + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-mixed + sectionName: foo + rules: + - backendRefs: + - name: service-1 + port: 8163 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: sp-gw-udp-whole + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-gateway + principal: + clientCIDRs: + - 10.10.0.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: sp-gw-udp-section-foo # This policy should attach sectionName foo + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + sectionName: foo + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-section-foo + principal: + clientCIDRs: + - 10.10.1.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: sp-udpr-app-bar # This policy should attach udproute bar + spec: + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-bar + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-route-bar + principal: + clientCIDRs: + - 10.10.2.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: sp-udpr-app-conflict # This policy should NOT attach udproute bar due to conflict with above policy + spec: + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-bar + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-route-conflict + principal: + clientCIDRs: + - 10.10.3.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: sp-udpr-app-baz-section # This policy should attach udproute baz via its rule name + spec: + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-baz + sectionName: rule-baz + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-route-baz + principal: + clientCIDRs: + - 10.10.6.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: sp-udpr-app-invalid-section # This policy should NOT attach due to invalid section + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + sectionName: bogus + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-invalid-section + principal: + clientCIDRs: + - 10.10.4.0/24 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: sp-mixed-gateway # This policy should attach to gateway-mixed, applying to all listeners, but only whats supported + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-mixed + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - 10.10.5.0/24 + cors: + allowOrigins: + - https://example.com + allowMethods: + - GET + - POST diff --git a/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.out.yaml b/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.out.yaml new file mode 100644 index 00000000000..92e47c74af6 --- /dev/null +++ b/internal/gatewayapi/testdata/udproute-securitypolicy-with-authorization-client-cidr.out.yaml @@ -0,0 +1,893 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-udp + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: foo + port: 8088 + protocol: UDP + - allowedRoutes: + namespaces: + from: All + name: bar + port: 8089 + protocol: UDP + - allowedRoutes: + namespaces: + from: All + name: baz + port: 8090 + protocol: UDP + status: + listeners: + - attachedRoutes: 1 + 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: foo + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute + - attachedRoutes: 1 + 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: bar + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute + - attachedRoutes: 1 + 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: baz + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-mixed + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: foo + port: 8443 + protocol: UDP + status: + listeners: + - attachedRoutes: 1 + 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 + - attachedRoutes: 1 + 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: foo + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: hr-app-mixed + namespace: default + spec: + parentRefs: + - name: gateway-mixed + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + 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-mixed + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-mixed: + proxy: + listeners: + - name: envoy-gateway/gateway-mixed/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - name: envoy-gateway/gateway-mixed/foo + ports: + - containerPort: 8443 + name: udp-8443 + protocol: UDP + servicePort: 8443 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-mixed + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-mixed + namespace: envoy-gateway-system + envoy-gateway/gateway-udp: + proxy: + listeners: + - name: envoy-gateway/gateway-udp/foo + ports: + - containerPort: 8088 + name: udp-8088 + protocol: UDP + servicePort: 8088 + - name: envoy-gateway/gateway-udp/bar + ports: + - containerPort: 8089 + name: udp-8089 + protocol: UDP + servicePort: 8089 + - name: envoy-gateway/gateway-udp/baz + ports: + - containerPort: 8090 + name: udp-8090 + protocol: UDP + servicePort: 8090 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-udp + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-udp + namespace: envoy-gateway-system +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-udpr-app-baz-section + namespace: default + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-route-baz + principal: + clientCIDRs: + - 10.10.6.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-baz + sectionName: rule-baz + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: baz + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-udpr-app-bar + namespace: default + spec: + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-route-bar + principal: + clientCIDRs: + - 10.10.2.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-bar + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: bar + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-udpr-app-conflict + namespace: default + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-route-conflict + principal: + clientCIDRs: + - 10.10.3.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app-bar + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: bar + conditions: + - lastTransitionTime: null + message: Unable to target UDPRoute udpr-app-bar, another SecurityPolicy has + already attached to it + reason: Conflicted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-gw-udp-section-foo + namespace: envoy-gateway + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-section-foo + principal: + clientCIDRs: + - 10.10.1.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + sectionName: foo + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: foo + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-udpr-app-invalid-section + namespace: envoy-gateway + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-invalid-section + principal: + clientCIDRs: + - 10.10.4.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + sectionName: bogus + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: bogus + conditions: + - lastTransitionTime: null + message: No section name bogus found for Gateway envoy-gateway/gateway-udp + reason: TargetNotFound + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-gw-udp-whole + namespace: envoy-gateway + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-gateway + principal: + clientCIDRs: + - 10.10.0.0/24 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + 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 + gateway listeners: [envoy-gateway/gateway-udp/foo] and these routes: [default/udpr-app-bar + default/udpr-app-baz]' + reason: Overridden + status: "True" + type: Overridden + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-mixed-gateway + namespace: envoy-gateway + spec: + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - 10.10.5.0/24 + cors: + allowMethods: + - GET + - POST + allowOrigins: + - https://example.com + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-mixed + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-mixed + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +udpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + name: udpr-app-foo + namespace: default + spec: + parentRefs: + - name: gateway-udp + namespace: envoy-gateway + sectionName: foo + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: UDP Port 8163 not found on Service default/service-1 + reason: PortNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-udp + namespace: envoy-gateway + sectionName: foo +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + name: udpr-app-bar + namespace: default + spec: + parentRefs: + - name: gateway-udp + namespace: envoy-gateway + sectionName: bar + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: UDP Port 8163 not found on Service default/service-1 + reason: PortNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-udp + namespace: envoy-gateway + sectionName: bar +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + name: udpr-app-baz + namespace: default + spec: + parentRefs: + - name: gateway-udp + namespace: envoy-gateway + sectionName: baz + rules: + - backendRefs: + - name: service-1 + port: 8163 + name: rule-baz + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: UDP Port 8163 not found on Service default/service-1 + reason: PortNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-udp + namespace: envoy-gateway + sectionName: baz +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + name: udpr-app-mixed + namespace: default + spec: + parentRefs: + - name: gateway-mixed + namespace: envoy-gateway + sectionName: foo + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: UDP Port 8163 not found on Service default/service-1 + reason: PortNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-mixed + namespace: envoy-gateway + sectionName: foo +xdsIR: + envoy-gateway/gateway-mixed: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-mixed-94aaf8eb + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-mixed + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-mixed-94aaf8eb + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-mixed + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-mixed + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-mixed/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: hr-app-mixed + namespace: default + name: httproute/default/hr-app-mixed/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/hr-app-mixed/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: hr-app-mixed + namespace: default + name: httproute/default/hr-app-mixed/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + security: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: securitypolicy/envoy-gateway/sp-mixed-gateway/authorization/rule/0 + principal: + clientCIDRs: + - cidr: 10.10.5.0/24 + distinct: false + invert: false + isIPv6: false + maskLen: 24 + cors: + allowMethods: + - GET + - POST + allowOrigins: + - distinct: false + exact: https://example.com + name: "" + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + udp: + - address: 0.0.0.0 + externalPort: 8443 + metadata: + kind: Gateway + name: gateway-mixed + namespace: envoy-gateway + sectionName: foo + name: envoy-gateway/gateway-mixed/foo + port: 8443 + route: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: securitypolicy/envoy-gateway/sp-mixed-gateway/authorization/rule/0 + principal: + clientCIDRs: + - cidr: 10.10.5.0/24 + distinct: false + invert: false + isIPv6: false + maskLen: 24 + destination: + metadata: + kind: UDPRoute + name: udpr-app-mixed + namespace: default + name: udproute/default/udpr-app-mixed/rule/-1 + name: udproute/default/udpr-app-mixed + envoy-gateway/gateway-udp: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-udp-2934226e + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-udp + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-udp-2934226e + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-udp + protocol: TCP + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + udp: + - address: 0.0.0.0 + externalPort: 8088 + metadata: + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: foo + name: envoy-gateway/gateway-udp/foo + port: 8088 + route: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-section-foo + principal: + clientCIDRs: + - cidr: 10.10.1.0/24 + distinct: false + invert: false + isIPv6: false + maskLen: 24 + destination: + metadata: + kind: UDPRoute + name: udpr-app-foo + namespace: default + name: udproute/default/udpr-app-foo/rule/-1 + name: udproute/default/udpr-app-foo + - address: 0.0.0.0 + externalPort: 8089 + metadata: + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: bar + name: envoy-gateway/gateway-udp/bar + port: 8089 + route: + authorization: + defaultAction: Allow + rules: + - action: Deny + name: deny-route-bar + principal: + clientCIDRs: + - cidr: 10.10.2.0/24 + distinct: false + invert: false + isIPv6: false + maskLen: 24 + destination: + metadata: + kind: UDPRoute + name: udpr-app-bar + namespace: default + name: udproute/default/udpr-app-bar/rule/-1 + name: udproute/default/udpr-app-bar + - address: 0.0.0.0 + externalPort: 8090 + metadata: + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: baz + name: envoy-gateway/gateway-udp/baz + port: 8090 + route: + authorization: + defaultAction: Deny + rules: + - action: Allow + name: allow-route-baz + principal: + clientCIDRs: + - cidr: 10.10.6.0/24 + distinct: false + invert: false + isIPv6: false + maskLen: 24 + destination: + metadata: + kind: UDPRoute + name: udpr-app-baz + namespace: default + sectionName: rule-baz + name: udproute/default/udpr-app-baz/rule/-1 + name: udproute/default/udpr-app-baz diff --git a/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.in.yaml b/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.in.yaml new file mode 100644 index 00000000000..21d0d3892c5 --- /dev/null +++ b/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.in.yaml @@ -0,0 +1,41 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-udp + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: udp + protocol: UDP + port: 8088 + allowedRoutes: + namespaces: + from: All +udpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + namespace: default + name: udpr-app + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-udp + sectionName: udp + rules: + - backendRefs: + - name: service-1 + port: 8163 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: sp-udpr-no-auth + spec: + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app diff --git a/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.out.yaml b/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.out.yaml new file mode 100644 index 00000000000..f2a4351ecee --- /dev/null +++ b/internal/gatewayapi/testdata/udproute-securitypolicy-without-authorization.out.yaml @@ -0,0 +1,170 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-udp + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: udp + port: 8088 + protocol: UDP + status: + listeners: + - attachedRoutes: 1 + 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: udp + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute +infraIR: + envoy-gateway/gateway-udp: + proxy: + listeners: + - name: envoy-gateway/gateway-udp/udp + ports: + - containerPort: 8088 + name: udp-8088 + protocol: UDP + servicePort: 8088 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-udp + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-udp + namespace: envoy-gateway-system +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: sp-udpr-no-auth + namespace: default + spec: + targetRef: + group: gateway.networking.k8s.io + kind: UDPRoute + name: udpr-app + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: udp + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +udpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: UDPRoute + metadata: + name: udpr-app + namespace: default + spec: + parentRefs: + - name: gateway-udp + namespace: envoy-gateway + sectionName: udp + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: UDP Port 8163 not found on Service default/service-1 + reason: PortNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-udp + namespace: envoy-gateway + sectionName: udp +xdsIR: + envoy-gateway/gateway-udp: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-udp-2934226e + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-udp + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-udp-2934226e + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-udp + protocol: TCP + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + udp: + - address: 0.0.0.0 + externalPort: 8088 + metadata: + kind: Gateway + name: gateway-udp + namespace: envoy-gateway + sectionName: udp + name: envoy-gateway/gateway-udp/udp + port: 8088 + route: + destination: + metadata: + kind: UDPRoute + name: udpr-app + namespace: default + name: udproute/default/udpr-app/rule/-1 + name: udproute/default/udpr-app