Skip to content

Commit

Permalink
feat:support configuring xff trusted cidrs
Browse files Browse the repository at this point in the history
Signed-off-by: Rudrakh Panigrahi <[email protected]>
  • Loading branch information
rudrakhp committed Nov 11, 2024
1 parent ec56a83 commit 3ba436b
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 51 deletions.
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 @@ const (
)

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 @@ func (h HTTPListener) Validate() error {
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
40 changes: 32 additions & 8 deletions internal/xds/translator/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package translator

import (
"errors"
xffv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/original_ip_detection/xff/v3"
"google.golang.org/protobuf/types/known/anypb"
"strconv"
"strings"

Expand Down Expand Up @@ -108,13 +110,6 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions {
return out
}

func xffNumTrustedHops(clientIPDetection *ir.ClientIPDetectionSettings) uint32 {
if clientIPDetection != nil && clientIPDetection.XForwardedFor != nil && clientIPDetection.XForwardedFor.NumTrustedHops != nil {
return *clientIPDetection.XForwardedFor.NumTrustedHops
}
return 0
}

func originalIPDetectionExtensions(clientIPDetection *ir.ClientIPDetectionSettings) []*corev3.TypedExtensionConfig {
// Return early if settings are nil
if clientIPDetection == nil {
Expand Down Expand Up @@ -143,6 +138,36 @@ func originalIPDetectionExtensions(clientIPDetection *ir.ClientIPDetectionSettin
})
}

if clientIPDetection.XForwardedFor != nil {
var xffHeaderConfigAny *anypb.Any
if clientIPDetection.XForwardedFor.NumTrustedHops != nil {
xffHeaderConfigAny, _ = protocov.ToAnyWithValidation(&xffv3.XffConfig{
XffNumTrustedHops: *clientIPDetection.XForwardedFor.NumTrustedHops,
})
} else 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,
},
})
}

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

return extensionConfig
}

Expand Down Expand Up @@ -314,7 +339,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: "0.0.0.0"
port: 8084
hostnames:
- "*"

Check failure on line 59 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

59:5 [indentation] wrong indentation: expected 2 but found 4

Check failure on line 59 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

59:5 [indentation] wrong indentation: expected 2 but found 4
routes:
- name: "fourth-route"

Check failure on line 61 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

61:5 [indentation] wrong indentation: expected 2 but found 4

Check failure on line 61 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

61:5 [indentation] wrong indentation: expected 2 but found 4
hostname: "*"
destination:
name: "fourth-route-dest"
settings:
- endpoints:

Check failure on line 66 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

66:11 [indentation] wrong indentation: expected 8 but found 10

Check failure on line 66 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

66:11 [indentation] wrong indentation: expected 8 but found 10
- host: "4.4.4.4"

Check failure on line 67 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

67:15 [indentation] wrong indentation: expected 12 but found 14

Check failure on line 67 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

67:15 [indentation] wrong indentation: expected 12 but found 14
port: 8084
clientIPDetection:
xForwardedFor:
trustedCidrs:
- "192.168.1.0/24"

Check failure on line 72 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

72:9 [indentation] wrong indentation: expected 6 but found 8
- "10.0.0.0/16"
- "172.16.0.0/12"

Check failure on line 74 in internal/xds/translator/testdata/in/xds-ir/client-ip-detection.yaml

View workflow job for this annotation

GitHub Actions / lint

74:26 [new-line-at-end-of-file] no new line character at the end of file
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_ONLY
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,19 @@
'@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
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 +113,47 @@
name: third-listener
name: third-listener
perConnectionBufferLimitBytes: 32768
- address:
socketAddress:
address: 0.0.0.0
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
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

0 comments on commit 3ba436b

Please sign in to comment.