From 6b13665822b025e70a82068696443b65e1eb38e6 Mon Sep 17 00:00:00 2001 From: Barry Robison Date: Sat, 15 Feb 2025 16:25:24 +1100 Subject: [PATCH 1/2] feat(labels): add support for custom ranges for az and service labels --- Dockerfile | 3 +++ pkg/labeler/public_ranges.go | 30 +++++++++++++++++++++++++++- pkg/labeler/public_ranges_test.go | 27 ++++++++++++++++--------- pkg/labeler/remote.go | 33 +++++++++++++++++++++++++++++-- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8a79891..7dc5624 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,11 +15,14 @@ RUN [[ $(ls -1 vendor/ | wc -l) -gt 0 ]] || (echo "Expected 'vendor' dependencie COPY cmd/ cmd/ COPY pkg/ pkg/ +COPY custom_ranges.json /etc/kubenetmon/custom_ranges.json + # Build ARG VERSION=dev ARG GIT_COMMIT=unspecified ARG TARGETOS TARGETARCH RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o bin/ ./... +RUN CGO_ENABLED=1 GOOS=linux go test -v -race -coverprofile cover.out -tags '!integration' ./... # Use distroless as minimal base image to package the kubenetmon binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/pkg/labeler/public_ranges.go b/pkg/labeler/public_ranges.go index 962ffc9..1dc5f81 100644 --- a/pkg/labeler/public_ranges.go +++ b/pkg/labeler/public_ranges.go @@ -142,10 +142,23 @@ type AzurePrefixGroupProperties struct { NetworkFeatures []string `json:"networkFeatures"` } +/* Custom IP range structs */ +type CustomIPRanges struct { + SyncToken string `json:"syncToken"` + CreateDate string `json:"createDate"` + Prefixes []CustomPrefix `json:"prefixes"` +} +type CustomPrefix struct { + IPPrefixStr string `json:"ip_prefix"` + AvailabilityZone string `json:"az"` + Service string `json:"service"` +} + type remoteIPPrefixDetail struct { cloud Cloud service string region string + az string } func (d *remoteIPPrefixDetail) Normalize() { @@ -167,7 +180,7 @@ var awsServicePriorities map[string]int = map[string]int{ "ec2": 2, } -func refreshRemoteIPs(aws AWSIPRanges, gcp GCPIPRanges, google GoogleIPRanges, azure AzureIPRanges) (map[ipaddr.IPv4AddressKey]remoteIPPrefixDetail, *ipaddr.IPv4AddressTrie, error) { +func refreshRemoteIPs(aws AWSIPRanges, gcp GCPIPRanges, google GoogleIPRanges, azure AzureIPRanges, custom CustomIPRanges) (map[ipaddr.IPv4AddressKey]remoteIPPrefixDetail, *ipaddr.IPv4AddressTrie, error) { remoteIPRanges := make(map[ipaddr.IPv4AddressKey]remoteIPPrefixDetail) trie := ipaddr.NewIPv4AddressTrie() @@ -210,6 +223,21 @@ func refreshRemoteIPs(aws AWSIPRanges, gcp GCPIPRanges, google GoogleIPRanges, a } } + for _, prefix := range custom.Prefixes { + ip, err := ipaddr.NewIPAddressString(prefix.IPPrefixStr).ToAddress() + if err != nil { + return nil, nil, fmt.Errorf("invalid IPv4 address %s", prefix.IPPrefixStr) + } + trie.Add(ip.ToIPv4()) + d := remoteIPPrefixDetail{ + cloud: AWS, + service: prefix.Service, + az: prefix.AvailabilityZone, + } + d.Normalize() + remoteIPRanges[ip.ToIPv4().ToKey()] = d + } + // Parse the prefix, we only care about IPv4 right now. for _, prefix := range gcp.Prefixes { if prefix.IPv4PrefixStr == "" { diff --git a/pkg/labeler/public_ranges_test.go b/pkg/labeler/public_ranges_test.go index a5786cc..0d5a757 100644 --- a/pkg/labeler/public_ranges_test.go +++ b/pkg/labeler/public_ranges_test.go @@ -29,8 +29,9 @@ func TestRefreshRemoteIPs(t *testing.T) { gcp := GCPIPRanges{} google := GoogleIPRanges{} azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.NoError(t, err) assert.NotNil(t, remoteIPRanges) assert.NotNil(t, trie) @@ -62,8 +63,9 @@ func TestRefreshRemoteIPs(t *testing.T) { gcp := GCPIPRanges{} google := GoogleIPRanges{} azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.Error(t, err) assert.Nil(t, remoteIPRanges) assert.Nil(t, trie) @@ -78,8 +80,9 @@ func TestRefreshRemoteIPs(t *testing.T) { } google := GoogleIPRanges{} azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.NoError(t, err) assert.NotNil(t, remoteIPRanges) assert.NotNil(t, trie) @@ -103,8 +106,9 @@ func TestRefreshRemoteIPs(t *testing.T) { } google := GoogleIPRanges{} azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.Error(t, err) assert.Nil(t, remoteIPRanges) assert.Nil(t, trie) @@ -119,8 +123,9 @@ func TestRefreshRemoteIPs(t *testing.T) { }, } azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.NoError(t, err) assert.NotNil(t, remoteIPRanges) assert.NotNil(t, trie) @@ -144,8 +149,9 @@ func TestRefreshRemoteIPs(t *testing.T) { }, } azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.Error(t, err) assert.Nil(t, remoteIPRanges) assert.Nil(t, trie) @@ -195,8 +201,9 @@ func TestRefreshRemoteIPs(t *testing.T) { }, }, } + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.NoError(t, err) assert.NotNil(t, remoteIPRanges) assert.NotNil(t, trie) @@ -234,8 +241,9 @@ func TestRefreshRemoteIPs(t *testing.T) { }, }, } + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.Error(t, err) assert.Nil(t, remoteIPRanges) assert.Nil(t, trie) @@ -256,8 +264,9 @@ func TestRefreshRemoteIPs(t *testing.T) { gcp := GCPIPRanges{} google := GoogleIPRanges{} azure := AzureIPRanges{} + custom := CustomIPRanges{} - remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure) + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) assert.NoError(t, err) assert.NotNil(t, remoteIPRanges) assert.NotNil(t, trie) diff --git a/pkg/labeler/remote.go b/pkg/labeler/remote.go index 1962465..b44fe76 100644 --- a/pkg/labeler/remote.go +++ b/pkg/labeler/remote.go @@ -1,7 +1,9 @@ package labeler import ( + "encoding/json" "fmt" + "os" "sync" "time" @@ -56,7 +58,12 @@ func NewRemoteLabeler(localRegion string, localCloud Cloud, environment Environm errorCounter.WithLabelValues([]string{"get_cloud_ranges"}...).Inc() return nil, fmt.Errorf("error fetching cloud ranges: %v", err) } - remoteIPPrefixes, remoteIPPrefixesTrie, err := refreshRemoteIPs(aws, gcp, google, azure) + custom, err := getCustomRanges() + if err != nil { + errorCounter.WithLabelValues([]string{"get_custom_ranges"}...).Inc() + return nil, fmt.Errorf("error fetching custom ranges: %v", err) + } + remoteIPPrefixes, remoteIPPrefixesTrie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) if err != nil { errorCounter.WithLabelValues([]string{"remote_ips_refresh"}...).Inc() publicIPRefreshCounter.WithLabelValues([]string{"failed"}...).Inc() @@ -106,7 +113,14 @@ func NewRemoteLabeler(localRegion string, localCloud Cloud, environment Environm log.Error().Err(err).Msg("failed to refresh cloud provider IPs") continue } - remoteIPPrefixes, remoteIPPrefixesTrie, err := refreshRemoteIPs(aws, gcp, google, azure) + custom, err := getCustomRanges() + if err != nil { + errorCounter.WithLabelValues([]string{"get_custom_ranges"}...).Inc() + publicIPRefreshCounter.WithLabelValues([]string{"failed"}...).Inc() + log.Error().Err(err).Msg("failed to refresh custom provider IPs") + continue + } + remoteIPPrefixes, remoteIPPrefixesTrie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) if err != nil { errorCounter.WithLabelValues([]string{"remote_ips_refresh"}...).Inc() publicIPRefreshCounter.WithLabelValues([]string{"failed"}...).Inc() @@ -144,6 +158,7 @@ func (l *RemoteLabeler) labelRemote(remoteEndpoint *endpointInfo, FlowData *Flow FlowData.RemoteRegion = remoteDetail.region FlowData.RemoteCloudService = remoteDetail.service FlowData.RemoteCloud = remoteDetail.cloud + FlowData.RemoteAvailabilityZone = remoteDetail.az if remoteDetail.cloud == l.cloud { if remoteDetail.region == "" { @@ -208,3 +223,17 @@ func getCloudRanges() (awsIPRanges AWSIPRanges, gcpIPRanges GCPIPRanges, googleI return } + +func getCustomRanges() (customIPRanges CustomIPRanges, err error) { + body, err := os.ReadFile("/etc/kubenetmon/custom_ranges.json") + if err != nil { + return customIPRanges, err + } + + err = json.Unmarshal(body, &customIPRanges) + if err != nil { + return customIPRanges, err + } + + return customIPRanges, err +} From 234d0f1c14aa655e11e138fd3af482811afd92e6 Mon Sep 17 00:00:00 2001 From: Barry Robison Date: Sat, 15 Feb 2025 16:32:43 +1100 Subject: [PATCH 2/2] feat(labels): add unit test for custom ranges --- Dockerfile | 2 +- pkg/labeler/public_ranges_test.go | 25 +++++++++++++++++++++++++ test/custom_ranges.json | 12 ++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/custom_ranges.json diff --git a/Dockerfile b/Dockerfile index 7dc5624..ecd5461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN [[ $(ls -1 vendor/ | wc -l) -gt 0 ]] || (echo "Expected 'vendor' dependencie COPY cmd/ cmd/ COPY pkg/ pkg/ -COPY custom_ranges.json /etc/kubenetmon/custom_ranges.json +COPY test/custom_ranges.json /etc/kubenetmon/custom_ranges.json # Build ARG VERSION=dev diff --git a/pkg/labeler/public_ranges_test.go b/pkg/labeler/public_ranges_test.go index 0d5a757..bf44faf 100644 --- a/pkg/labeler/public_ranges_test.go +++ b/pkg/labeler/public_ranges_test.go @@ -279,6 +279,31 @@ func TestRefreshRemoteIPs(t *testing.T) { assert.Equal(t, "service1", detail.service) assert.Equal(t, "us-east-1", detail.region) }) + + t.Run("Test Custom az", func(t *testing.T) { + custom := CustomIPRanges{ + Prefixes: []CustomPrefix{ + {IPPrefixStr: "10.1.1.16/32", Service: "redis", AvailabilityZone: "us-west-2a"}, + }, + } + aws := AWSIPRanges{} + gcp := GCPIPRanges{} + google := GoogleIPRanges{} + azure := AzureIPRanges{} + + remoteIPRanges, trie, err := refreshRemoteIPs(aws, gcp, google, azure, custom) + assert.NoError(t, err) + assert.NotNil(t, remoteIPRanges) + assert.NotNil(t, trie) + + assert.Equal(t, 1, len(remoteIPRanges)) + ip, err := ipaddr.NewIPAddressString("10.1.1.16/32").ToAddress() + assert.NoError(t, err) + assert.NotNil(t, ip) + detail := remoteIPRanges[ip.ToIPv4().ToKey()] + assert.Equal(t, "redis", detail.service) + assert.Equal(t, "us-west-2a", detail.az) + }) } func TestNormalizeCloudString(t *testing.T) { diff --git a/test/custom_ranges.json b/test/custom_ranges.json new file mode 100644 index 0000000..3e307a0 --- /dev/null +++ b/test/custom_ranges.json @@ -0,0 +1,12 @@ +{ + "syncToken": "1739569991", + "createDate": "2025-02-14-21-53-11", + "prefixes": [ + { + "ip_prefix": "10.16.1.16/32", + "az": "ap-southeast-2b", + "service": "redis" + } + ] +} + \ No newline at end of file