Skip to content

Commit 87bef3e

Browse files
authored
Merge pull request #818 from M00nF1sh/multi-certs
add support for multiple certificates
2 parents 5b57e85 + 4bc5ef5 commit 87bef3e

File tree

9 files changed

+443
-201
lines changed

9 files changed

+443
-201
lines changed

docs/guide/ingress/annotation.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ You can add kubernetes annotations to ingress and service objects to customize t
2323
|[alb.ingress.kubernetes.io/auth-session-timeout](#auth-session-timeout)|integer|604800|ingress,service|
2424
|[alb.ingress.kubernetes.io/auth-type](#auth-type)|none\|oidc\|cognito|none|ingress,service|
2525
|[alb.ingress.kubernetes.io/backend-protocol](#backend-protocol)|HTTP \| HTTPS|HTTP|ingress,service|
26-
|[alb.ingress.kubernetes.io/certificate-arn](#certificate-arn)|string|N/A|ingress|
26+
|[alb.ingress.kubernetes.io/certificate-arn](#certificate-arn)|stringList|N/A|ingress|
2727
|[alb.ingress.kubernetes.io/healthcheck-interval-seconds](#healthcheck-interval-seconds)|integer|'15'|ingress,service|
2828
|[alb.ingress.kubernetes.io/healthcheck-path](#healthcheck-path)|string|/|ingress,service|
2929
|[alb.ingress.kubernetes.io/healthcheck-port](#healthcheck-port)|integer \| traffic-port|traffic-port|ingress,service|
@@ -324,12 +324,21 @@ Health check on target groups can be controlled with following annotations:
324324
## SSL
325325
SSL support can be controlled with following annotations:
326326

327-
- <a name="certificate-arn">`alb.ingress.kubernetes.io/certificate-arn`</a> specifies the ARN of certificate managed by [AWS Certificate Manager](https://aws.amazon.com/certificate-manager)
328-
327+
- <a name="certificate-arn">`alb.ingress.kubernetes.io/certificate-arn`</a> specifies the ARN of one or more certificate managed by [AWS Certificate Manager](https://aws.amazon.com/certificate-manager)
328+
329+
!!!tip ""
330+
The first certificate in the list will be added as default certificate. And remaining certificate will be added to the optional certificate list.
331+
See [SSL Certificates](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#https-listener-certificates) for more details.
332+
329333
!!!example
330-
```
331-
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:xxxxx:certificate/xxxxxxx
332-
```
334+
- single certificate
335+
```
336+
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:xxxxx:certificate/xxxxxxx
337+
```
338+
- multiple certificates
339+
```
340+
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:xxxxx:certificate/cert1,arn:aws:acm:us-west-2:xxxxx:certificate/cert2,arn:aws:acm:us-west-2:xxxxx:certificate/cert3
341+
```
333342

334343
- <a name="ssl-policy">`alb.ingress.kubernetes.io/ssl-policy`</a> specifies the [Security Policy](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies) that should be assigned to the ALB, allowing you to control the protocol and ciphers.
335344

internal/alb/ls/listener.go

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import (
44
"context"
55
"fmt"
66

7+
"k8s.io/apimachinery/pkg/util/sets"
8+
9+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/parser"
10+
"github.com/pkg/errors"
11+
712
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/auth"
813

914
"github.com/aws/aws-sdk-go/aws/awsutil"
@@ -18,6 +23,15 @@ import (
1823
extensions "k8s.io/api/extensions/v1beta1"
1924
)
2025

26+
const (
27+
AnnotationSSLPolicy = "ssl-policy"
28+
AnnotationCertificateARN = "certificate-arn"
29+
)
30+
31+
const (
32+
DefaultSSLPolicy = "ELBSecurityPolicy-2016-08"
33+
)
34+
2135
type ReconcileOptions struct {
2236
LBArn string
2337
Ingress *extensions.Ingress
@@ -52,9 +66,11 @@ type defaultController struct {
5266
type listenerConfig struct {
5367
Port *int64
5468
Protocol *string
55-
SslPolicy *string
56-
Certificates []*elbv2.Certificate
5769
DefaultActions []*elbv2.Action
70+
71+
SslPolicy *string
72+
DefaultCertificate []*elbv2.Certificate
73+
ExtraCertificateARNs []string
5874
}
5975

6076
func (controller *defaultController) Reconcile(ctx context.Context, options ReconcileOptions) error {
@@ -73,6 +89,14 @@ func (controller *defaultController) Reconcile(ctx context.Context, options Reco
7389
return fmt.Errorf("failed to reconcile listener due to %v", err)
7490
}
7591
}
92+
93+
if options.Port.Scheme == elbv2.ProtocolEnumHttps {
94+
lsArn := aws.StringValue(instance.ListenerArn)
95+
if err := controller.reconcileExtraCertificates(ctx, lsArn, config.ExtraCertificateARNs); err != nil {
96+
return errors.Wrapf(err, "failed to reconcile extra certificates on listener %v", lsArn)
97+
}
98+
}
99+
76100
if err := controller.rulesController.Reconcile(ctx, instance, options.Ingress, options.IngressAnnos, options.TGGroup); err != nil {
77101
return fmt.Errorf("failed to reconcile rules due to %v", err)
78102
}
@@ -85,7 +109,7 @@ func (controller *defaultController) newLSInstance(ctx context.Context, lbArn st
85109
LoadBalancerArn: aws.String(lbArn),
86110
Port: config.Port,
87111
Protocol: config.Protocol,
88-
Certificates: config.Certificates,
112+
Certificates: config.DefaultCertificate,
89113
SslPolicy: config.SslPolicy,
90114
DefaultActions: config.DefaultActions,
91115
})
@@ -102,7 +126,7 @@ func (controller *defaultController) reconcileLSInstance(ctx context.Context, in
102126
ListenerArn: instance.ListenerArn,
103127
Port: config.Port,
104128
Protocol: config.Protocol,
105-
Certificates: config.Certificates,
129+
Certificates: config.DefaultCertificate,
106130
SslPolicy: config.SslPolicy,
107131
DefaultActions: config.DefaultActions,
108132
})
@@ -124,8 +148,8 @@ func (controller *defaultController) LSInstanceNeedsModification(ctx context.Con
124148
albctx.GetLogger(ctx).DebugLevelf(1, "listener protocol needs modification: %v => %v", awsutil.Prettify(instance.Protocol), awsutil.Prettify(config.Protocol))
125149
needModification = true
126150
}
127-
if !util.DeepEqual(instance.Certificates, config.Certificates) {
128-
albctx.GetLogger(ctx).DebugLevelf(1, "listener certificates needs modification: %v => %v", awsutil.Prettify(instance.Certificates), awsutil.Prettify(config.Certificates))
151+
if !util.DeepEqual(instance.Certificates, config.DefaultCertificate) {
152+
albctx.GetLogger(ctx).DebugLevelf(1, "listener certificates needs modification: %v => %v", awsutil.Prettify(instance.Certificates), awsutil.Prettify(config.DefaultCertificate))
129153
needModification = true
130154
}
131155
if !util.DeepEqual(instance.SslPolicy, config.SslPolicy) {
@@ -139,22 +163,71 @@ func (controller *defaultController) LSInstanceNeedsModification(ctx context.Con
139163
return needModification
140164
}
141165

166+
func (controller *defaultController) reconcileExtraCertificates(ctx context.Context, lsArn string, extraCertificateARNs []string) error {
167+
certificates, err := controller.cloud.DescribeListenerCertificates(ctx, lsArn)
168+
if err != nil {
169+
return err
170+
}
171+
actualExtraCertificateArns := sets.NewString()
172+
for _, certificate := range certificates {
173+
if !aws.BoolValue(certificate.IsDefault) {
174+
actualExtraCertificateArns.Insert(aws.StringValue(certificate.CertificateArn))
175+
}
176+
}
177+
desiredExtraCertificateArns := sets.NewString(extraCertificateARNs...)
178+
179+
certificatesToAdd := desiredExtraCertificateArns.Difference(actualExtraCertificateArns)
180+
certificatesToRemove := actualExtraCertificateArns.Difference(desiredExtraCertificateArns)
181+
for certARN := range certificatesToAdd {
182+
albctx.GetLogger(ctx).Infof("adding certificate %v to listener %v", certARN, lsArn)
183+
if _, err := controller.cloud.AddListenerCertificates(ctx, &elbv2.AddListenerCertificatesInput{
184+
ListenerArn: aws.String(lsArn),
185+
Certificates: []*elbv2.Certificate{
186+
{
187+
CertificateArn: aws.String(certARN),
188+
},
189+
},
190+
}); err != nil {
191+
return err
192+
}
193+
}
194+
for certARN := range certificatesToRemove {
195+
albctx.GetLogger(ctx).Infof("removing certificate %v from listener %v", certARN, lsArn)
196+
if _, err := controller.cloud.RemoveListenerCertificates(ctx, &elbv2.RemoveListenerCertificatesInput{
197+
ListenerArn: aws.String(lsArn),
198+
Certificates: []*elbv2.Certificate{
199+
{
200+
CertificateArn: aws.String(certARN),
201+
},
202+
},
203+
}); err != nil {
204+
return err
205+
}
206+
}
207+
return nil
208+
}
209+
142210
func (controller *defaultController) buildListenerConfig(ctx context.Context, options ReconcileOptions) (listenerConfig, error) {
143211
config := listenerConfig{
144212
Port: aws.Int64(options.Port.Port),
145213
Protocol: aws.String(options.Port.Scheme),
146214
}
147215
if options.Port.Scheme == elbv2.ProtocolEnumHttps {
148-
if options.IngressAnnos.Listener.CertificateArn != nil {
149-
config.Certificates = []*elbv2.Certificate{
150-
{
151-
CertificateArn: options.IngressAnnos.Listener.CertificateArn,
152-
},
153-
}
216+
sslPolicy := DefaultSSLPolicy
217+
_ = annotations.LoadStringAnnotation(AnnotationSSLPolicy, &sslPolicy, options.Ingress.Annotations)
218+
config.SslPolicy = aws.String(sslPolicy)
219+
220+
var certificateARNs []string
221+
_ = annotations.LoadStringSliceAnnotation(AnnotationCertificateARN, &certificateARNs, options.Ingress.Annotations)
222+
if len(certificateARNs) == 0 {
223+
return config, errors.Errorf("annotation %v must be specified for https listener", parser.GetAnnotationWithPrefix(AnnotationCertificateARN))
154224
}
155-
if options.IngressAnnos.Listener.SslPolicy != nil {
156-
config.SslPolicy = options.IngressAnnos.Listener.SslPolicy
225+
config.DefaultCertificate = []*elbv2.Certificate{
226+
{
227+
CertificateArn: aws.String(certificateARNs[0]),
228+
},
157229
}
230+
config.ExtraCertificateARNs = certificateARNs[1:]
158231
}
159232

160233
actions, err := controller.buildDefaultActions(ctx, options)

0 commit comments

Comments
 (0)