Skip to content

Commit f3ae919

Browse files
authored
Merge pull request #851 from khyew/cv-auto-lookup-cert-arns
Auto-add certs from ACM by hostname if none are specified via annotation
2 parents fda8e40 + b0e0b08 commit f3ae919

File tree

4 files changed

+442
-1
lines changed

4 files changed

+442
-1
lines changed

internal/alb/ls/listener.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ls
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"k8s.io/apimachinery/pkg/util/sets"
89

@@ -12,13 +13,15 @@ import (
1213
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/auth"
1314

1415
"github.com/aws/aws-sdk-go/aws/awsutil"
16+
"github.com/aws/aws-sdk-go/service/acm"
1517
"github.com/aws/aws-sdk-go/service/elbv2"
1618
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/alb/tg"
1719
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx"
1820
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/aws"
1921
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations"
2022
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/action"
2123
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/loadbalancer"
24+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/log"
2225
util "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/types"
2326
extensions "k8s.io/api/extensions/v1beta1"
2427
)
@@ -220,7 +223,18 @@ func (controller *defaultController) buildListenerConfig(ctx context.Context, op
220223
var certificateARNs []string
221224
_ = annotations.LoadStringSliceAnnotation(AnnotationCertificateARN, &certificateARNs, options.Ingress.Annotations)
222225
if len(certificateARNs) == 0 {
223-
return config, errors.Errorf("annotation %v must be specified for https listener", parser.GetAnnotationWithPrefix(AnnotationCertificateARN))
226+
certs, err := inferCertARNs(controller.cloud, options.Ingress, albctx.GetLogger(ctx))
227+
if err != nil {
228+
return config, errors.Errorf("missing certificates annotation %v and could not auto-load certificates from ACM: %v",
229+
parser.GetAnnotationWithPrefix(AnnotationCertificateARN), err)
230+
}
231+
if len(certs) == 0 {
232+
return config, errors.Errorf("missing certificates annotation %v could not find any matching certificates from ACM to auto-load",
233+
parser.GetAnnotationWithPrefix(AnnotationCertificateARN))
234+
}
235+
236+
albctx.GetLogger(ctx).Infof("Auto-detected and added %d certificates to listener", len(certs))
237+
certificateARNs = certs
224238
}
225239
config.DefaultCertificate = []*elbv2.Certificate{
226240
{
@@ -250,3 +264,76 @@ func (controller *defaultController) buildDefaultActions(ctx context.Context, op
250264
}
251265
return buildActions(ctx, authCfg, options.IngressAnnos, backend, options.TGGroup)
252266
}
267+
268+
// inferCertARNs retrieves a set of certificates from ACM that matches the ingress' hosts list
269+
func inferCertARNs(acmsvc aws.ACMAPI, ingress *extensions.Ingress, logger *log.Logger) ([]string, error) {
270+
var certArns []string
271+
var seen = map[string]bool{}
272+
var ingressHosts = uniqueHosts(ingress)
273+
274+
certs, err := acmsvc.ListCertificates([]string{acm.CertificateStatusIssued})
275+
if err != nil {
276+
return nil, err
277+
}
278+
279+
for _, c := range certs {
280+
for _, h := range ingressHosts {
281+
if domainMatchesHost(aws.StringValue(c.DomainName), h) {
282+
logger.Infof("Domain name '%s', matches TLS host '%v', adding to Listener", aws.StringValue(c.DomainName), h)
283+
if !seen[aws.StringValue(c.CertificateArn)] {
284+
certArns = append(certArns, aws.StringValue(c.CertificateArn))
285+
seen[aws.StringValue(c.CertificateArn)] = true
286+
}
287+
} else {
288+
logger.Debugf("Ignoring domain name '%s', doesn't match '%s'", aws.StringValue(c.DomainName), h)
289+
}
290+
}
291+
}
292+
293+
return certArns, nil
294+
}
295+
296+
func domainMatchesHost(domainName string, tlsHost string) bool {
297+
if strings.HasPrefix(domainName, "*.") {
298+
ds := strings.Split(domainName, ".")
299+
hs := strings.Split(tlsHost, ".")
300+
301+
if len(ds) != len(hs) {
302+
return false
303+
}
304+
305+
for i, dp := range ds {
306+
if i == 0 && dp == "*" {
307+
continue
308+
}
309+
if dp != hs[i] {
310+
return false
311+
}
312+
}
313+
return true
314+
}
315+
316+
return domainName == tlsHost
317+
}
318+
319+
func uniqueHosts(ingress *extensions.Ingress) []string {
320+
var result []string
321+
seen := map[string]bool{}
322+
323+
for _, r := range ingress.Spec.Rules {
324+
if !seen[r.Host] {
325+
result = append(result, r.Host)
326+
seen[r.Host] = true
327+
}
328+
}
329+
for _, t := range ingress.Spec.TLS {
330+
for _, h := range t.Hosts {
331+
if !seen[h] {
332+
result = append(result, h)
333+
seen[h] = true
334+
}
335+
}
336+
}
337+
338+
return result
339+
}

0 commit comments

Comments
 (0)