Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:support configuring xff trusted cidrs #4702

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 52 additions & 41 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,47 +33,49 @@
)

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

redacted = []byte("[redacted]")
)
Expand Down Expand Up @@ -348,6 +350,15 @@
errs = errors.Join(errs, err)
}
}
if h.ClientIPDetection != nil {
if h.ClientIPDetection.XForwardedFor != nil && h.ClientIPDetection.CustomHeader != nil {
errs = errors.Join(errs, ErrBothXForwardedForAndCustomHeaderInvalid)
} else if h.ClientIPDetection.XForwardedFor != nil {
if h.ClientIPDetection.XForwardedFor.NumTrustedHops != nil && h.ClientIPDetection.XForwardedFor.TrustedCIDRs != nil {
errs = errors.Join(errs, ErrBothNumTrustedHopsAndTrustedCIDRsInvalid)
}

Check warning on line 359 in internal/ir/xds.go

View check run for this annotation

Codecov / codecov/patch

internal/ir/xds.go#L354-L359

Added lines #L354 - L359 were not covered by tests
}
}
return errs
}

Expand Down
33 changes: 32 additions & 1 deletion internal/xds/translator/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import (
early_header_mutationv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/early_header_mutation/header_mutation/v3"
preservecasev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3"
customheaderv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/original_ip_detection/custom_header/v3"
xffv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/original_ip_detection/xff/v3"
quicv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/quic/v3"
tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
"k8s.io/utils/ptr"
Expand Down Expand Up @@ -141,6 +143,36 @@ func originalIPDetectionExtensions(clientIPDetection *ir.ClientIPDetectionSettin
Name: "envoy.extensions.http.original_ip_detection.custom_header",
TypedConfig: customHeaderConfigAny,
})
} else if clientIPDetection.XForwardedFor != nil {
var xffHeaderConfigAny *anypb.Any
if clientIPDetection.XForwardedFor.TrustedCIDRs != nil {
trustedCidrs := make([]*corev3.CidrRange, 0)
for _, cidr := range clientIPDetection.XForwardedFor.TrustedCIDRs {
parsedCidr := strings.Split(string(cidr), "/")
addressPrefix := parsedCidr[0]
prefixLen, _ := strconv.ParseUint(parsedCidr[1], 10, 32)
trustedCidrs = append(trustedCidrs, &corev3.CidrRange{
AddressPrefix: addressPrefix,
PrefixLen: wrapperspb.UInt32(uint32(prefixLen)),
})
}
xffHeaderConfigAny, _ = protocov.ToAnyWithValidation(&xffv3.XffConfig{
XffTrustedCidrs: &xffv3.XffTrustedCidrs{
Cidrs: trustedCidrs,
},
SkipXffAppend: wrapperspb.Bool(false),
})
} else if clientIPDetection.XForwardedFor.NumTrustedHops != nil {
xffHeaderConfigAny, _ = protocov.ToAnyWithValidation(&xffv3.XffConfig{
XffNumTrustedHops: xffNumTrustedHops(clientIPDetection),
SkipXffAppend: wrapperspb.Bool(false),
})
}

extensionConfig = append(extensionConfig, &corev3.TypedExtensionConfig{
Name: "envoy.extensions.http.original_ip_detection.xff",
TypedConfig: xffHeaderConfigAny,
})
}

return extensionConfig
Expand Down Expand Up @@ -292,7 +324,6 @@ func (t *Translator) addHCMToXDSListener(xdsListener *listenerv3.Listener, irLis
Http2ProtocolOptions: http2ProtocolOptions(irListener.HTTP2),
// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
UseRemoteAddress: &wrapperspb.BoolValue{Value: useRemoteAddress},
XffNumTrustedHops: xffNumTrustedHops(irListener.ClientIPDetection),
OriginalIpDetectionExtensions: originalIPDetectionExtensions,
// normalize paths according to RFC 3986
NormalizePath: &wrapperspb.BoolValue{Value: true},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,23 @@ http:
customHeader:
name: "x-my-custom-header"
failClosed: true
- name: "fourth-listener"
address: "::"
port: 8084
hostnames:
- "*"
routes:
- name: "fourth-route"
hostname: "*"
destination:
name: "fourth-route-dest"
settings:
- endpoints:
- host: "4.4.4.4"
port: 8084
clientIPDetection:
xForwardedFor:
trustedCidrs:
- "192.168.1.0/24"
- "10.0.0.0/16"
- "172.16.0.0/12"
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,21 @@
outlierDetection: {}
perConnectionBufferLimitBytes: 32768
type: EDS
- circuitBreakers:
thresholds:
- maxRetries: 1024
commonLbConfig:
localityWeightedLbConfig: {}
connectTimeout: 10s
dnsLookupFamily: V4_PREFERRED
edsClusterConfig:
edsConfig:
ads: {}
resourceApiVersion: V3
serviceName: fourth-route-dest
ignoreHealthOnHostRemoval: true
lbPolicy: LEAST_REQUEST
name: fourth-route-dest
outlierDetection: {}
perConnectionBufferLimitBytes: 32768
type: EDS
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@
loadBalancingWeight: 1
locality:
region: third-route-dest/backend/0
- clusterName: fourth-route-dest
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: 4.4.4.4
portValue: 8084
loadBalancingWeight: 1
loadBalancingWeight: 1
locality:
region: fourth-route-dest/backend/0
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
suppressEnvoyHeaders: true
normalizePath: true
originalIpDetectionExtensions:
- name: envoy.extensions.http.original_ip_detection.xff
typedConfig:
'@type': type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig
skipXffAppend: false
xffNumTrustedHops: 2
rds:
configSource:
ads: {}
resourceApiVersion: V3
routeConfigName: first-listener
serverHeaderTransformation: PASS_THROUGH
statPrefix: http-8081
useRemoteAddress: true
xffNumTrustedHops: 2
useRemoteAddress: false
name: first-listener
name: first-listener
perConnectionBufferLimitBytes: 32768
Expand Down Expand Up @@ -109,3 +114,48 @@
name: third-listener
name: third-listener
perConnectionBufferLimitBytes: 32768
- address:
socketAddress:
address: '::'
portValue: 8084
defaultFilterChain:
filters:
- name: envoy.filters.network.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
commonHttpProtocolOptions:
headersWithUnderscoresAction: REJECT_REQUEST
http2ProtocolOptions:
initialConnectionWindowSize: 1048576
initialStreamWindowSize: 65536
maxConcurrentStreams: 100
httpFilters:
- name: envoy.filters.http.router
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
suppressEnvoyHeaders: true
normalizePath: true
originalIpDetectionExtensions:
- name: envoy.extensions.http.original_ip_detection.xff
typedConfig:
'@type': type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig
skipXffAppend: false
xffTrustedCidrs:
cidrs:
- addressPrefix: 192.168.1.0
prefixLen: 24
- addressPrefix: 10.0.0.0
prefixLen: 16
- addressPrefix: 172.16.0.0
prefixLen: 12
rds:
configSource:
ads: {}
resourceApiVersion: V3
routeConfigName: fourth-listener
serverHeaderTransformation: PASS_THROUGH
statPrefix: http-8084
useRemoteAddress: false
name: fourth-listener
name: fourth-listener
perConnectionBufferLimitBytes: 32768
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,17 @@
cluster: third-route-dest
upgradeConfigs:
- upgradeType: websocket
- ignorePortInHostMatching: true
name: fourth-listener
virtualHosts:
- domains:
- '*'
name: fourth-listener/*
routes:
- match:
prefix: /
name: fourth-route
route:
cluster: fourth-route-dest
upgradeConfigs:
- upgradeType: websocket
Loading
Loading