Skip to content

add support for configure static IPv6 addresses on NLB #2790

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

Merged
merged 2 commits into from
Sep 8, 2022
Merged
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
18 changes: 16 additions & 2 deletions docs/guide/service/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval](#healthcheck-interval) | integer | 10 | |
| [service.beta.kubernetes.io/aws-load-balancer-eip-allocations](#eip-allocations) | stringList | | internet-facing lb only. Length must match the number of subnets|
| [service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses](#private-ipv4-addresses) | stringList | | internal lb only. Length must match the number of subnets |
| [service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses](#ipv6-addresses) | stringList | | dualstack lb only. Length must match the number of subnets |
| [service.beta.kubernetes.io/aws-load-balancer-target-group-attributes](#target-group-attributes) | stringMap | | |
| [service.beta.kubernetes.io/aws-load-balancer-subnets](#subnets) | stringList | | |
| [service.beta.kubernetes.io/aws-load-balancer-alpn-policy](#alpn-policy) | stringList | | |
Expand Down Expand Up @@ -146,7 +147,7 @@ on the load balancer.
- <a name="eip-allocations">`service.beta.kubernetes.io/aws-load-balancer-eip-allocations`</a> specifies a list of [elastic IP address](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) configuration for an internet-facing NLB.

!!!note
- This configuration is optional and use can use it to assign static IP addresses to your NLB
- This configuration is optional, and you can use it to assign static IP addresses to your NLB
- You must specify the same number of eip allocations as load balancer subnets [annotation](#subnets)
- NLB must be internet-facing

Expand All @@ -160,7 +161,7 @@ on the load balancer.

!!!note
- NLB must be internal
- This configuration is optional and use can use it to assign static IP addresses to your NLB
- This configuration is optional, and you can use it to assign static IPv4 addresses to your NLB
- You must specify the same number of private IPv4 addresses as load balancer subnets [annotation](#subnets)
- You must specify the IPv4 addresses from the load balancer subnet IPv4 ranges

Expand All @@ -169,6 +170,19 @@ on the load balancer.
service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses: 192.168.10.15, 192.168.32.16
```

- <a name="ipv6-addresses">`service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses`</a> specifies a list of IPv6 addresses for an dualstack NLB.

!!!note
- NLB must be dualstack
- This configuration is optional, and you can use it to assign static IPv6 addresses to your NLB
- You must specify the same number of private IPv6 addresses as load balancer subnets [annotation](#subnets)
- You must specify the IPv6 addresses from the load balancer subnet IPv6 ranges

!!!example
```
service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses: 2600:1f13:837:8501::1, 2600:1f13:837:8504::1
```

## Traffic Listening
Traffic Listening can be controlled with following annotations:

Expand Down
5 changes: 3 additions & 2 deletions pkg/annotations/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ const (
SvcLBSuffixHCProtocol = "aws-load-balancer-healthcheck-protocol"
SvcLBSuffixHCPort = "aws-load-balancer-healthcheck-port"
SvcLBSuffixHCPath = "aws-load-balancer-healthcheck-path"
SvcLBSuffixEIPAllocations = "aws-load-balancer-eip-allocations"
SvcLBSuffixPrivateIpv4Addresses = "aws-load-balancer-private-ipv4-addresses"
SvcLBSuffixTargetGroupAttributes = "aws-load-balancer-target-group-attributes"
SvcLBSuffixSubnets = "aws-load-balancer-subnets"
SvcLBSuffixEIPAllocations = "aws-load-balancer-eip-allocations"
SvcLBSuffixPrivateIpv4Addresses = "aws-load-balancer-private-ipv4-addresses"
SvcLBSuffixIpv6Addresses = "aws-load-balancer-ipv6-addresses"
SvcLBSuffixALPNPolicy = "aws-load-balancer-alpn-policy"
SvcLBSuffixTargetNodeLabels = "aws-load-balancer-target-node-labels"
SvcLBSuffixLoadBalancerAttributes = "aws-load-balancer-attributes"
Expand Down
1 change: 1 addition & 0 deletions pkg/deploy/elbv2/load_balancer_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ func buildSDKSubnetMapping(modelSubnetMapping elbv2model.SubnetMapping) *elbv2sd
return &elbv2sdk.SubnetMapping{
AllocationId: modelSubnetMapping.AllocationID,
PrivateIPv4Address: modelSubnetMapping.PrivateIPv4Address,
IPv6Address: modelSubnetMapping.IPv6Address,
SubnetId: awssdk.String(modelSubnetMapping.SubnetID),
}
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/model/elbv2/load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type SubnetMapping struct {
// [Network Load Balancers] The private IPv4 address for an internal load balancer.
PrivateIPv4Address *string `json:"privateIPv4Address,omitempty"`

// [Network Load Balancers] The IPv6 address.
IPv6Address *string `json:"ipv6Address,omitempty"`

// The ID of the subnet.
SubnetID string `json:"subnetID"`
}
Expand Down
51 changes: 50 additions & 1 deletion pkg/networking/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package networking

import "inet.af/netaddr"
import (
awssdk "github.com/aws/aws-sdk-go/aws"
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
"inet.af/netaddr"
"net/netip"
)

// TODO: replace netaddr package with built-in netip package once golang 1.18 released: https://pkg.go.dev/net/netip@master#Prefix

Expand All @@ -26,3 +31,47 @@ func IsIPWithinCIDRs(ip netaddr.IP, cidrs []netaddr.IPPrefix) bool {
}
return false
}

// FilterIPsWithinCIDRs returns IP addresses that were within specified CIDRs.
func FilterIPsWithinCIDRs(ips []netip.Addr, cidrs []netip.Prefix) []netip.Addr {
var ipsWithinCIDRs []netip.Addr
for _, ip := range ips {
for _, cidr := range cidrs {
if cidr.Contains(ip) {
ipsWithinCIDRs = append(ipsWithinCIDRs, ip)
break
}
}
}
return ipsWithinCIDRs
}

// GetSubnetAssociatedIPv4CIDRs returns the IPv4 CIDRs associated with EC2 subnet
func GetSubnetAssociatedIPv4CIDRs(subnet *ec2sdk.Subnet) ([]netip.Prefix, error) {
if subnet.CidrBlock == nil {
return nil, nil
}
cidrBlock := awssdk.StringValue(subnet.CidrBlock)
ipv4CIDR, err := netip.ParsePrefix(cidrBlock)
if err != nil {
return nil, err
}
return []netip.Prefix{ipv4CIDR}, nil
}

// GetSubnetAssociatedIPv6CIDRs returns the IPv6 CIDRs associated with EC2 subnet
func GetSubnetAssociatedIPv6CIDRs(subnet *ec2sdk.Subnet) ([]netip.Prefix, error) {
var ipv6CIDRs []netip.Prefix
for _, cidrAssociation := range subnet.Ipv6CidrBlockAssociationSet {
if awssdk.StringValue(cidrAssociation.Ipv6CidrBlockState.State) != ec2sdk.SubnetCidrBlockStateCodeAssociated {
continue
}
cidrBlock := awssdk.StringValue(cidrAssociation.Ipv6CidrBlock)
ipv6CIDR, err := netip.ParsePrefix(cidrBlock)
if err != nil {
return nil, err
}
ipv6CIDRs = append(ipv6CIDRs, ipv6CIDR)
}
return ipv6CIDRs, nil
}
201 changes: 201 additions & 0 deletions pkg/networking/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package networking

import (
awssdk "github.com/aws/aws-sdk-go/aws"
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"inet.af/netaddr"
"net/netip"
"testing"
)

Expand Down Expand Up @@ -137,3 +140,201 @@ func TestIsIPWithinCIDRs(t *testing.T) {
})
}
}

func TestFilterIPsWithinCIDRs(t *testing.T) {
type args struct {
ips []netip.Addr
cidrs []netip.Prefix
}
tests := []struct {
name string
args args
want []netip.Addr
}{
{
name: "ipv4 addresses within one CIDR",
args: args{
ips: []netip.Addr{
netip.MustParseAddr("192.168.1.42"),
netip.MustParseAddr("192.168.2.42"),
netip.MustParseAddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
},
cidrs: []netip.Prefix{
netip.MustParsePrefix("192.168.1.0/24"),
},
},
want: []netip.Addr{
netip.MustParseAddr("192.168.1.42"),
},
},
{
name: "ipv4 addresses within multiple CIDRs",
args: args{
ips: []netip.Addr{
netip.MustParseAddr("192.168.1.42"),
netip.MustParseAddr("192.168.2.42"),
netip.MustParseAddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
},
cidrs: []netip.Prefix{
netip.MustParsePrefix("192.168.1.0/24"),
netip.MustParsePrefix("192.168.0.0/16"),
},
},
want: []netip.Addr{
netip.MustParseAddr("192.168.1.42"),
netip.MustParseAddr("192.168.2.42"),
},
},
{
name: "ipv6 addresses within one CIDR",
args: args{
ips: []netip.Addr{
netip.MustParseAddr("2600:1F13:0837:8500::1"),
netip.MustParseAddr("2600:1F13:0837:8504::1"),
netip.MustParseAddr("192.168.1.42"),
},
cidrs: []netip.Prefix{
netip.MustParsePrefix("2600:1f13:837:8500::/64"),
},
},
want: []netip.Addr{
netip.MustParseAddr("2600:1F13:0837:8500::1"),
},
},
{
name: "ipv6 addresses within multiple CIDRs",
args: args{
ips: []netip.Addr{
netip.MustParseAddr("2600:1F13:0837:8500::1"),
netip.MustParseAddr("2600:1F13:0837:8504::1"),
netip.MustParseAddr("192.168.1.42"),
},
cidrs: []netip.Prefix{
netip.MustParsePrefix("2600:1f13:837:8500::/64"),
netip.MustParsePrefix("2600:1f13:837:8500::/56"),
},
},
want: []netip.Addr{
netip.MustParseAddr("2600:1F13:0837:8500::1"),
netip.MustParseAddr("2600:1F13:0837:8504::1"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FilterIPsWithinCIDRs(tt.args.ips, tt.args.cidrs)
assert.Equal(t, tt.want, got)
})
}
}

func TestGetSubnetAssociatedIPv4CIDRs(t *testing.T) {
type args struct {
subnet *ec2sdk.Subnet
}
tests := []struct {
name string
args args
want []netip.Prefix
wantErr error
}{
{
name: "one IPv4 CIDR",
args: args{
subnet: &ec2sdk.Subnet{
CidrBlock: awssdk.String("192.168.1.0/24"),
},
},
want: []netip.Prefix{
netip.MustParsePrefix("192.168.1.0/24"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetSubnetAssociatedIPv4CIDRs(tt.args.subnet)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.Equal(t, tt.want, got)
}
})
}
}

func TestGetSubnetAssociatedIPv6CIDRs(t *testing.T) {
type args struct {
subnet *ec2sdk.Subnet
}
tests := []struct {
name string
args args
want []netip.Prefix
wantErr error
}{
{
name: "one IPv6 CIDR",
args: args{
subnet: &ec2sdk.Subnet{
CidrBlock: awssdk.String("192.168.1.0/24"),
Ipv6CidrBlockAssociationSet: []*ec2sdk.SubnetIpv6CidrBlockAssociation{
{
Ipv6CidrBlock: awssdk.String("2600:1f13:837:8500::/64"),
Ipv6CidrBlockState: &ec2sdk.SubnetCidrBlockState{
State: awssdk.String(ec2sdk.SubnetCidrBlockStateCodeAssociated),
},
},
},
},
},
want: []netip.Prefix{
netip.MustParsePrefix("2600:1f13:837:8500::/64"),
},
},
{
name: "multiple IPv6 CIDR",
args: args{
subnet: &ec2sdk.Subnet{
CidrBlock: awssdk.String("192.168.1.0/24"),
Ipv6CidrBlockAssociationSet: []*ec2sdk.SubnetIpv6CidrBlockAssociation{
{
Ipv6CidrBlock: awssdk.String("2600:1f13:837:8500::/64"),
Ipv6CidrBlockState: &ec2sdk.SubnetCidrBlockState{
State: awssdk.String(ec2sdk.SubnetCidrBlockStateCodeAssociated),
},
},
{
Ipv6CidrBlock: awssdk.String("2600:1f13:837:8504::/64"),
Ipv6CidrBlockState: &ec2sdk.SubnetCidrBlockState{
State: awssdk.String(ec2sdk.SubnetCidrBlockStateCodeAssociated),
},
},
},
},
},
want: []netip.Prefix{
netip.MustParsePrefix("2600:1f13:837:8500::/64"),
netip.MustParsePrefix("2600:1f13:837:8504::/64"),
},
},
{
name: "zero IPv6 CIDR",
args: args{
subnet: &ec2sdk.Subnet{
CidrBlock: awssdk.String("192.168.1.0/24"),
},
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetSubnetAssociatedIPv6CIDRs(tt.args.subnet)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.Equal(t, tt.want, got)
}
})
}
}
Loading