Skip to content

Commit 0a3a877

Browse files
author
speruri
committed
Add support for NLB target group attributes (ProxyProtocolV2, PreserveClientIP)
This change adds support for configuring two NLB-specific target group attributes: 1. Proxy Protocol v2: Enable with `--nlb-proxy-protocol-v2` flag (default: false) - Enables Proxy Protocol v2 for NLB target groups - Opt-in feature, disabled by default 2. Preserve Client IP: Configure with `--nlb-preserve-client-ip` flag (default: true) - Preserves client IP address in NLB target group connections - Enabled by default These attributes are only applied to Network Load Balancer target groups and do not affect Application Load Balancers. BREAKING CHANGE: The `preserve_client_ip` attribute is now explicitly set to true by the controller on all NLB target groups. Previously, this attribute was not explicitly set, which relied on AWS NLB's default behavior (which is also true). If your NLB target groups currently have `preserve_client_ip=false` set outside of the controller, updating to this version will override it to true. To maintain the previous behavior of `preserve_client_ip=false`, pass the `--nlb-preserve-client-ip=false` flag when running the controller. Changes: - Add NLB attribute flags to controller CLI - Extend AWS adapter with WithNLBProxyProtocolV2() and WithNLBPreserveClientIP() methods - Update CloudFormation template generation to conditionally add NLB attributes - Add comprehensive test coverage (5 test cases) - Update README with v0.20 upgrade notes and breaking change documentation Signed-off-by: speruri <surya.srikar.peruri@zalando.de>
1 parent e7cbdf6 commit 0a3a877

File tree

7 files changed

+141
-7
lines changed

7 files changed

+141
-7
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,28 @@ This information is used to manage AWS resources for each ingress objects of the
4343
- Support for `ipv4` and `ipv6` target group IP address types for ALB and NLB
4444
- set default target group IP address type using `--target-group-ip-address-type=ipv6`
4545
- IPv6 targets require dualstack load balancers (`--ip-addr-type=dualstack`)
46+
- Support for NLB target group attributes (Network Load Balancers only)
47+
- Proxy Protocol v2: enable with `--nlb-proxy-protocol-v2` (default: false)
48+
- Preserve Client IP: configure with `--nlb-preserve-client-ip` (default: true, matching AWS NLB default)
4649

4750
## Upgrade
4851

52+
### <v0.20 to >=v0.20
53+
54+
Version `v0.20` adds support for NLB target group attributes:
55+
56+
- **Proxy Protocol v2**: Enable with `--nlb-proxy-protocol-v2` flag (default: false, disabled)
57+
- Enables Proxy Protocol v2 for Network Load Balancer target groups
58+
- Only applies to NLBs; ALBs do not support this feature
59+
60+
- **Preserve Client IP**: Configure with `--nlb-preserve-client-ip` flag (default: true, enabled)
61+
- Preserves client IP address in NLB target group connections
62+
- Defaults to true, matching AWS NLB default behavior
63+
- Set to false to override and disable this feature
64+
- **Breaking change**: Previously this attribute was not explicitly set. Updating to v0.20 will set `preserve_client_ip.enabled=true` on all NLB target groups. For setups that require it disabled, use `--nlb-preserve-client-ip=false`
65+
66+
These attributes are only applied to Network Load Balancer target groups. Application Load Balancers are not affected.
67+
4968
### <v0.19 to >=v0.19
5069

5170
Version `v0.19` adds support for IPv6 target group IP address type. When using IPv6 targets, ensure your load balancer is configured as dualstack (`--ip-addr-type=dualstack` or `alb.ingress.kubernetes.io/ip-address-type: dualstack`). IPv4-only load balancers cannot route to IPv6 targets and will fail with a clear error message.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v0.19
1+
v0.20

aws/adapter.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ type Adapter struct {
7070
httpRedirectToHTTPS bool
7171
nlbCrossZone bool
7272
nlbHTTPEnabled bool
73+
nlbProxyProtocolV2Enabled bool
74+
nlbPreserveClientIPEnabled bool
7375
customFilter string
7476
internalDomains []string
7577
denyInternalDomains bool
@@ -495,6 +497,20 @@ func (a *Adapter) WithNLBHTTPEnabled(nlbHTTPEnabled bool) *Adapter {
495497
return a
496498
}
497499

500+
// WithNLBProxyProtocolV2 returns the receiver adapter after setting the
501+
// nlbProxyProtocolV2Enabled config.
502+
func (a *Adapter) WithNLBProxyProtocolV2(nlbProxyProtocolV2Enabled bool) *Adapter {
503+
a.nlbProxyProtocolV2Enabled = nlbProxyProtocolV2Enabled
504+
return a
505+
}
506+
507+
// WithNLBPreserveClientIP returns the receiver adapter after setting the
508+
// nlbPreserveClientIPEnabled config.
509+
func (a *Adapter) WithNLBPreserveClientIP(nlbPreserveClientIPEnabled bool) *Adapter {
510+
a.nlbPreserveClientIPEnabled = nlbPreserveClientIPEnabled
511+
return a
512+
}
513+
498514
// WithCustomFilter returns the receiver adapter after setting a custom filter expression
499515
func (a *Adapter) WithCustomFilter(customFilter string) *Adapter {
500516
a.customFilter = customFilter
@@ -841,6 +857,8 @@ func (a *Adapter) CreateStack(ctx context.Context, certificateARNs []string, sch
841857
httpRedirectToHTTPS: a.httpRedirectToHTTPS,
842858
nlbCrossZone: a.nlbCrossZone,
843859
nlbZoneAffinity: a.nlbZoneAffinity,
860+
nlbProxyProtocolV2Enabled: a.nlbProxyProtocolV2Enabled,
861+
nlbPreserveClientIPEnabled: a.nlbPreserveClientIPEnabled,
844862
http2: http2,
845863
tags: a.stackTags,
846864
internalDomains: a.internalDomains,
@@ -903,6 +921,8 @@ func (a *Adapter) UpdateStack(ctx context.Context, stackName string, certificate
903921
httpRedirectToHTTPS: a.httpRedirectToHTTPS,
904922
nlbCrossZone: a.nlbCrossZone,
905923
nlbZoneAffinity: a.nlbZoneAffinity,
924+
nlbProxyProtocolV2Enabled: a.nlbProxyProtocolV2Enabled,
925+
nlbPreserveClientIPEnabled: a.nlbPreserveClientIPEnabled,
906926
http2: http2,
907927
tags: a.stackTags,
908928
internalDomains: a.internalDomains,

aws/cf.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ type stackSpec struct {
191191
albLogsS3Prefix string
192192
wafWebAclId string
193193
nlbZoneAffinity string
194+
nlbProxyProtocolV2Enabled bool
195+
nlbPreserveClientIPEnabled bool
194196
cwAlarms CloudWatchAlarmList
195197
httpRedirectToHTTPS bool
196198
nlbCrossZone bool

aws/cf_template.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,13 +487,32 @@ func newTargetGroup(spec *stackSpec, targetPortParameter string) *cloudformation
487487
healthCheckProtocol = "HTTPS"
488488
}
489489

490-
targetGroup := &cloudformation.ElasticLoadBalancingV2TargetGroup{
491-
TargetGroupAttributes: &cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttributeList{
492-
{
493-
Key: cloudformation.String("deregistration_delay.timeout_seconds"),
494-
Value: cloudformation.String(fmt.Sprintf("%d", spec.deregistrationDelayTimeoutSeconds)),
495-
},
490+
// Build target group attributes
491+
attrsList := cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttributeList{
492+
{
493+
Key: cloudformation.String("deregistration_delay.timeout_seconds"),
494+
Value: cloudformation.String(fmt.Sprintf("%d", spec.deregistrationDelayTimeoutSeconds)),
496495
},
496+
}
497+
498+
// Add NLB-specific attributes
499+
if spec.loadbalancerType == LoadBalancerTypeNetwork {
500+
if spec.nlbProxyProtocolV2Enabled {
501+
attrsList = append(attrsList, cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttribute{
502+
Key: cloudformation.String("proxy_protocol_v2.enabled"),
503+
Value: cloudformation.String("true"),
504+
})
505+
}
506+
if spec.nlbPreserveClientIPEnabled {
507+
attrsList = append(attrsList, cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttribute{
508+
Key: cloudformation.String("preserve_client_ip.enabled"),
509+
Value: cloudformation.String("true"),
510+
})
511+
}
512+
}
513+
514+
targetGroup := &cloudformation.ElasticLoadBalancingV2TargetGroup{
515+
TargetGroupAttributes: &attrsList,
497516
HealthCheckIntervalSeconds: cloudformation.Ref(parameterTargetGroupHealthCheckIntervalParameter).Integer(),
498517
HealthCheckPath: cloudformation.Ref(parameterTargetGroupHealthCheckPathParameter).String(),
499518
HealthCheckPort: cloudformation.Ref(parameterTargetGroupHealthCheckPortParameter).String(),

aws/cf_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,3 +932,69 @@ func TestShouldDelete(t *testing.T) {
932932
}
933933

934934
}
935+
936+
func TestNLBTargetGroupAttributes(t *testing.T) {
937+
for _, ti := range []struct {
938+
name string
939+
givenSpec stackSpec
940+
expectedAttrCount int
941+
}{
942+
{
943+
name: "ALB-with-nlb-flags-ignored",
944+
givenSpec: stackSpec{
945+
loadbalancerType: LoadBalancerTypeApplication,
946+
nlbProxyProtocolV2Enabled: true,
947+
nlbPreserveClientIPEnabled: true,
948+
deregistrationDelayTimeoutSeconds: 30,
949+
},
950+
expectedAttrCount: 1, // only deregistration delay
951+
},
952+
{
953+
name: "NLB-with-proxy-protocol-v2",
954+
givenSpec: stackSpec{
955+
loadbalancerType: LoadBalancerTypeNetwork,
956+
nlbProxyProtocolV2Enabled: true,
957+
nlbPreserveClientIPEnabled: false,
958+
deregistrationDelayTimeoutSeconds: 30,
959+
},
960+
expectedAttrCount: 2, // deregistration delay + proxy protocol v2
961+
},
962+
{
963+
name: "NLB-with-preserve-client-ip",
964+
givenSpec: stackSpec{
965+
loadbalancerType: LoadBalancerTypeNetwork,
966+
nlbProxyProtocolV2Enabled: false,
967+
nlbPreserveClientIPEnabled: true,
968+
deregistrationDelayTimeoutSeconds: 30,
969+
},
970+
expectedAttrCount: 2, // deregistration delay + preserve client ip
971+
},
972+
{
973+
name: "NLB-with-both-attributes",
974+
givenSpec: stackSpec{
975+
loadbalancerType: LoadBalancerTypeNetwork,
976+
nlbProxyProtocolV2Enabled: true,
977+
nlbPreserveClientIPEnabled: true,
978+
deregistrationDelayTimeoutSeconds: 30,
979+
},
980+
expectedAttrCount: 3, // deregistration delay + both NLB attributes
981+
},
982+
{
983+
name: "NLB-with-no-extra-attributes",
984+
givenSpec: stackSpec{
985+
loadbalancerType: LoadBalancerTypeNetwork,
986+
nlbProxyProtocolV2Enabled: false,
987+
nlbPreserveClientIPEnabled: false,
988+
deregistrationDelayTimeoutSeconds: 30,
989+
},
990+
expectedAttrCount: 1, // only deregistration delay
991+
},
992+
} {
993+
t.Run(ti.name, func(t *testing.T) {
994+
tg := newTargetGroup(&ti.givenSpec, "TargetPort")
995+
assert.NotNil(t, tg)
996+
assert.NotNil(t, tg.TargetGroupAttributes)
997+
assert.Equal(t, ti.expectedAttrCount, len(*tg.TargetGroupAttributes), "target group attributes count mismatch")
998+
})
999+
}
1000+
}

controller.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ var (
8181
nlbZoneAffinity string
8282
nlbCrossZone bool
8383
nlbHTTPEnabled bool
84+
nlbProxyProtocolV2Enabled bool
85+
nlbPreserveClientIPEnabled bool
8486
ingressAPIVersion string
8587
internalDomains []string
8688
targetAccessMode string
@@ -183,6 +185,10 @@ func loadSettings() error {
183185
Default("false").BoolVar(&nlbCrossZone)
184186
kingpin.Flag("nlb-http-enabled", "Enable HTTP (port 80) for Network Load Balancers. By default this is disabled as NLB can't provide HTTP -> HTTPS redirect.").
185187
Default("false").BoolVar(&nlbHTTPEnabled)
188+
kingpin.Flag("nlb-proxy-protocol-v2", "Enable Proxy Protocol v2 for Network Load Balancers. This setting only applies to 'network' Load Balancers.").
189+
Default("false").BoolVar(&nlbProxyProtocolV2Enabled)
190+
kingpin.Flag("nlb-preserve-client-ip", "Enable preserve client IP address for Network Load Balancers. This setting only applies to 'network' Load Balancers.").
191+
Default("true").BoolVar(&nlbPreserveClientIPEnabled)
186192
kingpin.Flag("deny-internal-domains", "Sets a rule on ALB's Listeners that denies requests with the Host header as a internal domain. Domains can be set with the -internal-domains flag.").
187193
Default("false").BoolVar(&denyInternalDomains)
188194
kingpin.Flag("internal-domains", "Define the internal domains to be blocked when -deny-internal-domains is set to true. Set it multiple times for multiple domains. The maximum size of each name is 128 characters. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character).").
@@ -346,6 +352,8 @@ func main() {
346352
WithNLBCrossZone(nlbCrossZone).
347353
WithNLBZoneAffinity(nlbZoneAffinity).
348354
WithNLBHTTPEnabled(nlbHTTPEnabled).
355+
WithNLBProxyProtocolV2(nlbProxyProtocolV2Enabled).
356+
WithNLBPreserveClientIP(nlbPreserveClientIPEnabled).
349357
WithCustomFilter(customFilter).
350358
WithStackTags(additionalStackTags).
351359
WithInternalDomains(internalDomains).

0 commit comments

Comments
 (0)