diff --git a/README.md b/README.md index 69380bd8..957de726 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..46230bbf 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -3,6 +3,7 @@ package linode import ( "fmt" "io" + "net" "os" "strconv" "sync" @@ -37,6 +38,7 @@ var Options struct { VPCName string LoadBalancerType string BGPNodeSelector string + 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 39932c31..67b8b976 100644 --- a/cloud/linode/common.go +++ b/cloud/linode/common.go @@ -2,6 +2,7 @@ package linode import ( "fmt" + "net" "strconv" "strings" @@ -42,3 +43,11 @@ func IgnoreLinodeAPIError(err error, code int) error { return err } + +func isPrivate(ip *net.IP) bool { + if Options.LinodeExternalNetwork == nil { + return ip.IsPrivate() + } + + return ip.IsPrivate() && !Options.LinodeExternalNetwork.Contains(*ip) +} 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/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/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..f8b3c1ee 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "net" "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 := 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 + } + pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) pflag.CommandLine.AddGoFlagSet(flag.CommandLine)