Skip to content

Commit c6af549

Browse files
committed
add SSL cert discovery
1 parent 3f91bb6 commit c6af549

19 files changed

+363
-43
lines changed

pkg/build/builder.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/util/sets"
99
api "sigs.k8s.io/aws-alb-ingress-controller/pkg/apis/ingress/v1alpha1"
1010
"sigs.k8s.io/aws-alb-ingress-controller/pkg/auth"
11+
"sigs.k8s.io/aws-alb-ingress-controller/pkg/build/tls"
1112
"sigs.k8s.io/aws-alb-ingress-controller/pkg/cloud"
1213
"sigs.k8s.io/aws-alb-ingress-controller/pkg/ingress"
1314
"sigs.k8s.io/aws-alb-ingress-controller/pkg/k8s"
@@ -78,9 +79,11 @@ func (b *defaultBuilder) buildListenersAndLBSG(ctx context.Context, stack *LoadB
7879
defaultActionsByPort := map[int64][]api.ListenerAction{}
7980
rulesByPort := map[int64][]api.ListenerRule{}
8081

82+
annoCertBuilder := tls.NewAnnotationCertificateBuilder(b.annotationParser)
83+
inferACMCertBuilder := tls.NewInferACMCertificateBuilder(b.cloud)
8184
for _, ing := range ingGroup.ActiveMembers {
8285
tlsPolicy := b.buildIngressTLSPolicy(ctx, ing)
83-
tlsCerts, err := b.buildIngressTLSCerts(ctx, ing)
86+
tlsCerts, err := annoCertBuilder.Build(ctx, ing)
8487
if err != nil {
8588
return nil, nil, err
8689
}
@@ -104,6 +107,14 @@ func (b *defaultBuilder) buildListenersAndLBSG(ctx context.Context, stack *LoadB
104107
tlsPolicyByPort[port] = tlsPolicy
105108
}
106109

110+
if len(tlsCerts) == 0 {
111+
var err error
112+
tlsCerts, err = inferACMCertBuilder.Build(ctx, ing)
113+
if err != nil {
114+
return nil, nil, err
115+
}
116+
}
117+
107118
// maintain original order for tlsCertsByPort[port], since we use the first cert as default listener certificate.
108119
existingTLSCertSet := sets.NewString(tlsCertsByPort[port]...)
109120
for _, cert := range tlsCerts {

pkg/build/listener.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ import (
99
"sigs.k8s.io/aws-alb-ingress-controller/pkg/k8s"
1010
)
1111

12-
// build the TLS Cert list for specified Ingress.
13-
func (b *defaultBuilder) buildIngressTLSCerts(ctx context.Context, ing *extensions.Ingress) ([]string, error) {
14-
var rawTLSCerts []string
15-
_ = b.annotationParser.ParseStringSliceAnnotation(k8s.AnnotationSuffixCertificateARN, &rawTLSCerts, ing.Annotations)
16-
17-
// TODO(@M00nF1sh): Cert Discovery
18-
return rawTLSCerts, nil
19-
}
20-
2112
func (b *defaultBuilder) buildIngressTLSPolicy(ctx context.Context, ing *extensions.Ingress) string {
2213
var tlsPolicy string
2314
_ = b.annotationParser.ParseStringAnnotation(k8s.AnnotationSuffixSSLPolicy, &tlsPolicy, ing.Annotations)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package tls
2+
3+
import (
4+
"context"
5+
extensions "k8s.io/api/extensions/v1beta1"
6+
"sigs.k8s.io/aws-alb-ingress-controller/pkg/k8s"
7+
)
8+
9+
func NewAnnotationCertificateBuilder(annotationParser k8s.AnnotationParser) CertificateBuilder {
10+
return &annotationCertificateBuilder{
11+
annotationParser: annotationParser,
12+
}
13+
}
14+
15+
type annotationCertificateBuilder struct {
16+
annotationParser k8s.AnnotationParser
17+
}
18+
19+
func (b *annotationCertificateBuilder) Build(ctx context.Context, ing *extensions.Ingress) ([]string, error) {
20+
var rawTLSCerts []string
21+
_ = b.annotationParser.ParseStringSliceAnnotation(k8s.AnnotationSuffixCertificateARN, &rawTLSCerts, ing.Annotations)
22+
23+
return rawTLSCerts, nil
24+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package tls
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go/aws"
6+
"github.com/aws/aws-sdk-go/service/acm"
7+
"github.com/pkg/errors"
8+
extensions "k8s.io/api/extensions/v1beta1"
9+
"k8s.io/apimachinery/pkg/util/sets"
10+
"reflect"
11+
"sigs.k8s.io/aws-alb-ingress-controller/pkg/cloud"
12+
"sigs.k8s.io/aws-alb-ingress-controller/pkg/k8s"
13+
"strings"
14+
"sync"
15+
)
16+
17+
func NewInferACMCertificateBuilder(cloud cloud.Cloud) CertificateBuilder {
18+
return &inferACMCertificateBuilder{
19+
cloud: cloud,
20+
}
21+
}
22+
23+
// inferACMCertificateBuilder
24+
type inferACMCertificateBuilder struct {
25+
cloud cloud.Cloud
26+
27+
domainNamesByCertMutex sync.Mutex
28+
domainNamesByCert map[string][]string
29+
}
30+
31+
// If TLS Certificate can be found for some or all hosts in Ingress, these certificate will be returned.
32+
// If multiple TLS Certificate are found for same host, an error will be returned.
33+
func (b *inferACMCertificateBuilder) Build(ctx context.Context, ing *extensions.Ingress) ([]string, error) {
34+
var tlsHosts = extractIngressTLSHosts(ing)
35+
if len(tlsHosts) == 0 {
36+
return nil, nil
37+
}
38+
39+
if err := b.loadDomainNamesByCert(ctx); err != nil {
40+
return nil, err
41+
}
42+
43+
certARNsForIng := sets.NewString()
44+
for host := range tlsHosts {
45+
certARNsForHost := sets.NewString()
46+
for certArn, domainNames := range b.domainNamesByCert {
47+
for _, domain := range domainNames {
48+
if isACMCertDomainMatchesTLSHost(domain, host) {
49+
certARNsForHost.Insert(certArn)
50+
break
51+
}
52+
}
53+
}
54+
ingKey := k8s.NamespacedName(ing).String()
55+
if len(certARNsForHost) > 1 {
56+
return nil, errors.Errorf("multiple certificate found for host: %s, ingress: %s", host, ingKey)
57+
}
58+
if len(certARNsForHost) == 0 {
59+
return nil, errors.Errorf("none certificate found for host: %s, ingress: %s", host, ingKey)
60+
}
61+
certARNsForIng = certARNsForIng.Union(certARNsForHost)
62+
}
63+
return certARNsForIng.List(), nil
64+
}
65+
66+
func (b *inferACMCertificateBuilder) loadDomainNamesByCert(ctx context.Context) error {
67+
if b.domainNamesByCert != nil {
68+
return nil
69+
}
70+
b.domainNamesByCertMutex.Lock()
71+
defer b.domainNamesByCertMutex.Unlock()
72+
if b.domainNamesByCert != nil {
73+
return nil
74+
}
75+
76+
certs, err := b.cloud.ACM().ListCertificatesAsList(ctx, &acm.ListCertificatesInput{
77+
CertificateStatuses: aws.StringSlice([]string{acm.CertificateStatusIssued}),
78+
})
79+
if err != nil {
80+
return err
81+
}
82+
83+
domainNamesByCert := make(map[string][]string, len(certs))
84+
for _, cert := range certs {
85+
certArn := aws.StringValue(cert.CertificateArn)
86+
domainNames, err := b.lookupCertificateDomainNames(ctx, certArn)
87+
if err != nil {
88+
return err
89+
}
90+
domainNamesByCert[certArn] = domainNames
91+
}
92+
b.domainNamesByCert = domainNamesByCert
93+
return nil
94+
}
95+
96+
// lookupCertificateDomainNames lookup the domainNames for certificate from aws ACM API.
97+
func (b *inferACMCertificateBuilder) lookupCertificateDomainNames(ctx context.Context, certArn string) ([]string, error) {
98+
resp, err := b.cloud.ACM().DescribeCertificateWithContext(ctx, &acm.DescribeCertificateInput{
99+
CertificateArn: aws.String(certArn),
100+
})
101+
if err != nil {
102+
return nil, err
103+
}
104+
return aws.StringValueSlice(resp.Certificate.SubjectAlternativeNames), nil
105+
}
106+
107+
// extractIngressTLSHosts extracts TLS HostNames from ingress.
108+
func extractIngressTLSHosts(ing *extensions.Ingress) sets.String {
109+
hosts := sets.NewString()
110+
111+
for _, r := range ing.Spec.Rules {
112+
if len(r.Host) != 0 {
113+
hosts.Insert(r.Host)
114+
}
115+
}
116+
117+
for _, t := range ing.Spec.TLS {
118+
hosts.Insert(t.Hosts...)
119+
}
120+
121+
return hosts
122+
}
123+
124+
func isACMCertDomainMatchesTLSHost(certDomain string, tlsHost string) bool {
125+
if strings.HasPrefix(certDomain, "*.") {
126+
ds := strings.Split(certDomain, ".")
127+
hs := strings.Split(tlsHost, ".")
128+
129+
if len(ds) != len(hs) {
130+
return false
131+
}
132+
133+
return reflect.DeepEqual(ds[1:], hs[1:])
134+
}
135+
136+
return certDomain == tlsHost
137+
}

pkg/build/tls/interfaces.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package tls
2+
3+
import (
4+
"context"
5+
extensions "k8s.io/api/extensions/v1beta1"
6+
)
7+
8+
// CertificateBuilder is responsible for constructing TLSCertificates for Ingress.
9+
type CertificateBuilder interface {
10+
Build(ctx context.Context, ing *extensions.Ingress) ([]string, error)
11+
}

pkg/cloud/acm.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cloud
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go/aws/session"
6+
"github.com/aws/aws-sdk-go/service/acm"
7+
"github.com/aws/aws-sdk-go/service/acm/acmiface"
8+
)
9+
10+
// ACM is an wrapper around original ACMAPI with additional convenient APIs.
11+
type ACM interface {
12+
acmiface.ACMAPI
13+
14+
ListCertificatesAsList(ctx context.Context, input *acm.ListCertificatesInput) ([]*acm.CertificateSummary, error)
15+
}
16+
17+
func NewACM(session *session.Session) ACM {
18+
return &defaultACM{
19+
acm.New(session),
20+
}
21+
}
22+
23+
var _ ACM = (*defaultACM)(nil)
24+
25+
type defaultACM struct {
26+
acmiface.ACMAPI
27+
}
28+
29+
func (c *defaultACM) ListCertificatesAsList(ctx context.Context, input *acm.ListCertificatesInput) ([]*acm.CertificateSummary, error) {
30+
var result []*acm.CertificateSummary
31+
if err := c.ListCertificatesPagesWithContext(ctx, input, func(output *acm.ListCertificatesOutput, _ bool) bool {
32+
result = append(result, output.CertificateSummaryList...)
33+
return true
34+
}); err != nil {
35+
return nil, err
36+
}
37+
return result, nil
38+
}

pkg/cloud/cloud.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
type Cloud interface {
10+
ACM() ACM
1011
ELBV2() ELBV2
1112
EC2() EC2
1213
RGT() RGT
@@ -18,6 +19,7 @@ type Cloud interface {
1819
type defaultCloud struct {
1920
config Config
2021

22+
acm ACM
2123
elbv2 ELBV2
2224
ec2 EC2
2325
rgt RGT
@@ -48,12 +50,17 @@ func New(cfg Config) (Cloud, error) {
4850
session = session.Copy(&aws.Config{Region: aws.String(cfg.Region)})
4951
return &defaultCloud{
5052
config: cfg,
53+
acm: NewACM(session),
5154
elbv2: NewELBV2(session),
5255
ec2: NewEC2(session),
5356
rgt: NewRGT(session),
5457
}, nil
5558
}
5659

60+
func (c *defaultCloud) ACM() ACM {
61+
return c.acm
62+
}
63+
5764
func (c *defaultCloud) ELBV2() ELBV2 {
5865
return c.elbv2
5966
}

pkg/controller/endpointbinding/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func Initialize(mgr manager.Manager, cloud cloud.Cloud, ebRepo backend.EndpointB
3030
return nil
3131
}
3232

33-
func watchClusterEvents(c controller.Controller, cache cache.Cache, ebRepo backend.EndpointBindingRepo) error {
33+
func watchClusterEvents(c controller.Controller, _ cache.Cache, ebRepo backend.EndpointBindingRepo) error {
3434
if err := watchEndpointBindingRepo(c, ebRepo); err != nil {
3535
return err
3636
}

pkg/controller/endpointbinding/reconciler.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ func (r *ReconcileEndpointBinding) Reconcile(request reconcile.Request) (reconci
5454

5555
desiredTargets, err := r.endpointResolver.Resolve(ctx, svcKey, eb.Spec.ServicePort, eb.Spec.TargetType)
5656
if err != nil {
57-
// TODO: (Fix this)
58-
return reconcile.Result{}, nil
57+
return reconcile.Result{}, err
5958
}
6059

6160
tgArn := eb.Spec.TargetGroup.TargetGroupARN

pkg/controller/ingress/eventhandlers/ingress.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
extensions "k8s.io/api/extensions/v1beta1"
66
"k8s.io/client-go/util/workqueue"
7+
"reflect"
78
"sigs.k8s.io/aws-alb-ingress-controller/pkg/ingress"
89
"sigs.k8s.io/aws-alb-ingress-controller/pkg/k8s"
910
"sigs.k8s.io/controller-runtime/pkg/event"
@@ -32,8 +33,14 @@ func (h *enqueueRequestsForIngressEvent) Create(e event.CreateEvent, queue workq
3233

3334
// Update is called in response to an update event - e.g. Pod Updated.
3435
func (h *enqueueRequestsForIngressEvent) Update(e event.UpdateEvent, queue workqueue.RateLimitingInterface) {
35-
h.enqueueIfIngressClassMatched(e.ObjectOld.(*extensions.Ingress), queue)
36-
h.enqueueIfIngressClassMatched(e.ObjectNew.(*extensions.Ingress), queue)
36+
ingOld := e.ObjectOld.(*extensions.Ingress)
37+
ingNew := e.ObjectNew.(*extensions.Ingress)
38+
if reflect.DeepEqual(ingOld.Annotations, ingNew.Annotations) && reflect.DeepEqual(ingOld.Spec, ingNew.Spec) {
39+
return
40+
}
41+
42+
h.enqueueIfIngressClassMatched(ingOld, queue)
43+
h.enqueueIfIngressClassMatched(ingNew, queue)
3744
}
3845

3946
// Delete is called in response to a delete event - e.g. Pod Deleted.

pkg/controller/ingress/reconciler.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package ingress
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
6+
"github.com/pkg/errors"
77
corev1 "k8s.io/api/core/v1"
88
extensions "k8s.io/api/extensions/v1beta1"
99
"sigs.k8s.io/aws-alb-ingress-controller/pkg/build"
@@ -76,20 +76,18 @@ func (r *ReconcileIngress) Reconcile(request reconcile.Request) (reconcile.Resul
7676
return reconcile.Result{}, err
7777
}
7878

79-
logging.FromContext(ctx).Info("successfully built model", "groupID", group.ID.String())
80-
8179
payload, err := json.Marshal(model)
82-
fmt.Println(string(payload))
80+
logging.FromContext(ctx).Info("successfully built model", "groupID", groupID.String(),
81+
"model", string(payload))
8382

84-
lbDNS, err := r.modelDeployer.Deploy(ctx, model)
85-
if err != nil {
83+
if err := r.modelDeployer.Deploy(ctx, &model); err != nil {
8684
return reconcile.Result{}, err
8785
}
8886
logging.FromContext(ctx).Info("successfully deployed model", "groupID", group.ID.String())
8987

90-
if lbDNS != "" {
88+
if model.LoadBalancer != nil {
9189
for _, ing := range group.ActiveMembers {
92-
if err := r.updateIngressStatus(ctx, ing, lbDNS); err != nil {
90+
if err := r.updateIngressStatus(ctx, ing, model.LoadBalancer.Status.DNSName); err != nil {
9391
return reconcile.Result{}, err
9492
}
9593
}
@@ -116,6 +114,10 @@ func (r *ReconcileIngress) updateIngressStatus(ctx context.Context, ingress *ext
116114
Hostname: lbDNS,
117115
},
118116
}
117+
if err := r.client.Status().Update(ctx, ingress); err != nil {
118+
return errors.Wrapf(err, "failed to update ingress:%v", ingress)
119+
}
120+
119121
return r.client.Status().Update(ctx, ingress)
120122
}
121123
return nil

0 commit comments

Comments
 (0)