Skip to content

Commit 952a2c1

Browse files
Add Backendset SSL certificates
1 parent b36103d commit 952a2c1

10 files changed

+467
-96
lines changed

docs/tutorial-ssl-backendset.md

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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-backendset-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+
***Note: Certificates for BackendSets require a CA certificate to be provided.***
64+
65+
```yaml
66+
apiVersion: v1
67+
kind: Secret
68+
metadata:
69+
name: ssl-certificate-secret
70+
type: Opaque
71+
data:
72+
ca.crt: LS0tLS1CRUdJTiBDRV(...)
73+
tls.crt: LS0tLS1CRUdJTi(...)
74+
tls.key: LS0tLS1CRUdJTi(...)
75+
```
76+
77+
```
78+
kubectl create -f ssl-certificate-secret.yaml
79+
```
80+
81+
Create the service:
82+
83+
```
84+
$ kubectl create -f manifests/demo/nginx-demo-svc-ssl.yaml
85+
```
86+
87+
Watch the service and await a public IP address. This will be the load balancer
88+
IP which you can use to connect to your service.
89+
90+
```
91+
$ kubectl get svc --watch
92+
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
93+
nginx-service 10.96.97.137 129.213.12.174 80:30274/TCP 5m
94+
```
95+
96+
You can now access your service via the provisioned load balancer using either
97+
http or https:
98+
99+
```
100+
curl http://129.213.12.174
101+
curl --insecure https://129.213.12.174
102+
```
103+
104+
Note: The `--insecure` flag above is only required due to our use of self-signed
105+
certificates in this example.

pkg/oci/load_balancer.go

+54-23
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ const (
6363
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
6464
ServiceAnnotationLoadBalancerTLSSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-secret"
6565

66+
// ServiceAnnotationLoadBalancerBackendSetSecret is a Service annotation for
67+
// specifying the generic secret to install on the load balancer listeners which
68+
// have SSL enabled.
69+
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
70+
ServiceAnnotationLoadBalancerBackendSetSecret = "service.beta.kubernetes.io/oci-load-balancer-backendset-secret"
71+
6672
// ServiceAnnotationLoadBalancerConnectionIdleTimeout is the annotation used
6773
// on the service to specify the idle connection timeout.
6874
ServiceAnnotationLoadBalancerConnectionIdleTimeout = "service.beta.kubernetes.io/oci-load-balancer-connection-idle-timeout"
@@ -89,9 +95,10 @@ const (
8995
// Fallback value if annotation on service is not set
9096
lbDefaultShape = "100Mbps"
9197

92-
lbNodesHealthCheckPath = "/healthz"
93-
lbNodesHealthCheckPort = k8sports.ProxyHealthzPort
94-
lbNodesHealthCheckProto = "HTTP"
98+
lbNodesHealthCheckPath = "/healthz"
99+
lbNodesHealthCheckPort = k8sports.ProxyHealthzPort
100+
lbNodesHealthCheckProtoHTTP = "HTTP"
101+
lbNodesHealthCheckProtoTCP = "TCP"
95102
)
96103

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

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

198208
ns, name := parseSecretString(secretString)
@@ -201,24 +211,35 @@ func (cp *CloudProvider) readSSLSecret(svc *v1.Service) (string, string, error)
201211
}
202212
secret, err := cp.kubeclient.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{})
203213
if err != nil {
204-
return "", "", err
214+
return nil, err
205215
}
206216

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)
217+
var cacert, cert, key, pass []byte
218+
var cacertstr, passstr string
219+
if cacert, ok = secret.Data[SSLCAFileName]; !ok {
220+
cacertstr = ""
221+
} else {
222+
cacertstr = string(cacert)
210223
}
211-
if key, ok = secret.Data[sslPrivateKeyFileName]; !ok {
212-
return "", "", errors.Errorf("%s not found in secret %s/%s", sslPrivateKeyFileName, ns, name)
224+
if cert, ok = secret.Data[SSLCertificateFileName]; !ok {
225+
return nil, errors.Errorf("%s not found in secret %s/%s", SSLCertificateFileName, ns, name)
213226
}
214-
215-
return string(cert), string(key), nil
227+
if key, ok = secret.Data[SSLPrivateKeyFileName]; !ok {
228+
return nil, errors.Errorf("%s not found in secret %s/%s", SSLPrivateKeyFileName, ns, name)
229+
}
230+
if pass, ok = secret.Data[SSLPassphrase]; !ok {
231+
passstr = ""
232+
} else {
233+
passstr = string(pass)
234+
}
235+
return &SSLK8SSecret{CACert: cacertstr, PublicCert: string(cert), PrivateKey: string(key),
236+
Passphrase: passstr}, nil
216237
}
217238

218239
// ensureSSLCertificate creates a OCI SSL certificate to the given load
219240
// 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
241+
func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalancer.LoadBalancer, sslConfig *SSLConfig, svc *v1.Service) error {
242+
name := sslConfig.Name
222243
logger := cp.logger.With("loadBalancerID", *lb.Id, "certificateName", name)
223244
_, err := cp.client.LoadBalancer().GetCertificateByName(ctx, *lb.Id, name)
224245
if err == nil {
@@ -230,7 +251,8 @@ func (cp *CloudProvider) ensureSSLCertificate(ctx context.Context, lb *loadbalan
230251
}
231252

232253
// Although we iterate here only one certificate is supported at the moment.
233-
certs, err := spec.Certificates()
254+
certs := make(map[string]loadbalancer.CertificateDetails)
255+
err = buildCertificates(sslConfig, svc, certs)
234256
if err != nil {
235257
return err
236258
}
@@ -266,7 +288,12 @@ func (cp *CloudProvider) createLoadBalancer(ctx context.Context, spec *LBSpec) (
266288
}
267289

268290
// Then we create the load balancer and wait for it to be online.
269-
certs, err := spec.Certificates()
291+
certs := make(map[string]loadbalancer.CertificateDetails)
292+
err = buildCertificates(spec.ListenerSSLConfig, spec.service, certs)
293+
if err != nil {
294+
return nil, errors.Wrap(err, "get certificates")
295+
}
296+
err = buildCertificates(spec.BackendSetSSLConfig, spec.service, certs)
270297
if err != nil {
271298
return nil, errors.Wrap(err, "get certificates")
272299
}
@@ -323,16 +350,17 @@ func (cp *CloudProvider) EnsureLoadBalancer(ctx context.Context, clusterName str
323350
}
324351
exists := !client.IsNotFound(err)
325352

326-
var ssl *SSLConfig
353+
var sslListener, sslBackendSet *SSLConfig
327354
if requiresCertificate(service) {
328355
ports, err := getSSLEnabledPorts(service)
329356
if err != nil {
330357
return nil, err
331358
}
332-
ssl = NewSSLConfig(lbName, ports, cp)
359+
sslListener = NewSSLConfig(lbName, ServiceAnnotationLoadBalancerTLSSecret, ports, cp)
360+
sslBackendSet = NewSSLConfig(lbName, ServiceAnnotationLoadBalancerBackendSetSecret, ports, cp)
333361
}
334362
subnets := []string{cp.config.LoadBalancer.Subnet1, cp.config.LoadBalancer.Subnet2}
335-
spec, err := NewLBSpec(service, nodes, subnets, ssl, cp.securityListManagerFactory)
363+
spec, err := NewLBSpec(service, nodes, subnets, sslListener, sslBackendSet, cp.securityListManagerFactory)
336364
if err != nil {
337365
logger.With(zap.Error(err)).Error("Failed to derive LBSpec")
338366
return nil, err
@@ -351,8 +379,11 @@ func (cp *CloudProvider) EnsureLoadBalancer(ctx context.Context, clusterName str
351379

352380
// If the load balancer needs an SSL cert ensure it is present.
353381
if requiresCertificate(service) {
354-
if err := cp.ensureSSLCertificate(ctx, lb, spec); err != nil {
355-
return nil, errors.Wrap(err, "ensuring ssl certificate")
382+
if err := cp.ensureSSLCertificate(ctx, lb, spec.ListenerSSLConfig, spec.service); err != nil {
383+
return nil, errors.Wrap(err, "ensuring ssl certificate for listeners")
384+
}
385+
if err := cp.ensureSSLCertificate(ctx, lb, spec.BackendSetSSLConfig, spec.service); err != nil {
386+
return nil, errors.Wrap(err, "ensuring ssl certificate for backend sets")
356387
}
357388
}
358389

0 commit comments

Comments
 (0)