Skip to content

Commit 429affc

Browse files
Add Backendset SSL certificates
1 parent d94b7a7 commit 429affc

10 files changed

+658
-91
lines changed

docs/tutorial-ssl-backendset.md

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Tutorial
2+
3+
This example will show you how to use the CCM to create a load balancer with SSL
4+
termination for BackendSets.
5+
6+
### Load balancer with SSL termination for BackendSets example
7+
8+
When you create a service with --type=LoadBalancer a OCI load balancer will be
9+
created.
10+
11+
The example below will create an NGINX deployment and expose it via a load
12+
balancer serving http on port 80, and https on 443. Note that the service
13+
**type** is set to **LoadBalancer**.
14+
15+
```yaml
16+
---
17+
apiVersion: apps/v1beta1
18+
kind: Deployment
19+
metadata:
20+
name: nginx-deployment
21+
spec:
22+
replicas: 2
23+
template:
24+
metadata:
25+
labels:
26+
app: nginx
27+
spec:
28+
containers:
29+
- name: nginx
30+
image: nginx
31+
ports:
32+
- containerPort: 80
33+
---
34+
kind: Service
35+
apiVersion: v1
36+
metadata:
37+
name: nginx-service
38+
annotations:
39+
service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
40+
service.beta.kubernetes.io/oci-load-balancer-tls-secret: ssl-certificate-secret
41+
service.beta.kubernetes.io/oci-load-balancer-tls-backend-secret: ssl-certificate-secret
42+
spec:
43+
selector:
44+
app: nginx
45+
type: LoadBalancer
46+
ports:
47+
- name: http
48+
port: 80
49+
targetPort: 80
50+
- name: https
51+
port: 443
52+
targetPort: 80
53+
```
54+
55+
First the required secret needs to be created in Kubernetes. For the purposes
56+
of this example, we will create a self-signed certificate. However, in
57+
production you would most likely use a public certificate signed by a
58+
certificate authority.
59+
60+
Below is an example of a secret configuration file required to be uploaded as a Kubernetes
61+
generic secret. The CA certificate, the public certificate and the private key need to be base64 encoded :
62+
63+
```
64+
apiVersion: v1
65+
kind: Secret
66+
metadata:
67+
name: ssl-certificate-secret
68+
type: Opaque
69+
data:
70+
ca.crt: LS0tLS1CRUdJTiBDRV(...)
71+
tls.crt: LS0tLS1CRUdJTi(...)
72+
tls.key: LS0tLS1CRUdJTi(...)
73+
```
74+
75+
Self-signed certificates can be generated using the following GitHub repository: https://github.com/square/certstrap
76+
77+
78+
```
79+
kubectl create -f ssl-certificate-secret.yaml
80+
```
81+
82+
Create the service:
83+
84+
```
85+
$ kubectl create -f manifests/demo/nginx-demo-svc-ssl.yaml
86+
```
87+
88+
Watch the service and await a public IP address. This will be the load balancer
89+
IP which you can use to connect to your service.
90+
91+
```
92+
$ kubectl get svc --watch
93+
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
94+
nginx-service 10.96.97.137 129.213.12.174 80:30274/TCP 5m
95+
```
96+
97+
You can now access your service via the provisioned load balancer using either
98+
http or https:
99+
100+
```
101+
curl http://129.213.12.174
102+
curl --insecure https://129.213.12.174
103+
```
104+
105+
Note: The `--insecure` flag above is only required due to our use of self-signed
106+
certificates in this example.

pkg/oci/load_balancer.go

+49-24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package oci
1717
import (
1818
"context"
1919

20+
"github.com/oracle/oci-go-sdk/common"
2021
"github.com/oracle/oci-go-sdk/core"
2122
"github.com/oracle/oci-go-sdk/loadbalancer"
2223
"github.com/pkg/errors"
@@ -63,6 +64,12 @@ const (
6364
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
6465
ServiceAnnotationLoadBalancerTLSSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-secret"
6566

67+
// ServiceAnnotationLoadBalancerTLSBackendSecret is a Service annotation for
68+
// specifying the TLS secret to install on the load balancer listeners which
69+
// have SSL enabled.
70+
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
71+
ServiceAnnotationLoadBalancerTLSBackendSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-backend-secret"
72+
6673
// ServiceAnnotationLoadBalancerConnectionIdleTimeout is the annotation used
6774
// on the service to specify the idle connection timeout.
6875
ServiceAnnotationLoadBalancerConnectionIdleTimeout = "service.beta.kubernetes.io/oci-load-balancer-connection-idle-timeout"
@@ -89,9 +96,10 @@ const (
8996
// Fallback value if annotation on service is not set
9097
lbDefaultShape = "100Mbps"
9198

92-
lbNodesHealthCheckPath = "/healthz"
93-
lbNodesHealthCheckPort = k8sports.ProxyHealthzPort
94-
lbNodesHealthCheckProto = "HTTP"
99+
lbNodesHealthCheckPath = "/healthz"
100+
lbNodesHealthCheckPort = k8sports.ProxyHealthzPort
101+
lbNodesHealthCheckProtoHTTP = "HTTP"
102+
lbNodesHealthCheckProtoTCP = "TCP"
95103
)
96104

97105
// GetLoadBalancer returns whether the specified load balancer exists, and if
@@ -189,10 +197,10 @@ func getSubnetsForNodes(ctx context.Context, nodes []*v1.Node, client client.Int
189197

190198
// readSSLSecret returns the certificate and private key from a Kubernetes TLS
191199
// private key Secret.
192-
func (cp *CloudProvider) readSSLSecret(svc *v1.Service) (string, string, error) {
193-
secretString, ok := svc.Annotations[ServiceAnnotationLoadBalancerTLSSecret]
194-
if !ok {
195-
return "", "", errors.Errorf("no %q annotation found", ServiceAnnotationLoadBalancerTLSSecret)
200+
func (cp *CloudProvider) readSSLSecret(secretType string, svc *v1.Service) (*SSLK8SSecret, error) {
201+
secretString, ok := svc.Annotations[secretType]
202+
if !ok && secretType == ServiceAnnotationLoadBalancerTLSSecret {
203+
return &SSLK8SSecret{}, errors.Errorf("no %q annotation found", secretType)
196204
}
197205

198206
ns, name := parseSecretString(secretString)
@@ -201,24 +209,35 @@ func (cp *CloudProvider) readSSLSecret(svc *v1.Service) (string, string, error)
201209
}
202210
secret, err := cp.kubeclient.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{})
203211
if err != nil {
204-
return "", "", err
212+
return &SSLK8SSecret{}, err
205213
}
206214

207-
var cert, key []byte
208-
if cert, ok = secret.Data[sslCertificateFileName]; !ok {
209-
return "", "", errors.Errorf("%s not found in secret %s/%s", sslCertificateFileName, ns, name)
215+
var cacert, cert, key, pass []byte
216+
var cacertstr, passstr *string
217+
if cacert, ok = secret.Data[SSLCAFileName]; !ok {
218+
cacertstr = new(string)
219+
} else {
220+
cacertstr = common.String(string(cacert))
210221
}
211-
if key, ok = secret.Data[sslPrivateKeyFileName]; !ok {
212-
return "", "", errors.Errorf("%s not found in secret %s/%s", sslPrivateKeyFileName, ns, name)
222+
if cert, ok = secret.Data[SSLCertificateFileName]; !ok {
223+
return &SSLK8SSecret{}, errors.Errorf("%s not found in secret %s/%s", SSLCertificateFileName, ns, name)
213224
}
214-
215-
return string(cert), string(key), nil
225+
if key, ok = secret.Data[SSLPrivateKeyFileName]; !ok {
226+
return &SSLK8SSecret{}, errors.Errorf("%s not found in secret %s/%s", SSLPrivateKeyFileName, ns, name)
227+
}
228+
if pass, ok = secret.Data[SSLPassphrase]; !ok {
229+
passstr = new(string)
230+
} else {
231+
passstr = common.String(string(pass))
232+
}
233+
return &SSLK8SSecret{CACert: cacertstr, PublicCert: common.String(string(cert)), PrivateKey: common.String(string(key)),
234+
Passphrase: passstr}, nil
216235
}
217236

218237
// ensureSSLCertificate creates a OCI SSL certificate to the given load
219238
// balancer, if it doesn't already exist.
220-
func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalancer.LoadBalancer, spec *LBSpec) error {
221-
name := spec.SSLConfig.Name
239+
func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalancer.LoadBalancer, sslConfig *SSLConfig, svc *v1.Service) error {
240+
name := sslConfig.Name
222241
logger := cp.logger.With("loadBalancerID", *lb.Id, "certificateName", name)
223242
_, err := cp.client.LoadBalancer().GetCertificateByName(ctx, *lb.Id, name)
224243
if err == nil {
@@ -230,7 +249,8 @@ func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalan
230249
}
231250

232251
// Although we iterate here only one certificate is supported at the moment.
233-
certs, err := spec.Certificates()
252+
certs := make(map[string]loadbalancer.CertificateDetails)
253+
err = buildCertificates(sslConfig, svc, certs)
234254
if err != nil {
235255
return err
236256
}
@@ -266,7 +286,8 @@ func (cp *CloudProvider) createLoadBalancer(ctx context.Context, spec *LBSpec) (
266286
}
267287

268288
// Then we create the load balancer and wait for it to be online.
269-
certs, err := spec.Certificates()
289+
certs := make(map[string]loadbalancer.CertificateDetails)
290+
err = buildCertificates(spec.SSLConfig, spec.service, certs)
270291
if err != nil {
271292
return nil, errors.Wrap(err, "get certificates")
272293
}
@@ -323,16 +344,17 @@ func (cp *CloudProvider) EnsureLoadBalancer(ctx context.Context, clusterName str
323344
}
324345
exists := !client.IsNotFound(err)
325346

326-
var ssl *SSLConfig
347+
var ssl, sslBackendSet *SSLConfig
327348
if requiresCertificate(service) {
328349
ports, err := getSSLEnabledPorts(service)
329350
if err != nil {
330351
return nil, err
331352
}
332-
ssl = NewSSLConfig(lbName, ports, cp)
353+
ssl = NewSSLConfig(lbName, ServiceAnnotationLoadBalancerTLSSecret, ports, cp)
354+
sslBackendSet = NewSSLConfig(lbName, ServiceAnnotationLoadBalancerTLSBackendSecret, ports, cp)
333355
}
334356
subnets := []string{cp.config.LoadBalancer.Subnet1, cp.config.LoadBalancer.Subnet2}
335-
spec, err := NewLBSpec(service, nodes, subnets, ssl, cp.securityListManagerFactory)
357+
spec, err := NewLBSpec(service, nodes, subnets, ssl, sslBackendSet, cp.securityListManagerFactory)
336358
if err != nil {
337359
logger.With(zap.Error(err)).Error("Failed to derive LBSpec")
338360
return nil, err
@@ -351,8 +373,11 @@ func (cp *CloudProvider) EnsureLoadBalancer(ctx context.Context, clusterName str
351373

352374
// If the load balancer needs an SSL cert ensure it is present.
353375
if requiresCertificate(service) {
354-
if err := cp.ensureSSLCertificate(ctx, lb, spec); err != nil {
355-
return nil, errors.Wrap(err, "ensuring ssl certificate")
376+
if err := cp.ensureSSLCertificate(ctx, lb, spec.SSLConfig, spec.service); err != nil {
377+
return nil, errors.Wrap(err, "ensuring ssl certificate for listeners")
378+
}
379+
if err := cp.ensureSSLCertificate(ctx, lb, spec.SSLBackendSetConfig, spec.service); err != nil {
380+
return nil, errors.Wrap(err, "ensuring ssl certificate for backend sets")
356381
}
357382
}
358383

0 commit comments

Comments
 (0)