From 4c94969634050a04da0a337657abdf3f65b3e6ab Mon Sep 17 00:00:00 2001 From: Michal Kovarik Date: Wed, 25 Sep 2024 11:35:45 +0200 Subject: [PATCH 1/3] Allow mark internal network as external With new enviroment variable `LINODE_EXTERNAL_SUBNET` is possible to set internal network subnet to be used as external network. Useful for running on testing Linode cloud instances which are providing external IP addresses in internal network. --- README.md | 1 + cloud/linode/cloud.go | 2 ++ cloud/linode/common.go | 13 +++++++++++++ cloud/linode/instances.go | 4 ++-- cloud/linode/node_controller.go | 2 +- main.go | 19 ++++++++++++++++--- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 69380bd8..bf102bbc 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,7 @@ Environment Variable | Default | Description `LINODE_INSTANCE_CACHE_TTL` | `15` | Default timeout of instance cache in seconds `LINODE_ROUTES_CACHE_TTL_SECONDS` | `60` | Default timeout of route cache in seconds `LINODE_REQUEST_TIMEOUT_SECONDS` | `120` | Default timeout in seconds for http requests to linode API +`LINODE_EXTERNAL_SUBNET` | `` | Mark private network as external. Example - `172.24.0.0/16` ## Generating a Manifest for Deployment Use the script located at `./deploy/generate-manifest.sh` to generate a self-contained deployment manifest for the Linode CCM. Two arguments are required. diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index f1138988..99f88e9a 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -3,6 +3,7 @@ package linode import ( "fmt" "io" + "net/netip" "os" "strconv" "sync" @@ -37,6 +38,7 @@ var Options struct { VPCName string LoadBalancerType string BGPNodeSelector string + LinodeExternalNetwork *netip.Prefix } // vpcDetails is set when VPCName options flag is set. diff --git a/cloud/linode/common.go b/cloud/linode/common.go index 39932c31..79f6ed38 100644 --- a/cloud/linode/common.go +++ b/cloud/linode/common.go @@ -2,6 +2,8 @@ package linode import ( "fmt" + "net" + "net/netip" "strconv" "strings" @@ -42,3 +44,14 @@ func IgnoreLinodeAPIError(err error, code int) error { return err } + +func isPrivate(ip *net.IP) bool { + if Options.LinodeExternalNetwork == nil { + return ip.IsPrivate() + } + ipAddr, err := netip.ParseAddr(ip.String()) + if err != nil { + panic(err) + } + return ip.IsPrivate() && !Options.LinodeExternalNetwork.Contains(ipAddr) +} diff --git a/cloud/linode/instances.go b/cloud/linode/instances.go index 21711745..0a78e5d6 100644 --- a/cloud/linode/instances.go +++ b/cloud/linode/instances.go @@ -49,7 +49,7 @@ func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []s for _, ip := range instance.IPv4 { ipType := v1.NodeExternalIP - if ip.IsPrivate() { + if isPrivate(ip) { ipType = v1.NodeInternalIP } ips = append(ips, nodeIP{ip: ip.String(), ipType: ipType}) @@ -155,7 +155,7 @@ func (i *instances) linodeByIP(kNode *v1.Node) (*linodego.Instance, error) { } for _, node := range i.nodeCache.nodes { for _, nodeIP := range node.instance.IPv4 { - if !nodeIP.IsPrivate() && slices.Contains(kNodeAddresses, nodeIP.String()) { + if !isPrivate(nodeIP) && slices.Contains(kNodeAddresses, nodeIP.String()) { return node.instance, nil } } diff --git a/cloud/linode/node_controller.go b/cloud/linode/node_controller.go index 3c980bb7..fe502a6a 100644 --- a/cloud/linode/node_controller.go +++ b/cloud/linode/node_controller.go @@ -172,7 +172,7 @@ func (s *nodeController) handleNode(ctx context.Context, node *v1.Node) error { // supports other subnets with nodebalancer, this logic needs to be updated. // https://www.linode.com/docs/api/linode-instances/#linode-view for _, addr := range linode.IPv4 { - if addr.IsPrivate() { + if isPrivate(addr) { expectedPrivateIP = addr.String() break } diff --git a/main.go b/main.go index 2e99b10a..116dcaa5 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "net/netip" "os" "k8s.io/component-base/logs" @@ -25,9 +26,10 @@ import ( ) const ( - sentryDSNVariable = "SENTRY_DSN" - sentryEnvironmentVariable = "SENTRY_ENVIRONMENT" - sentryReleaseVariable = "SENTRY_RELEASE" + sentryDSNVariable = "SENTRY_DSN" + sentryEnvironmentVariable = "SENTRY_ENVIRONMENT" + sentryReleaseVariable = "SENTRY_RELEASE" + linodeExternalSubnetVariable = "LINODE_EXTERNAL_SUBNET" ) func initializeSentry() { @@ -114,6 +116,17 @@ func main() { os.Exit(1) } + if externalSubnet, ok := os.LookupEnv(linodeExternalSubnetVariable); ok && externalSubnet != "" { + network, err := netip.ParsePrefix(externalSubnet) + if err != nil { + msg := fmt.Sprintf("Unable to parse %s as network subnet: %v", externalSubnet, err) + sentry.CaptureError(ctx, fmt.Errorf(msg)) + fmt.Fprintf(os.Stderr, "%v\n", msg) + os.Exit(1) + } + linode.Options.LinodeExternalNetwork = &network + } + pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) pflag.CommandLine.AddGoFlagSet(flag.CommandLine) From 0d0f7e5f96be4098dfa407cafa17e6679ba77103 Mon Sep 17 00:00:00 2001 From: Michal Kovarik Date: Wed, 25 Sep 2024 12:09:15 +0200 Subject: [PATCH 2/3] use net.IPNet and add test --- cloud/linode/cloud.go | 4 ++-- cloud/linode/common.go | 8 ++------ cloud/linode/instances_test.go | 23 +++++++++++++++++++++-- main.go | 6 +++--- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index 99f88e9a..46230bbf 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -3,7 +3,7 @@ package linode import ( "fmt" "io" - "net/netip" + "net" "os" "strconv" "sync" @@ -38,7 +38,7 @@ var Options struct { VPCName string LoadBalancerType string BGPNodeSelector string - LinodeExternalNetwork *netip.Prefix + LinodeExternalNetwork *net.IPNet } // vpcDetails is set when VPCName options flag is set. diff --git a/cloud/linode/common.go b/cloud/linode/common.go index 79f6ed38..67b8b976 100644 --- a/cloud/linode/common.go +++ b/cloud/linode/common.go @@ -3,7 +3,6 @@ package linode import ( "fmt" "net" - "net/netip" "strconv" "strings" @@ -49,9 +48,6 @@ func isPrivate(ip *net.IP) bool { if Options.LinodeExternalNetwork == nil { return ip.IsPrivate() } - ipAddr, err := netip.ParseAddr(ip.String()) - if err != nil { - panic(err) - } - return ip.IsPrivate() && !Options.LinodeExternalNetwork.Contains(ipAddr) + + return ip.IsPrivate() && !Options.LinodeExternalNetwork.Contains(*ip) } diff --git a/cloud/linode/instances_test.go b/cloud/linode/instances_test.go index 57d5eec5..775f288e 100644 --- a/cloud/linode/instances_test.go +++ b/cloud/linode/instances_test.go @@ -146,14 +146,16 @@ func TestMetadataRetrieval(t *testing.T) { name string inputIPv4s []string inputIPv6 string + externalNetwork string outputAddresses []v1.NodeAddress expectedErr error }{ - {"no IPs", nil, "", nil, instanceNoIPAddressesError{192910}}, + {"no IPs", nil, "", "", nil, instanceNoIPAddressesError{192910}}, { "one public, one private", []string{"32.74.121.25", "192.168.121.42"}, "", + "", []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "32.74.121.25"}, {Type: v1.NodeInternalIP, Address: "192.168.121.42"}}, nil, }, @@ -161,6 +163,7 @@ func TestMetadataRetrieval(t *testing.T) { "one public ipv4, one public ipv6", []string{"32.74.121.25"}, "2600:3c06::f03c:94ff:fe1e:e072", + "", []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "32.74.121.25"}, {Type: v1.NodeExternalIP, Address: "2600:3c06::f03c:94ff:fe1e:e072"}}, nil, }, @@ -168,6 +171,7 @@ func TestMetadataRetrieval(t *testing.T) { "one public, no private", []string{"32.74.121.25"}, "", + "", []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "32.74.121.25"}}, nil, }, @@ -175,6 +179,7 @@ func TestMetadataRetrieval(t *testing.T) { "one private, no public", []string{"192.168.121.42"}, "", + "", []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "192.168.121.42"}}, nil, }, @@ -182,6 +187,7 @@ func TestMetadataRetrieval(t *testing.T) { "two public addresses", []string{"32.74.121.25", "32.74.121.22"}, "", + "", []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "32.74.121.25"}, {Type: v1.NodeExternalIP, Address: "32.74.121.22"}}, nil, }, @@ -189,9 +195,18 @@ func TestMetadataRetrieval(t *testing.T) { "two private addresses", []string{"192.168.121.42", "10.0.2.15"}, "", + "", []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "192.168.121.42"}, {Type: v1.NodeInternalIP, Address: "10.0.2.15"}}, nil, }, + { + "two private addresses - one in network marked as external", + []string{"192.168.121.42", "10.0.2.15"}, + "", + "10.0.2.0/16", + []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "192.168.121.42"}, {Type: v1.NodeExternalIP, Address: "10.0.2.15"}}, + nil, + }, } for _, test := range ipTests { @@ -201,7 +216,11 @@ func TestMetadataRetrieval(t *testing.T) { name := "my-instance" providerID := providerIDPrefix + strconv.Itoa(id) node := nodeWithProviderID(providerID) - + if test.externalNetwork == "" { + Options.LinodeExternalNetwork = nil + } else { + _, Options.LinodeExternalNetwork, _ = net.ParseCIDR(test.externalNetwork) + } ips := make([]*net.IP, 0, len(test.inputIPv4s)) for _, ip := range test.inputIPv4s { parsed := net.ParseIP(ip) diff --git a/main.go b/main.go index 116dcaa5..f8b3c1ee 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "context" "flag" "fmt" - "net/netip" + "net" "os" "k8s.io/component-base/logs" @@ -117,14 +117,14 @@ func main() { } if externalSubnet, ok := os.LookupEnv(linodeExternalSubnetVariable); ok && externalSubnet != "" { - network, err := netip.ParsePrefix(externalSubnet) + _, network, err := net.ParseCIDR(externalSubnet) if err != nil { msg := fmt.Sprintf("Unable to parse %s as network subnet: %v", externalSubnet, err) sentry.CaptureError(ctx, fmt.Errorf(msg)) fmt.Fprintf(os.Stderr, "%v\n", msg) os.Exit(1) } - linode.Options.LinodeExternalNetwork = &network + linode.Options.LinodeExternalNetwork = network } pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) From fab127df342c0889f44f61b51590400cb136094d Mon Sep 17 00:00:00 2001 From: Michal Kovarik Date: Thu, 26 Sep 2024 14:36:21 +0200 Subject: [PATCH 3/3] fix empty value in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf102bbc..957de726 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ Environment Variable | Default | Description `LINODE_INSTANCE_CACHE_TTL` | `15` | Default timeout of instance cache in seconds `LINODE_ROUTES_CACHE_TTL_SECONDS` | `60` | Default timeout of route cache in seconds `LINODE_REQUEST_TIMEOUT_SECONDS` | `120` | Default timeout in seconds for http requests to linode API -`LINODE_EXTERNAL_SUBNET` | `` | Mark private network as external. Example - `172.24.0.0/16` +`LINODE_EXTERNAL_SUBNET` | | Mark private network as external. Example - `172.24.0.0/16` ## Generating a Manifest for Deployment Use the script located at `./deploy/generate-manifest.sh` to generate a self-contained deployment manifest for the Linode CCM. Two arguments are required.