Skip to content

Commit 3188c64

Browse files
authored
Merge pull request #22 from appscode-cloud/lka2
Add test for LoadBalancer Service with a single TLS port
2 parents 70b2faa + 4db53a3 commit 3188c64

File tree

15 files changed

+799
-242
lines changed

15 files changed

+799
-242
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010
- GO111MODULE=on
1111

1212
go:
13-
- 1.11.x
13+
- 1.12.x
1414
- tip
1515

1616
cache:

Makefile

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,31 @@ IMG ?= linode/linode-cloud-controller-manager:latest
22

33
export GO111MODULE=on
44

5-
all: build
5+
$(GOPATH)/bin/goimports:
6+
GO111MODULE=off go get golang.org/x/tools/cmd/goimports
7+
8+
$(GOPATH)/bin/ginkgo:
9+
GO111MODULE=off go get -u github.com/onsi/ginkgo/ginkgo
610

7-
build: fmt
11+
vet:
12+
go vet -composites=false ./...
13+
14+
fmt: vet $(GOPATH)/bin/goimports
15+
# goimports runs a gofmt
16+
goimports -w *.go cloud
17+
18+
build: test fmt
819
go build -o dist/linode-cloud-controller-manager github.com/linode/linode-cloud-controller-manager
920

21+
test: $(GOPATH)/bin/ginkgo
22+
ginkgo -r --v --progress --trace --cover --skipPackage=test -- --v=3
23+
24+
docker-build: build
25+
docker build . -t ${IMG}
26+
27+
docker-push:
28+
docker push ${IMG}
29+
1030
run: build
1131
dist/linode-cloud-controller-manager \
1232
--logtostderr=true \
@@ -22,26 +42,5 @@ run-debug: build
2242
--kubeconfig=${KUBECONFIG} \
2343
--linodego-debug
2444

25-
$(GOPATH)/bin/goimports:
26-
GO111MODULE=off go get golang.org/x/tools/cmd/goimports
27-
28-
vet:
29-
go vet -composites=false ./...
30-
31-
imports: $(GOPATH)/bin/goimports
32-
goimports -w *.go cloud
33-
34-
fmt: vet imports
35-
gofmt -s -w *.go cloud
36-
37-
$(GOPATH)/bin/ginkgo:
38-
GO111MODULE=off go get -u github.com/onsi/ginkgo/ginkgo
39-
40-
test: $(GOPATH)/bin/ginkgo
41-
ginkgo -r --v --progress --trace --cover --skipPackage=test -- --v=3
42-
43-
docker-build:
44-
docker build . -t ${IMG}
45+
all: build
4546

46-
docker-push:
47-
docker push ${IMG}

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ Annotation (Suffix) | Values | Default | Description
4646
`protocol` | `tcp`, `http`, `https` | `tcp` | This annotation is used to specify the default protocol for Linode NodeBalancer. For ports specified in the `linode-loadbalancer-tls-ports` annotation, this protocol is overwritten to `https`
4747
`stickiness` | `none`, `table`, `http_cookie` | `none` | Controls how session stickiness is handled on this port.
4848
`algorithm` | `round_robin`, `least_connections` | `round_robin` | Specifies which algorithm the Linode NodeBalancer should use
49-
`tls-ports` | int (e.g. `443,6443,7443`) | | This annotation specifies the ports the NodeBalancer should use for `https`
50-
`ssl-cert` | string | | The Base64 Encoded SSL certificates for this service. The full certificate chain may be provided. (`base64 -w0 ssl.crt`)
51-
`ssl-key` | string | | The Base64 Encoded private key corresponding to this port's certificate. The key can not be passphrase protected. (`base64 -w0 ssl.key`)
49+
`tls` | json array (e.g. `[ { "tls-secret-name": "prod-app-tls", "port": 443}, {"tls-secret-name": "dev-app-tls", "port": 8443} ]`) | | Specifies TLS ports with their corresponding secrets, the secret type should be `kubernetes.io/tls`
5250
`check-type` | `none`, `connection`, `http`, `http_body` | | The type of health check to perform against back-ends to ensure they are serving requests
5351
`check-path` | string | | The URL path to check on each back-end during health checks
5452
`check-body` | string | | Text which must be present in the response body to pass the NodeBalancer health check

cloud/linode/loadbalancers.go

Lines changed: 87 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package linode
22

33
import (
44
"context"
5-
"encoding/base64"
65
"encoding/json"
76
"fmt"
87
"strconv"
98
"strings"
109

10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/client-go/kubernetes"
12+
"k8s.io/client-go/rest"
13+
1114
"github.com/linode/linodego"
1215
"github.com/pkg/errors"
1316
v1 "k8s.io/api/core/v1"
@@ -20,25 +23,15 @@ const (
2023
// is overwritten to https. Options are tcp, http and https. Defaults to tcp.
2124
annLinodeProtocol = "service.beta.kubernetes.io/linode-loadbalancer-protocol"
2225

23-
// annLinodeTLSPorts is the annotation used to specify which ports of the loadbalancer
24-
// should use the https protocol. This is a comma separated list of ports
25-
// (e.g. 443,6443,7443).
26-
annLinodeTLSPorts = "service.beta.kubernetes.io/linode-loadbalancer-tls-ports"
27-
2826
annLinodeCheckPath = "service.beta.kubernetes.io/linode-loadbalancer-check-path"
2927
annLinodeCheckBody = "service.beta.kubernetes.io/linode-loadbalancer-check-body"
3028
annLinodeHealthCheckType = "service.beta.kubernetes.io/linode-loadbalancer-check-type"
3129

32-
// annLinodeCertificateID is the annotation specifying the certificate ID
33-
// used for https protocol. This annotation is required if annLinodeTLSPorts
34-
// is passed.
35-
annLinodeSSLCertificate = "service.beta.kubernetes.io/linode-loadbalancer-ssl-cert"
36-
annLinodeSSLKey = "service.beta.kubernetes.io/linode-loadbalancer-ssl-key"
37-
3830
annLinodeHealthCheckInterval = "service.beta.kubernetes.io/linode-loadbalancer-check-interval"
3931
annLinodeHealthCheckTimeout = "service.beta.kubernetes.io/linode-loadbalancer-check-timeout"
4032
annLinodeHealthCheckAttempts = "service.beta.kubernetes.io/linode-loadbalancer-check-attempts"
4133
annLinodeHealthCheckPassive = "service.beta.kubernetes.io/linode-loadbalancer-check-passive"
34+
annLinodeLoadBalancerTLS = "service.beta.kubernetes.io/linode-loadbalancer-tls"
4235

4336
annLinodeSessionPersistence = "service.beta.kubernetes.io/linode-loadbalancer-stickiness"
4437

@@ -58,6 +51,13 @@ var lbNotFound = errors.New("loadbalancer not found")
5851
type loadbalancers struct {
5952
client *linodego.Client
6053
zone string
54+
55+
kubeClient kubernetes.Interface
56+
}
57+
58+
type tlsAnnotation struct {
59+
TlsSecretName string `json:"tls-secret-name"`
60+
Port int `json:"port"`
6161
}
6262

6363
// newLoadbalancers returns a cloudprovider.LoadBalancer whose concrete type is a *loadbalancer.
@@ -346,27 +346,34 @@ func (l *loadbalancers) buildNodeBalancerConfig(service *v1.Service, port int) (
346346
config.CheckPassive = checkPassive
347347

348348
if protocol == linodego.ProtocolHTTPS {
349-
isTLS, err := isTLSPort(service, port)
350-
if err != nil {
349+
if err = l.processHTTPS(service, &config, port); err != nil {
351350
return config, err
352351
}
353-
if isTLS {
354-
cert, key := getSSLCertInfo(service)
355-
if cert == "" && key == "" {
356-
return config, fmt.Errorf("must set %v and %v annotation for https protocol", annLinodeSSLCertificate, annLinodeSSLKey)
357-
}
358-
if cert != "" {
359-
config.SSLCert = cert
360-
}
361-
if key != "" {
362-
config.SSLKey = key
363-
}
364-
}
365352
}
366353

367354
return config, nil
368355
}
369356

357+
func (l *loadbalancers) processHTTPS(service *v1.Service, nbConfig *linodego.NodeBalancerConfig, port int) error {
358+
if err := l.retrieveKubeClient(); err != nil {
359+
return err
360+
}
361+
tlsAnnotations, err := getTLSAnnotations(service)
362+
if err != nil {
363+
return err
364+
}
365+
366+
tlsPorts := getTLSPorts(tlsAnnotations)
367+
isTLS := isTLSPort(tlsPorts, port)
368+
if isTLS {
369+
nbConfig.SSLCert, nbConfig.SSLKey, err = getTLSCertInfo(l.kubeClient, tlsAnnotations, service.Namespace, port)
370+
if err != nil {
371+
return err
372+
}
373+
}
374+
return nil
375+
}
376+
370377
// buildLoadBalancerRequest returns a linodego.NodeBalancer
371378
// requests for service across nodes.
372379
func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, service *v1.Service, nodes []*v1.Node) (*linodego.NodeBalancer, error) {
@@ -402,6 +409,22 @@ func (l *loadbalancers) buildNodeBalancerNodeCreateOptions(node *v1.Node, nodePo
402409
}
403410
}
404411

412+
func (l *loadbalancers) retrieveKubeClient() error {
413+
if l.kubeClient == nil {
414+
kubeConfig, err := rest.InClusterConfig()
415+
if err != nil {
416+
return err
417+
}
418+
419+
l.kubeClient, err = kubernetes.NewForConfig(kubeConfig)
420+
if err != nil {
421+
return err
422+
}
423+
}
424+
425+
return nil
426+
}
427+
405428
// getProtocol returns the desired protocol of service.
406429
func getProtocol(service *v1.Service) (linodego.ConfigProtocol, error) {
407430
protocol, ok := service.Annotations[annLinodeProtocol]
@@ -427,39 +450,36 @@ func getHealthCheckType(service *v1.Service) (linodego.ConfigCheck, error) {
427450
return linodego.ConfigCheck(hType), nil
428451
}
429452

430-
func isTLSPort(service *v1.Service, port int) (bool, error) {
431-
tlsPortsSlice, err := getTLSPorts(service)
432-
if err != nil {
433-
return false, err
434-
}
453+
func isTLSPort(tlsPortsSlice []int, port int) bool {
435454
for _, tlsPort := range tlsPortsSlice {
436455
if port == tlsPort {
437-
return true, nil
456+
return true
438457
}
439458
}
440-
return false, nil
459+
return false
441460
}
442461

443462
// getTLSPorts returns the ports of service that are set to use TLS.
444-
func getTLSPorts(service *v1.Service) ([]int, error) {
445-
tlsPorts, ok := service.Annotations[annLinodeTLSPorts]
446-
if !ok {
447-
return nil, nil
463+
func getTLSPorts(tlsAnnotations []*tlsAnnotation) []int {
464+
tlsPortsInt := make([]int, 0)
465+
for _, tlsAnnotation := range tlsAnnotations {
466+
tlsPortsInt = append(tlsPortsInt, tlsAnnotation.Port)
448467
}
449468

450-
tlsPortsSlice := strings.Split(tlsPorts, ",")
451-
452-
tlsPortsInt := make([]int, len(tlsPortsSlice))
453-
for i, tlsPort := range tlsPortsSlice {
454-
port, err := strconv.Atoi(tlsPort)
455-
if err != nil {
456-
return nil, err
457-
}
469+
return tlsPortsInt
470+
}
458471

459-
tlsPortsInt[i] = port
472+
func getTLSAnnotations(service *v1.Service) ([]*tlsAnnotation, error) {
473+
annotationJSON, ok := service.Annotations[annLinodeLoadBalancerTLS]
474+
if !ok {
475+
return nil, fmt.Errorf("annotation %v must be specified", annLinodeLoadBalancerTLS)
460476
}
461-
462-
return tlsPortsInt, nil
477+
tlsAnnotations := make([]*tlsAnnotation, 0)
478+
err := json.Unmarshal([]byte(annotationJSON), &tlsAnnotations)
479+
if err != nil {
480+
return nil, err
481+
}
482+
return tlsAnnotations, nil
463483
}
464484

465485
func getNodeInternalIp(node *v1.Node) string {
@@ -503,18 +523,26 @@ func getStickiness(service *v1.Service) linodego.ConfigStickiness {
503523
}
504524
}
505525

506-
func getSSLCertInfo(service *v1.Service) (string, string) {
507-
cert := service.Annotations[annLinodeSSLCertificate]
508-
if cert != "" {
509-
cb, _ := base64.StdEncoding.DecodeString(cert)
510-
cert = strings.TrimSpace(string(cb))
511-
}
512-
key := service.Annotations[annLinodeSSLKey]
513-
if key != "" {
514-
kb, _ := base64.StdEncoding.DecodeString(key)
515-
key = strings.TrimSpace(string(kb))
526+
func getTLSCertInfo(kubeClient kubernetes.Interface, tlsAnnotations []*tlsAnnotation, namespace string, port int) (string, string, error) {
527+
for _, tlsAnnotation := range tlsAnnotations {
528+
if tlsAnnotation.Port == port {
529+
secret, err := kubeClient.CoreV1().Secrets(namespace).Get(tlsAnnotation.TlsSecretName, metav1.GetOptions{})
530+
if err != nil {
531+
return "", "", err
532+
}
533+
534+
cert := string(secret.Data[v1.TLSCertKey])
535+
cert = strings.TrimSpace(cert)
536+
537+
key := string(secret.Data[v1.TLSPrivateKeyKey])
538+
539+
key = strings.TrimSpace(key)
540+
541+
return cert, key, nil
542+
}
516543
}
517-
return cert, key
544+
545+
return "", "", fmt.Errorf("cert & key for port %v is not specified in annotation %v", port, annLinodeLoadBalancerTLS)
518546
}
519547

520548
func getConnectionThrottle(service *v1.Service) int {

0 commit comments

Comments
 (0)