Skip to content

Commit

Permalink
Merge remote-tracking branch 'r/main' into remove-klog-v1
Browse files Browse the repository at this point in the history
  • Loading branch information
luthermonson committed Feb 3, 2024
2 parents 93fb1cf + 49549b4 commit 17b86c1
Show file tree
Hide file tree
Showing 24 changed files with 2,646 additions and 482 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
IMG ?= linode/linode-cloud-controller-manager:canary
RELEASE_DIR ?= release
GOLANGCI_LINT_IMG := golangci/golangci-lint:v1.55-alpine
PLATFORM ?= linux/amd64

export GO111MODULE=on

Expand Down Expand Up @@ -66,7 +67,7 @@ imgname:
.PHONY: docker-build
# we cross compile the binary for linux, then build a container
docker-build: build-linux
docker build . -t ${IMG}
DOCKER_BUILDKIT=1 docker build --platform=$(PLATFORM) --tag ${IMG} .

.PHONY: docker-push
# must run the docker build before pushing the image
Expand Down
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ Annotation (Suffix) | Values | Default | Description
`nodebalancer-id` | string | | The ID of the NodeBalancer to front the service. When not specified, a new NodeBalancer will be created. This can be configured on service creation or patching
`hostname-only-ingress` | [bool](#annotation-bool-values) | `false` | When `true`, the LoadBalancerStatus for the service will only contain the Hostname. This is useful for bypassing kube-proxy's rerouting of in-cluster requests originally intended for the external LoadBalancer to the service's constituent pod IPs.
`tags` | string | | A comma seperated list of tags to be applied to the createad NodeBalancer instance
`firewall-id` | string | | The Firewall ID that's applied to the NodeBalancer instance.
`firewall-id` | string | | An existing Cloud Firewall ID to be attached to the NodeBalancer instance. See [Firewalls](#firewalls).
`firewall-acl` | string | | The Firewall rules to be applied to the NodeBalancer. Adding this annotation creates a new CCM managed Linode CloudFirewall instance. See [Firewalls](#firewalls).

#### Deprecated Annotations
These annotations are deprecated, and will be removed in a future release.
Expand All @@ -77,6 +78,56 @@ Key | Values | Default | Description
`proxy-protocol` | `none`, `v1`, `v2` | `none` | Specifies whether to use a version of Proxy Protocol on the underlying NodeBalancer. Overwrites `default-proxy-protocol`.
`tls-secret-name` | string | | Specifies a secret to use for TLS. The secret type should be `kubernetes.io/tls`.

#### Firewalls
Firewall rules can be applied to the CCM Managed NodeBalancers in two distinct ways.

##### CCM Managed Firewall
To use this feature, ensure that the linode api token used with the ccm has the `add_firewalls` grant.

The CCM accepts firewall ACLs in json form. The ACL can either be an `allowList` or a `denyList`. Supplying both is not supported. Supplying neither is not supported. The `allowList` sets up a CloudFirewall that `ACCEPT`s traffic only from the specified IPs/CIDRs and `DROP`s everything else. The `denyList` sets up a CloudFirewall that `DROP`s traffic only from the specified IPs/CIDRs and `ACCEPT`s everything else. Ports are automatically inferred from the service configuration.

See [Firewall rules](https://www.linode.com/docs/api/networking/#firewall-create__request-body-schema) for more details on how to specify the IPs/CIDRs

Example usage of an ACL to allow traffic from a specific set of addresses

```yaml
kind: Service
apiVersion: v1
metadata:
name: https-lb
annotations:
service.beta.kubernetes.io/linode-loadbalancer-firewall-acl: |
{
"allowList": {
"ipv4": ["192.166.0.0/16", "172.23.41.0/24"],
"ipv6": ["2001:DB8::/128"]
},
}
spec:
type: LoadBalancer
selector:
app: nginx-https-example
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
- name: https
protocol: TCP
port: 443
targetPort: https
```
##### User Managed Firewall
Users can create CloudFirewall instances, supply their own rules and attach them to the NodeBalancer. To do so, set the
`service.beta.kubernetes.io/linode-loadbalancer-firewall-id` annotation to the ID of the cloud firewall. The CCM does not manage the lifecycle of the CloudFirewall Instance in this case. Users are responsible for ensuring the policies are correct.

**Note**<br/>
If the user supplies a firewall-id, and later switches to using an ACL, the CCM will take over the CloudFirewall Instance. To avoid this, delete the service, and re-create it so the original CloudFirewall is left undisturbed.



### Nodes
Kubernetes Nodes can be configured with the following annotations.

Expand Down
34 changes: 34 additions & 0 deletions cloud/linode/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package linode

const (
// annLinodeDefaultProtocol is the annotation used to specify the default protocol
// for Linode load balancers. Options are tcp, http and https. Defaults to tcp.
annLinodeDefaultProtocol = "service.beta.kubernetes.io/linode-loadbalancer-default-protocol"
annLinodePortConfigPrefix = "service.beta.kubernetes.io/linode-loadbalancer-port-"
annLinodeDefaultProxyProtocol = "service.beta.kubernetes.io/linode-loadbalancer-default-proxy-protocol"

annLinodeCheckPath = "service.beta.kubernetes.io/linode-loadbalancer-check-path"
annLinodeCheckBody = "service.beta.kubernetes.io/linode-loadbalancer-check-body"
annLinodeHealthCheckType = "service.beta.kubernetes.io/linode-loadbalancer-check-type"

annLinodeHealthCheckInterval = "service.beta.kubernetes.io/linode-loadbalancer-check-interval"
annLinodeHealthCheckTimeout = "service.beta.kubernetes.io/linode-loadbalancer-check-timeout"
annLinodeHealthCheckAttempts = "service.beta.kubernetes.io/linode-loadbalancer-check-attempts"
annLinodeHealthCheckPassive = "service.beta.kubernetes.io/linode-loadbalancer-check-passive"

// annLinodeThrottle is the annotation specifying the value of the Client Connection
// Throttle, which limits the number of subsequent new connections per second from the
// same client IP. Options are a number between 1-20, or 0 to disable. Defaults to 20.
annLinodeThrottle = "service.beta.kubernetes.io/linode-loadbalancer-throttle"

annLinodeLoadBalancerPreserve = "service.beta.kubernetes.io/linode-loadbalancer-preserve"
annLinodeNodeBalancerID = "service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-id"

annLinodeHostnameOnlyIngress = "service.beta.kubernetes.io/linode-loadbalancer-hostname-only-ingress"
annLinodeLoadBalancerTags = "service.beta.kubernetes.io/linode-loadbalancer-tags"
annLinodeCloudFirewallID = "service.beta.kubernetes.io/linode-loadbalancer-firewall-id"
annLinodeCloudFirewallACL = "service.beta.kubernetes.io/linode-loadbalancer-firewall-acl"

annLinodeNodePrivateIP = "node.k8s.linode.com/private-ip"
annLinodeHostUUID = "node.k8s.linode.com/host-uuid"
)
41 changes: 41 additions & 0 deletions cloud/linode/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package linode

import (
"context"
"net/url"
"regexp"
"strings"

"github.com/linode/linodego"
)
Expand All @@ -23,7 +26,45 @@ type Client interface {
DeleteNodeBalancerConfig(context.Context, int, int) error
ListNodeBalancerConfigs(context.Context, int, *linodego.ListOptions) ([]linodego.NodeBalancerConfig, error)
RebuildNodeBalancerConfig(context.Context, int, int, linodego.NodeBalancerConfigRebuildOptions) (*linodego.NodeBalancerConfig, error)
ListNodeBalancerFirewalls(ctx context.Context, nodebalancerID int, opts *linodego.ListOptions) ([]linodego.Firewall, error)
ListFirewallDevices(ctx context.Context, firewallID int, opts *linodego.ListOptions) ([]linodego.FirewallDevice, error)
DeleteFirewallDevice(ctx context.Context, firewallID, deviceID int) error
CreateFirewallDevice(ctx context.Context, firewallID int, opts linodego.FirewallDeviceCreateOptions) (*linodego.FirewallDevice, error)
CreateFirewall(ctx context.Context, opts linodego.FirewallCreateOptions) (*linodego.Firewall, error)
DeleteFirewall(ctx context.Context, fwid int) error
GetFirewall(context.Context, int) (*linodego.Firewall, error)
UpdateFirewallRules(context.Context, int, linodego.FirewallRuleSet) (*linodego.FirewallRuleSet, error)
}

// linodego.Client implements Client
var _ Client = (*linodego.Client)(nil)

func newLinodeClient(token, ua, apiURL string) (*linodego.Client, error) {
linodeClient := linodego.NewClient(nil)
linodeClient.SetUserAgent(ua)
linodeClient.SetToken(token)

// Validate apiURL
parsedURL, err := url.Parse(apiURL)
if err != nil {
return nil, err
}

validatedURL := &url.URL{
Host: parsedURL.Host,
Scheme: parsedURL.Scheme,
}

linodeClient.SetBaseURL(validatedURL.String())

version := ""
matches := regexp.MustCompile(`/v\d+`).FindAllString(parsedURL.Path, -1)

if len(matches) > 0 {
version = strings.Trim(matches[len(matches)-1], "/")
}

linodeClient.SetAPIVersion(version)

return &linodeClient, nil
}
22 changes: 16 additions & 6 deletions cloud/linode/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
ProviderName = "linode"
accessTokenEnv = "LINODE_API_TOKEN"
regionEnv = "LINODE_REGION"
urlEnv = "LINODE_URL"
)

// Options is a configuration object for this cloudprovider implementation.
Expand Down Expand Up @@ -52,28 +53,37 @@ func newCloud() (cloudprovider.Interface, error) {
return nil, fmt.Errorf("%s must be set in the environment (use a k8s secret)", regionEnv)
}

linodeClient := linodego.NewClient(nil)
linodeClient.SetToken(apiToken)
url := os.Getenv(urlEnv)
ua := fmt.Sprintf("linode-cloud-controller-manager %s", linodego.DefaultUserAgent)

linodeClient, err := newLinodeClient(apiToken, ua, url)
if err != nil {
return nil, fmt.Errorf("client was not created succesfully: %w", err)
}

if Options.LinodeGoDebug {
linodeClient.SetDebug(true)
}
linodeClient.SetUserAgent(fmt.Sprintf("linode-cloud-controller-manager %s", linodego.DefaultUserAgent))

// Return struct that satisfies cloudprovider.Interface
return &linodeCloud{
client: &linodeClient,
instances: newInstances(&linodeClient),
loadbalancers: newLoadbalancers(&linodeClient, region),
client: linodeClient,
instances: newInstances(linodeClient),
loadbalancers: newLoadbalancers(linodeClient, region),
}, nil
}

func (c *linodeCloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stopCh <-chan struct{}) {
kubeclient := clientBuilder.ClientOrDie("linode-shared-informers")
sharedInformer := informers.NewSharedInformerFactory(kubeclient, 0)
serviceInformer := sharedInformer.Core().V1().Services()
nodeInformer := sharedInformer.Core().V1().Nodes()

serviceController := newServiceController(c.loadbalancers.(*loadbalancers), serviceInformer)
go serviceController.Run(stopCh)

nodeController := newNodeController(kubeclient, c.client, nodeInformer)
go nodeController.Run(stopCh)
}

func (c *linodeCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
Expand Down
6 changes: 5 additions & 1 deletion cloud/linode/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ func (e invalidProviderIDError) Error() string {
return fmt.Sprintf("invalid provider ID %q", e.value)
}

func isLinodeProviderID(providerID string) bool {
return strings.HasPrefix(providerID, providerIDPrefix)
}

func parseProviderID(providerID string) (int, error) {
if !strings.HasPrefix(providerID, providerIDPrefix) {
if !isLinodeProviderID(providerID) {
return 0, invalidProviderIDError{providerID}
}
id, err := strconv.Atoi(strings.TrimPrefix(providerID, providerIDPrefix))
Expand Down
Loading

0 comments on commit 17b86c1

Please sign in to comment.