Skip to content

Commit 77655cb

Browse files
arkodgdavem-git
andauthored
support TCPRoute Authz in xDS translator (#7184)
* support TCPRoute Authz in xDS translator Relates to #4908 Signed-off-by: Arko Dasgupta <arko@tetrate.io> Co-authored-by: davem-git <demathieu@gmail.com>
1 parent c0e11d4 commit 77655cb

File tree

9 files changed

+337
-37
lines changed

9 files changed

+337
-37
lines changed

internal/ir/xds.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2041,6 +2041,8 @@ type TCPRoute struct {
20412041
BackendConnection *BackendConnection `json:"backendConnection,omitempty" yaml:"backendConnection,omitempty"`
20422042
// DNS is used to configure how DNS resolution is handled for the route
20432043
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
2044+
// Authorization defines the schema for the authorization.
2045+
Authorization *Authorization `json:"authorization,omitempty" yaml:"authorization,omitempty"`
20442046
}
20452047

20462048
// TLS holds information for configuring TLS on a listener

internal/ir/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright Envoy Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package translator
7+
8+
import (
9+
cncfv3 "github.com/cncf/xds/go/xds/core/v3"
10+
matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3"
11+
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
12+
rbacconfigv3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
13+
networkrbacv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
14+
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
15+
"google.golang.org/protobuf/types/known/anypb"
16+
17+
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
18+
"github.com/envoyproxy/gateway/internal/ir"
19+
"github.com/envoyproxy/gateway/internal/utils/proto"
20+
)
21+
22+
func buildTCPRBACFilter(statPrefix string, authorization *ir.Authorization) (*listenerv3.Filter, error) {
23+
if authorization == nil {
24+
return nil, nil
25+
}
26+
27+
rbacCfg, err := buildTCPRBACConfig(statPrefix, authorization)
28+
if err != nil {
29+
return nil, err
30+
}
31+
if rbacCfg == nil {
32+
return nil, nil
33+
}
34+
35+
return toNetworkFilter(wellknown.RoleBasedAccessControl, rbacCfg)
36+
}
37+
38+
func buildTCPRBACConfig(statPrefix string, authorization *ir.Authorization) (*networkrbacv3.RBAC, error) {
39+
allowAction, denyAction, err := buildTCPRBACActions()
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
matchers := make([]*matcherv3.Matcher_MatcherList_FieldMatcher, 0, len(authorization.Rules))
45+
for _, rule := range authorization.Rules {
46+
predicate, err := buildTCPPrincipalPredicate(rule.Principal)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
ruleAction := allowAction
52+
if rule.Action == egv1a1.AuthorizationActionDeny {
53+
ruleAction = denyAction
54+
}
55+
56+
matchers = append(matchers, &matcherv3.Matcher_MatcherList_FieldMatcher{
57+
Predicate: predicate,
58+
OnMatch: &matcherv3.Matcher_OnMatch{
59+
OnMatch: &matcherv3.Matcher_OnMatch_Action{
60+
Action: &cncfv3.TypedExtensionConfig{
61+
Name: rule.Name,
62+
TypedConfig: ruleAction,
63+
},
64+
},
65+
},
66+
})
67+
}
68+
69+
defaultAction := denyAction
70+
if authorization.DefaultAction == egv1a1.AuthorizationActionAllow {
71+
defaultAction = allowAction
72+
}
73+
74+
matcher := &matcherv3.Matcher{
75+
OnNoMatch: &matcherv3.Matcher_OnMatch{
76+
OnMatch: &matcherv3.Matcher_OnMatch_Action{
77+
Action: &cncfv3.TypedExtensionConfig{
78+
Name: "default",
79+
TypedConfig: defaultAction,
80+
},
81+
},
82+
},
83+
}
84+
if len(matchers) > 0 {
85+
matcher.MatcherType = &matcherv3.Matcher_MatcherList_{
86+
MatcherList: &matcherv3.Matcher_MatcherList{
87+
Matchers: matchers,
88+
},
89+
}
90+
} else {
91+
matcher.MatcherType = nil
92+
}
93+
94+
return &networkrbacv3.RBAC{
95+
StatPrefix: statPrefix,
96+
Matcher: matcher,
97+
}, nil
98+
}
99+
100+
func buildTCPRBACActions() (*anypb.Any, *anypb.Any, error) {
101+
allowAction, err := proto.ToAnyWithValidation(&rbacconfigv3.Action{
102+
Name: "ALLOW",
103+
Action: rbacconfigv3.RBAC_ALLOW,
104+
})
105+
if err != nil {
106+
return nil, nil, err
107+
}
108+
109+
denyAction, err := proto.ToAnyWithValidation(&rbacconfigv3.Action{
110+
Name: "DENY",
111+
Action: rbacconfigv3.RBAC_DENY,
112+
})
113+
if err != nil {
114+
return nil, nil, err
115+
}
116+
117+
return allowAction, denyAction, nil
118+
}
119+
120+
func buildTCPPrincipalPredicate(principal ir.Principal) (*matcherv3.Matcher_MatcherList_Predicate, error) {
121+
// only build predicate for CIDR
122+
if len(principal.ClientCIDRs) == 0 {
123+
return nil, nil
124+
}
125+
return buildIPPredicate(principal.ClientCIDRs)
126+
}

internal/xds/translator/listener.go

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -640,47 +640,19 @@ func (t *Translator) addXdsTCPFilterChain(
640640

641641
// Append port to the statPrefix.
642642
statPrefix = strings.Join([]string{statPrefix, strconv.Itoa(int(xdsListener.Address.GetSocketAddress().GetPortValue()))}, "-")
643-
al, error := buildXdsAccessLog(accesslog, ir.ProxyAccessLogTypeRoute)
644-
if error != nil {
645-
return error
646-
}
647-
mgr := &tcpv3.TcpProxy{
648-
AccessLog: al,
649-
StatPrefix: statPrefix,
650-
ClusterSpecifier: &tcpv3.TcpProxy_Cluster{
651-
Cluster: clusterName,
652-
},
653-
HashPolicy: buildTCPProxyHashPolicy(irRoute.LoadBalancer),
654-
}
655-
656-
if timeout != nil && timeout.TCP != nil {
657-
if timeout.TCP.IdleTimeout != nil {
658-
mgr.IdleTimeout = durationpb.New(timeout.TCP.IdleTimeout.Duration)
659-
}
660-
}
661-
662-
var filters []*listenerv3.Filter
663-
664-
if connection != nil && connection.ConnectionLimit != nil {
665-
cl := buildConnectionLimitFilter(statPrefix, connection)
666-
if clf, err := toNetworkFilter(networkConnectionLimit, cl); err == nil {
667-
filters = append(filters, clf)
668-
} else {
669-
return err
670-
}
671-
}
672643

673-
if mgrf, err := toNetworkFilter(wellknown.TCPProxy, mgr); err == nil {
674-
filters = append(filters, mgrf)
675-
} else {
644+
filterChain, err := buildTCPFilterChain(
645+
irRoute,
646+
clusterName,
647+
statPrefix,
648+
accesslog,
649+
timeout,
650+
connection,
651+
)
652+
if err != nil {
676653
return err
677654
}
678655

679-
filterChain := &listenerv3.FilterChain{
680-
Name: tlsListenerFilterChainName(irRoute),
681-
Filters: filters,
682-
}
683-
684656
if isTLSPassthrough {
685657
if err := addServerNamesMatch(xdsListener, filterChain, irRoute.TLS.TLSInspectorConfig.SNIs); err != nil {
686658
return err
@@ -707,6 +679,61 @@ func (t *Translator) addXdsTCPFilterChain(
707679
return nil
708680
}
709681

682+
func buildTCPFilterChain(
683+
irRoute *ir.TCPRoute,
684+
clusterName string,
685+
statPrefix string,
686+
accesslog *ir.AccessLog,
687+
timeout *ir.ClientTimeout,
688+
connection *ir.ClientConnection,
689+
) (*listenerv3.FilterChain, error) {
690+
var filters []*listenerv3.Filter
691+
692+
al, err := buildXdsAccessLog(accesslog, ir.ProxyAccessLogTypeRoute)
693+
if err != nil {
694+
return nil, err
695+
}
696+
697+
if authzFilter, err := buildTCPRBACFilter(statPrefix, irRoute.Authorization); err != nil {
698+
return nil, err
699+
} else if authzFilter != nil {
700+
filters = append(filters, authzFilter)
701+
}
702+
703+
// Connection limit (if configured)
704+
if connection != nil && connection.ConnectionLimit != nil {
705+
cl := buildConnectionLimitFilter(statPrefix, connection)
706+
if clf, err := toNetworkFilter(networkConnectionLimit, cl); err == nil {
707+
filters = append(filters, clf)
708+
} else {
709+
return nil, err
710+
}
711+
}
712+
713+
// TCP proxy last
714+
mgr := &tcpv3.TcpProxy{
715+
AccessLog: al,
716+
StatPrefix: statPrefix,
717+
ClusterSpecifier: &tcpv3.TcpProxy_Cluster{
718+
Cluster: clusterName,
719+
},
720+
HashPolicy: buildTCPProxyHashPolicy(irRoute.LoadBalancer),
721+
}
722+
if timeout != nil && timeout.TCP != nil && timeout.TCP.IdleTimeout != nil {
723+
mgr.IdleTimeout = durationpb.New(timeout.TCP.IdleTimeout.Duration)
724+
}
725+
if mgrf, err := toNetworkFilter(wellknown.TCPProxy, mgr); err == nil {
726+
filters = append(filters, mgrf)
727+
} else {
728+
return nil, err
729+
}
730+
731+
return &listenerv3.FilterChain{
732+
Filters: filters,
733+
Name: tlsListenerFilterChainName(irRoute),
734+
}, nil
735+
}
736+
710737
func buildConnectionLimitFilter(statPrefix string, connection *ir.ClientConnection) *connection_limitv3.ConnectionLimit {
711738
cl := &connection_limitv3.ConnectionLimit{
712739
StatPrefix: statPrefix,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tcp:
2+
- name: "tcp-listener-authorization"
3+
address: "::"
4+
port: 10080
5+
routes:
6+
- name: "tcp-route-authorization"
7+
authorization:
8+
defaultAction: Allow
9+
rules:
10+
- action: Deny
11+
name: deny-office
12+
principal:
13+
clientCIDRs:
14+
- cidr: 10.0.0.0/24
15+
distinct: false
16+
ip: 10.0.0.0
17+
isIPv6: false
18+
maskLen: 24
19+
- action: Allow
20+
name: allow-corp
21+
principal:
22+
clientCIDRs:
23+
- cidr: 192.168.100.0/24
24+
distinct: false
25+
ip: 192.168.100.0
26+
isIPv6: false
27+
maskLen: 24
28+
destination:
29+
name: "tcp-route-authorization-dest"
30+
settings:
31+
- endpoints:
32+
- host: "10.2.3.4"
33+
port: 50000
34+
name: "tcp-route-authorization-dest/backend/0"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
- circuitBreakers:
2+
thresholds:
3+
- maxRetries: 1024
4+
commonLbConfig: {}
5+
connectTimeout: 10s
6+
dnsLookupFamily: V4_PREFERRED
7+
edsClusterConfig:
8+
edsConfig:
9+
ads: {}
10+
resourceApiVersion: V3
11+
serviceName: tcp-route-authorization-dest
12+
ignoreHealthOnHostRemoval: true
13+
lbPolicy: LEAST_REQUEST
14+
loadBalancingPolicy:
15+
policies:
16+
- typedExtensionConfig:
17+
name: envoy.load_balancing_policies.least_request
18+
typedConfig:
19+
'@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
20+
localityLbConfig:
21+
localityWeightedLbConfig: {}
22+
name: tcp-route-authorization-dest
23+
perConnectionBufferLimitBytes: 32768
24+
type: EDS
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- clusterName: tcp-route-authorization-dest
2+
endpoints:
3+
- lbEndpoints:
4+
- endpoint:
5+
address:
6+
socketAddress:
7+
address: 10.2.3.4
8+
portValue: 50000
9+
loadBalancingWeight: 1
10+
loadBalancingWeight: 1
11+
locality:
12+
region: tcp-route-authorization-dest/backend/0

0 commit comments

Comments
 (0)