Skip to content

Commit 73f5b21

Browse files
committed
feat:support configuring xff trusted cidrs
Signed-off-by: Rudrakh Panigrahi <[email protected]>
1 parent 9cbdded commit 73f5b21

9 files changed

+347
-46
lines changed

internal/ir/xds.go

+52-41
Original file line numberDiff line numberDiff line change
@@ -35,47 +35,49 @@ const (
3535
)
3636

3737
var (
38-
ErrListenerNameEmpty = errors.New("field Name must be specified")
39-
ErrListenerAddressInvalid = errors.New("field Address must be a valid IP address")
40-
ErrListenerPortInvalid = errors.New("field Port specified is invalid")
41-
ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry")
42-
ErrTCPRouteSNIsEmpty = errors.New("field SNIs must be specified with at least a single server name entry")
43-
ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified")
44-
ErrTLSPrivateKey = errors.New("field PrivateKey must be specified")
45-
ErrRouteNameEmpty = errors.New("field Name must be specified")
46-
ErrHTTPRouteHostnameEmpty = errors.New("field Hostname must be specified")
47-
ErrDestinationNameEmpty = errors.New("field Name must be specified")
48-
ErrDestEndpointHostInvalid = errors.New("field Address must be a valid IP or FQDN address")
49-
ErrDestEndpointPortInvalid = errors.New("field Port specified is invalid")
50-
ErrDestEndpointUDSPortInvalid = errors.New("field Port must not be specified for Unix Domain Socket address")
51-
ErrDestEndpointUDSHostInvalid = errors.New("field Host must not be specified for Unix Domain Socket address")
52-
ErrStringMatchConditionInvalid = errors.New("only one of the Exact, Prefix, SafeRegex or Distinct fields must be set")
53-
ErrStringMatchInvertDistinctInvalid = errors.New("only one of the Invert or Distinct fields can be set")
54-
ErrStringMatchNameIsEmpty = errors.New("field Name must be specified")
55-
ErrDirectResponseStatusInvalid = errors.New("only HTTP status codes 100 - 599 are supported for DirectResponse")
56-
ErrRedirectUnsupportedStatus = errors.New("only HTTP status codes 301 and 302 are supported for redirect filters")
57-
ErrRedirectUnsupportedScheme = errors.New("only http and https are supported for the scheme in redirect filters")
58-
ErrHTTPPathModifierDoubleReplace = errors.New("redirect filter cannot have a path modifier that supplies more than one of fullPathReplace, prefixMatchReplace and regexMatchReplace")
59-
ErrHTTPPathModifierNoReplace = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
60-
ErrHTTPPathRegexModifierNoSetting = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
61-
ErrHTTPHostModifierDoubleReplace = errors.New("redirect filter cannot have a host modifier that supplies more than one of Hostname, Header and Backend")
62-
ErrAddHeaderEmptyName = errors.New("header modifier filter cannot configure a header without a name to be added")
63-
ErrAddHeaderDuplicate = errors.New("header modifier filter attempts to add the same header more than once (case insensitive)")
64-
ErrRemoveHeaderDuplicate = errors.New("header modifier filter attempts to remove the same header more than once (case insensitive)")
65-
ErrLoadBalancerInvalid = errors.New("loadBalancer setting is invalid, only one setting can be set")
66-
ErrHealthCheckTimeoutInvalid = errors.New("field HealthCheck.Timeout must be specified")
67-
ErrHealthCheckIntervalInvalid = errors.New("field HealthCheck.Interval must be specified")
68-
ErrHealthCheckUnhealthyThresholdInvalid = errors.New("field HealthCheck.UnhealthyThreshold should be greater than 0")
69-
ErrHealthCheckHealthyThresholdInvalid = errors.New("field HealthCheck.HealthyThreshold should be greater than 0")
70-
ErrHealthCheckerInvalid = errors.New("health checker setting is invalid, only one health checker can be set")
71-
ErrHCHTTPHostInvalid = errors.New("field HTTPHealthChecker.Host should be specified")
72-
ErrHCHTTPPathInvalid = errors.New("field HTTPHealthChecker.Path should be specified")
73-
ErrHCHTTPMethodInvalid = errors.New("only one of the GET, HEAD, POST, DELETE, OPTIONS, TRACE, PATCH of HTTPHealthChecker.Method could be set")
74-
ErrHCHTTPExpectedStatusesInvalid = errors.New("field HTTPHealthChecker.ExpectedStatuses should be specified")
75-
ErrHealthCheckPayloadInvalid = errors.New("one of Text, Binary fields must be set in payload")
76-
ErrHTTPStatusInvalid = errors.New("HTTPStatus should be in [200,600)")
77-
ErrOutlierDetectionBaseEjectionTimeInvalid = errors.New("field OutlierDetection.BaseEjectionTime must be specified")
78-
ErrOutlierDetectionIntervalInvalid = errors.New("field OutlierDetection.Interval must be specified")
38+
ErrListenerNameEmpty = errors.New("field Name must be specified")
39+
ErrListenerAddressInvalid = errors.New("field Address must be a valid IP address")
40+
ErrListenerPortInvalid = errors.New("field Port specified is invalid")
41+
ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry")
42+
ErrTCPRouteSNIsEmpty = errors.New("field SNIs must be specified with at least a single server name entry")
43+
ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified")
44+
ErrTLSPrivateKey = errors.New("field PrivateKey must be specified")
45+
ErrRouteNameEmpty = errors.New("field Name must be specified")
46+
ErrHTTPRouteHostnameEmpty = errors.New("field Hostname must be specified")
47+
ErrDestinationNameEmpty = errors.New("field Name must be specified")
48+
ErrDestEndpointHostInvalid = errors.New("field Address must be a valid IP or FQDN address")
49+
ErrDestEndpointPortInvalid = errors.New("field Port specified is invalid")
50+
ErrDestEndpointUDSPortInvalid = errors.New("field Port must not be specified for Unix Domain Socket address")
51+
ErrDestEndpointUDSHostInvalid = errors.New("field Host must not be specified for Unix Domain Socket address")
52+
ErrStringMatchConditionInvalid = errors.New("only one of the Exact, Prefix, SafeRegex or Distinct fields must be set")
53+
ErrStringMatchInvertDistinctInvalid = errors.New("only one of the Invert or Distinct fields can be set")
54+
ErrStringMatchNameIsEmpty = errors.New("field Name must be specified")
55+
ErrDirectResponseStatusInvalid = errors.New("only HTTP status codes 100 - 599 are supported for DirectResponse")
56+
ErrRedirectUnsupportedStatus = errors.New("only HTTP status codes 301 and 302 are supported for redirect filters")
57+
ErrRedirectUnsupportedScheme = errors.New("only http and https are supported for the scheme in redirect filters")
58+
ErrHTTPPathModifierDoubleReplace = errors.New("redirect filter cannot have a path modifier that supplies more than one of fullPathReplace, prefixMatchReplace and regexMatchReplace")
59+
ErrHTTPPathModifierNoReplace = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
60+
ErrHTTPPathRegexModifierNoSetting = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
61+
ErrHTTPHostModifierDoubleReplace = errors.New("redirect filter cannot have a host modifier that supplies more than one of Hostname, Header and Backend")
62+
ErrAddHeaderEmptyName = errors.New("header modifier filter cannot configure a header without a name to be added")
63+
ErrAddHeaderDuplicate = errors.New("header modifier filter attempts to add the same header more than once (case insensitive)")
64+
ErrRemoveHeaderDuplicate = errors.New("header modifier filter attempts to remove the same header more than once (case insensitive)")
65+
ErrLoadBalancerInvalid = errors.New("loadBalancer setting is invalid, only one setting can be set")
66+
ErrHealthCheckTimeoutInvalid = errors.New("field HealthCheck.Timeout must be specified")
67+
ErrHealthCheckIntervalInvalid = errors.New("field HealthCheck.Interval must be specified")
68+
ErrHealthCheckUnhealthyThresholdInvalid = errors.New("field HealthCheck.UnhealthyThreshold should be greater than 0")
69+
ErrHealthCheckHealthyThresholdInvalid = errors.New("field HealthCheck.HealthyThreshold should be greater than 0")
70+
ErrHealthCheckerInvalid = errors.New("health checker setting is invalid, only one health checker can be set")
71+
ErrHCHTTPHostInvalid = errors.New("field HTTPHealthChecker.Host should be specified")
72+
ErrHCHTTPPathInvalid = errors.New("field HTTPHealthChecker.Path should be specified")
73+
ErrHCHTTPMethodInvalid = errors.New("only one of the GET, HEAD, POST, DELETE, OPTIONS, TRACE, PATCH of HTTPHealthChecker.Method could be set")
74+
ErrHCHTTPExpectedStatusesInvalid = errors.New("field HTTPHealthChecker.ExpectedStatuses should be specified")
75+
ErrHealthCheckPayloadInvalid = errors.New("one of Text, Binary fields must be set in payload")
76+
ErrHTTPStatusInvalid = errors.New("HTTPStatus should be in [200,600)")
77+
ErrOutlierDetectionBaseEjectionTimeInvalid = errors.New("field OutlierDetection.BaseEjectionTime must be specified")
78+
ErrOutlierDetectionIntervalInvalid = errors.New("field OutlierDetection.Interval must be specified")
79+
ErrBothXForwardedForAndCustomHeaderInvalid = errors.New("only one of ClientIPDetection.XForwardedFor and ClientIPDetection.CustomHeader must be set")
80+
ErrBothNumTrustedHopsAndTrustedCIDRsInvalid = errors.New("only one of ClientIPDetection.XForwardedFor.NumTrustedHops and ClientIPDetection.XForwardedFor.TrustedCIDRs must be set")
7981

8082
redacted = []byte("[redacted]")
8183
)
@@ -345,6 +347,15 @@ func (h HTTPListener) Validate() error {
345347
errs = errors.Join(errs, err)
346348
}
347349
}
350+
if h.ClientIPDetection != nil {
351+
if h.ClientIPDetection.XForwardedFor != nil && h.ClientIPDetection.CustomHeader != nil {
352+
errs = errors.Join(errs, ErrBothXForwardedForAndCustomHeaderInvalid)
353+
} else if h.ClientIPDetection.XForwardedFor != nil {
354+
if h.ClientIPDetection.XForwardedFor.NumTrustedHops != nil && h.ClientIPDetection.XForwardedFor.TrustedCIDRs != nil {
355+
errs = errors.Join(errs, ErrBothNumTrustedHopsAndTrustedCIDRsInvalid)
356+
}
357+
}
358+
}
348359
return errs
349360
}
350361

internal/xds/translator/listener.go

+37-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import (
2323
early_header_mutationv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/early_header_mutation/header_mutation/v3"
2424
preservecasev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3"
2525
customheaderv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/original_ip_detection/custom_header/v3"
26+
xffv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/original_ip_detection/xff/v3"
2627
quicv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/quic/v3"
2728
tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
2829
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
2930
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
3031
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
3132
"google.golang.org/protobuf/proto"
33+
"google.golang.org/protobuf/types/known/anypb"
3234
"google.golang.org/protobuf/types/known/durationpb"
3335
"google.golang.org/protobuf/types/known/wrapperspb"
3436
"k8s.io/utils/ptr"
@@ -108,9 +110,13 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions {
108110
return out
109111
}
110112

113+
// xffNumTrustedHops returns the number of hops to be configured in proxy
114+
// Need to decrement number of hops configured by EGW user by 1 for backward compatibility
115+
// See for more: https://github.com/envoyproxy/envoy/issues/34241
111116
func xffNumTrustedHops(clientIPDetection *ir.ClientIPDetectionSettings) uint32 {
112-
if clientIPDetection != nil && clientIPDetection.XForwardedFor != nil && clientIPDetection.XForwardedFor.NumTrustedHops != nil {
113-
return *clientIPDetection.XForwardedFor.NumTrustedHops
117+
if clientIPDetection != nil && clientIPDetection.XForwardedFor != nil &&
118+
clientIPDetection.XForwardedFor.NumTrustedHops != nil && *clientIPDetection.XForwardedFor.NumTrustedHops > 0 {
119+
return *clientIPDetection.XForwardedFor.NumTrustedHops - 1
114120
}
115121
return 0
116122
}
@@ -141,6 +147,35 @@ func originalIPDetectionExtensions(clientIPDetection *ir.ClientIPDetectionSettin
141147
Name: "envoy.extensions.http.original_ip_detection.custom_header",
142148
TypedConfig: customHeaderConfigAny,
143149
})
150+
} else if clientIPDetection.XForwardedFor != nil {
151+
var xffHeaderConfigAny *anypb.Any
152+
if clientIPDetection.XForwardedFor.TrustedCIDRs != nil {
153+
trustedCidrs := make([]*corev3.CidrRange, 0)
154+
for _, cidr := range clientIPDetection.XForwardedFor.TrustedCIDRs {
155+
parsedCidr := strings.Split(string(cidr), "/")
156+
addressPrefix := parsedCidr[0]
157+
prefixLen, _ := strconv.ParseUint(parsedCidr[1], 10, 32)
158+
trustedCidrs = append(trustedCidrs, &corev3.CidrRange{
159+
AddressPrefix: addressPrefix,
160+
PrefixLen: wrapperspb.UInt32(uint32(prefixLen)),
161+
})
162+
}
163+
xffHeaderConfigAny, _ = protocov.ToAnyWithValidation(&xffv3.XffConfig{
164+
XffTrustedCidrs: &xffv3.XffTrustedCidrs{
165+
Cidrs: trustedCidrs,
166+
},
167+
SkipXffAppend: wrapperspb.Bool(false),
168+
})
169+
} else if clientIPDetection.XForwardedFor.NumTrustedHops != nil {
170+
xffHeaderConfigAny, _ = protocov.ToAnyWithValidation(&xffv3.XffConfig{
171+
XffNumTrustedHops: xffNumTrustedHops(clientIPDetection),
172+
SkipXffAppend: wrapperspb.Bool(false),
173+
})
174+
}
175+
extensionConfig = append(extensionConfig, &corev3.TypedExtensionConfig{
176+
Name: "envoy.extensions.http.original_ip_detection.xff",
177+
TypedConfig: xffHeaderConfigAny,
178+
})
144179
}
145180

146181
return extensionConfig
@@ -292,7 +327,6 @@ func (t *Translator) addHCMToXDSListener(xdsListener *listenerv3.Listener, irLis
292327
Http2ProtocolOptions: http2ProtocolOptions(irListener.HTTP2),
293328
// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
294329
UseRemoteAddress: &wrapperspb.BoolValue{Value: useRemoteAddress},
295-
XffNumTrustedHops: xffNumTrustedHops(irListener.ClientIPDetection),
296330
OriginalIpDetectionExtensions: originalIPDetectionExtensions,
297331
// normalize paths according to RFC 3986
298332
NormalizePath: &wrapperspb.BoolValue{Value: true},

internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

+20
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,23 @@ http:
5252
customHeader:
5353
name: "x-my-custom-header"
5454
failClosed: true
55+
- name: "fourth-listener"
56+
address: "::"
57+
port: 8084
58+
hostnames:
59+
- "*"
60+
routes:
61+
- name: "fourth-route"
62+
hostname: "*"
63+
destination:
64+
name: "fourth-route-dest"
65+
settings:
66+
- endpoints:
67+
- host: "4.4.4.4"
68+
port: 8084
69+
clientIPDetection:
70+
xForwardedFor:
71+
trustedCidrs:
72+
- "192.168.1.0/24"
73+
- "10.0.0.0/16"
74+
- "172.16.0.0/12"

internal/xds/translator/testdata/out/xds-ir/client-ip-detection.clusters.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,20 @@
4949
name: third-route-dest
5050
perConnectionBufferLimitBytes: 32768
5151
type: EDS
52+
- circuitBreakers:
53+
thresholds:
54+
- maxRetries: 1024
55+
commonLbConfig:
56+
localityWeightedLbConfig: {}
57+
connectTimeout: 10s
58+
dnsLookupFamily: V4_PREFERRED
59+
edsClusterConfig:
60+
edsConfig:
61+
ads: {}
62+
resourceApiVersion: V3
63+
serviceName: fourth-route-dest
64+
ignoreHealthOnHostRemoval: true
65+
lbPolicy: LEAST_REQUEST
66+
name: fourth-route-dest
67+
perConnectionBufferLimitBytes: 32768
68+
type: EDS

internal/xds/translator/testdata/out/xds-ir/client-ip-detection.endpoints.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,15 @@
3434
loadBalancingWeight: 1
3535
locality:
3636
region: third-route-dest/backend/0
37+
- clusterName: fourth-route-dest
38+
endpoints:
39+
- lbEndpoints:
40+
- endpoint:
41+
address:
42+
socketAddress:
43+
address: 4.4.4.4
44+
portValue: 8084
45+
loadBalancingWeight: 1
46+
loadBalancingWeight: 1
47+
locality:
48+
region: fourth-route-dest/backend/0

internal/xds/translator/testdata/out/xds-ir/client-ip-detection.listeners.yaml

+52-2
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@
1919
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
2020
suppressEnvoyHeaders: true
2121
normalizePath: true
22+
originalIpDetectionExtensions:
23+
- name: envoy.extensions.http.original_ip_detection.xff
24+
typedConfig:
25+
'@type': type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig
26+
skipXffAppend: false
27+
xffNumTrustedHops: 1
2228
rds:
2329
configSource:
2430
ads: {}
2531
resourceApiVersion: V3
2632
routeConfigName: first-listener
2733
serverHeaderTransformation: PASS_THROUGH
2834
statPrefix: http-8081
29-
useRemoteAddress: true
30-
xffNumTrustedHops: 2
35+
useRemoteAddress: false
3136
name: first-listener
3237
name: first-listener
3338
perConnectionBufferLimitBytes: 32768
@@ -109,3 +114,48 @@
109114
name: third-listener
110115
name: third-listener
111116
perConnectionBufferLimitBytes: 32768
117+
- address:
118+
socketAddress:
119+
address: '::'
120+
portValue: 8084
121+
defaultFilterChain:
122+
filters:
123+
- name: envoy.filters.network.http_connection_manager
124+
typedConfig:
125+
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
126+
commonHttpProtocolOptions:
127+
headersWithUnderscoresAction: REJECT_REQUEST
128+
http2ProtocolOptions:
129+
initialConnectionWindowSize: 1048576
130+
initialStreamWindowSize: 65536
131+
maxConcurrentStreams: 100
132+
httpFilters:
133+
- name: envoy.filters.http.router
134+
typedConfig:
135+
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
136+
suppressEnvoyHeaders: true
137+
normalizePath: true
138+
originalIpDetectionExtensions:
139+
- name: envoy.extensions.http.original_ip_detection.xff
140+
typedConfig:
141+
'@type': type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig
142+
skipXffAppend: false
143+
xffTrustedCidrs:
144+
cidrs:
145+
- addressPrefix: 192.168.1.0
146+
prefixLen: 24
147+
- addressPrefix: 10.0.0.0
148+
prefixLen: 16
149+
- addressPrefix: 172.16.0.0
150+
prefixLen: 12
151+
rds:
152+
configSource:
153+
ads: {}
154+
resourceApiVersion: V3
155+
routeConfigName: fourth-listener
156+
serverHeaderTransformation: PASS_THROUGH
157+
statPrefix: http-8084
158+
useRemoteAddress: false
159+
name: fourth-listener
160+
name: fourth-listener
161+
perConnectionBufferLimitBytes: 32768

internal/xds/translator/testdata/out/xds-ir/client-ip-detection.routes.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,17 @@
4040
cluster: third-route-dest
4141
upgradeConfigs:
4242
- upgradeType: websocket
43+
- ignorePortInHostMatching: true
44+
name: fourth-listener
45+
virtualHosts:
46+
- domains:
47+
- '*'
48+
name: fourth-listener/*
49+
routes:
50+
- match:
51+
prefix: /
52+
name: fourth-route
53+
route:
54+
cluster: fourth-route-dest
55+
upgradeConfigs:
56+
- upgradeType: websocket

0 commit comments

Comments
 (0)