Skip to content

Commit

Permalink
Add sni support
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkeloscar committed Dec 17, 2017
1 parent 806f9df commit 65fd11b
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 115 deletions.
28 changes: 15 additions & 13 deletions aws/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const (

nameTag = "Name"

certificateARNTag = "ingress:certificate-arn"
certificateARNsTag = "ingress:certificate-arns"
)

var (
Expand Down Expand Up @@ -225,15 +225,17 @@ func (a *Adapter) FindManagedStacks() ([]*Stack, error) {
return stacks, nil
}

// CreateStack creates a new Application Load Balancer using CloudFormation. The stack name is derived
// from the Cluster ID and the certificate ARN (when available).
// All the required resources (listeners and target group) are created in a transactional fashion.
// CreateStack creates a new Application Load Balancer using CloudFormation.
// The stack name is derived from the Cluster ID and a has of the certificate
// ARNs (when available).
// All the required resources (listeners and target group) are created in a
// transactional fashion.
// Failure to create the stack causes it to be deleted automatically.
func (a *Adapter) CreateStack(certificateARN string, scheme string) (string, error) {
func (a *Adapter) CreateStack(certificateARNs []string, scheme string) (string, error) {
spec := &stackSpec{
name: a.stackName(certificateARN),
name: a.stackName(certificateARNs),
scheme: scheme,
certificateARN: certificateARN,
certificateARNs: certificateARNs,
securityGroupID: a.SecurityGroupID(),
subnets: a.PublicSubnetIDs(),
vpcID: a.VpcID(),
Expand All @@ -249,11 +251,11 @@ func (a *Adapter) CreateStack(certificateARN string, scheme string) (string, err
return createStack(a.cloudformation, spec)
}

func (a *Adapter) UpdateStack(certificateARN string, scheme string) (string, error) {
func (a *Adapter) UpdateStack(certificateARNs []string, scheme string) (string, error) {
spec := &stackSpec{
name: a.stackName(certificateARN),
name: a.stackName(certificateARNs),
scheme: scheme,
certificateARN: certificateARN,
certificateARNs: certificateARNs,
securityGroupID: a.SecurityGroupID(),
subnets: a.PublicSubnetIDs(),
vpcID: a.VpcID(),
Expand All @@ -269,8 +271,8 @@ func (a *Adapter) UpdateStack(certificateARN string, scheme string) (string, err
return updateStack(a.cloudformation, spec)
}

func (a *Adapter) stackName(certificateARN string) string {
return normalizeStackName(a.ClusterID(), certificateARN)
func (a *Adapter) stackName(certificateARNs []string) string {
return normalizeStackName(a.ClusterID(), certificateARNs)
}

// GetStack returns the CloudFormation stack details with the name or ID from the argument
Expand All @@ -282,7 +284,7 @@ func (a *Adapter) GetStack(stackID string) (*Stack, error) {
func (a *Adapter) MarkToDeleteStack(stack *Stack) (time.Time, error) {
t0 := time.Now().Add(a.stackTTL)

return t0, markToDeleteStack(a.cloudformation, a.stackName(stack.CertificateARN()), t0.Format(time.RFC3339))
return t0, markToDeleteStack(a.cloudformation, stack.name, t0.Format(time.RFC3339))
}

// DeleteStack deletes the CloudFormation stack with the given name
Expand Down
115 changes: 91 additions & 24 deletions aws/cf.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
yaml "gopkg.in/yaml.v2"
)

const (
Expand All @@ -20,12 +21,12 @@ const (

// Stack is a simple wrapper around a CloudFormation Stack.
type Stack struct {
name string
dnsName string
scheme string
targetGroupARN string
certificateARN string
tags map[string]string
name string
dnsName string
scheme string
targetGroupARN string
CertificateARNs []string
tags map[string]string
}

func (s *Stack) Name() string {
Expand All @@ -40,10 +41,6 @@ func (s *Stack) Scheme() string {
return s.scheme
}

func (s *Stack) CertificateARN() string {
return s.certificateARN
}

func (s *Stack) TargetGroupARN() string {
return s.targetGroupARN
}
Expand Down Expand Up @@ -123,14 +120,14 @@ const (
parameterTargetGroupHealthCheckPortParameter = "TargetGroupHealthCheckPortParameter"
parameterTargetGroupHealthCheckIntervalParameter = "TargetGroupHealthCheckIntervalParameter"
parameterTargetGroupVPCIDParameter = "TargetGroupVPCIDParameter"
parameterListenerCertificateParameter = "ListenerCertificateParameter"
parameterListenerCertificatesParameter = "ListenerCertificatesParameter"
)

type stackSpec struct {
name string
scheme string
subnets []string
certificateARN string
certificateARNs []string
securityGroupID string
clusterID string
vpcID string
Expand All @@ -145,11 +142,75 @@ type healthCheck struct {
interval time.Duration
}

type Certificate struct {
CertificateArn string `yaml:"CertificateArn"`
}

// cfTemplate is an opauqe structure for unmarshaling a yaml cloudformation
// stack into in order to replace the list of certificates for the
// HTTPSListener.
type cfTemplate struct {
AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"`
Description string `yaml:"Description"`
Parameters interface{} `yaml:"Parameters"`
Conditions interface{} `yaml:"Conditions"`
Resources struct {
HTTPListener interface{} `yaml:"HTTPListener"`
HTTPSListener struct {
Type string `yaml:"Type"`
Condition string `yaml:"Condition"`
Properties struct {
DefaultActions []struct {
Type string `yaml:"Type"`
TargetGroupArn string `yaml:"TargetGroupArn"`
} `yaml:"DefaultActions"`
LoadBalancerArn string `yaml:"LoadBalancerArn"`
Port int `yaml:"Port"`
Protocol string `yaml:"Protocol"`
Certificates []Certificate `yaml:"Certificates"`
}
}
LB interface{} `yaml:"LB"`
TG interface{} `yaml:"TG"`
} `yaml:"Resources"`
Outputs interface{} `yaml:"Outputs"`
}

// injectCertificates injects a list of certificates into the cloudformation
// template.
func injectCertificates(cfTmpl string, certs []string) (string, error) {
var template cfTemplate
err := yaml.Unmarshal([]byte(cfTmpl), &template)
if err != nil {
return "", err
}

certificates := make([]Certificate, len(certs))
for i, cert := range certs {
certificates[i] = Certificate{CertificateArn: cert}
}

template.Resources.HTTPSListener.Properties.Certificates = certificates

data, err := yaml.Marshal(&template)
if err != nil {
return "", err
}

return string(data), nil
}

func createStack(svc cloudformationiface.CloudFormationAPI, spec *stackSpec) (string, error) {
template := templateYAML
if spec.customTemplate != "" {
template = spec.customTemplate
}

template, err := injectCertificates(template, spec.certificateARNs)
if err != nil {
return "", err
}

params := &cloudformation.CreateStackInput{
StackName: aws.String(spec.name),
OnFailure: aws.String(cloudformation.OnFailureDelete),
Expand All @@ -158,7 +219,7 @@ func createStack(svc cloudformationiface.CloudFormationAPI, spec *stackSpec) (st
cfParam(parameterLoadBalancerSecurityGroupParameter, spec.securityGroupID),
cfParam(parameterLoadBalancerSubnetsParameter, strings.Join(spec.subnets, ",")),
cfParam(parameterTargetGroupVPCIDParameter, spec.vpcID),
cfParam(parameterListenerCertificateParameter, spec.certificateARN),
cfParam(parameterListenerCertificatesParameter, strings.Join(spec.certificateARNs, ",")),
},
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
Expand All @@ -167,8 +228,8 @@ func createStack(svc cloudformationiface.CloudFormationAPI, spec *stackSpec) (st
TemplateBody: aws.String(template),
TimeoutInMinutes: aws.Int64(int64(spec.timeoutInMinutes)),
}
if spec.certificateARN != "" {
params.Tags = append(params.Tags, cfTag(certificateARNTag, spec.certificateARN))
if len(spec.certificateARNs) > 0 {
params.Tags = append(params.Tags, cfTag(certificateARNsTag, strings.Join(spec.certificateARNs, ",")))
}
if spec.healthCheck != nil {
params.Parameters = append(params.Parameters,
Expand All @@ -190,23 +251,29 @@ func updateStack(svc cloudformationiface.CloudFormationAPI, spec *stackSpec) (st
if spec.customTemplate != "" {
template = spec.customTemplate
}

template, err := injectCertificates(template, spec.certificateARNs)
if err != nil {
return "", err
}

params := &cloudformation.UpdateStackInput{
StackName: aws.String(spec.name),
Parameters: []*cloudformation.Parameter{
cfParam(parameterLoadBalancerSchemeParameter, spec.scheme),
cfParam(parameterLoadBalancerSecurityGroupParameter, spec.securityGroupID),
cfParam(parameterLoadBalancerSubnetsParameter, strings.Join(spec.subnets, ",")),
cfParam(parameterTargetGroupVPCIDParameter, spec.vpcID),
cfParam(parameterListenerCertificateParameter, spec.certificateARN),
cfParam(parameterListenerCertificatesParameter, strings.Join(spec.certificateARNs, ",")),
},
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+spec.clusterID, resourceLifecycleOwned),
},
TemplateBody: aws.String(template),
}
if spec.certificateARN != "" {
params.Tags = append(params.Tags, cfTag(certificateARNTag, spec.certificateARN))
if len(spec.certificateARNs) > 0 {
params.Tags = append(params.Tags, cfTag(certificateARNsTag, strings.Join(spec.certificateARNs, ",")))
}
if spec.healthCheck != nil {
params.Parameters = append(params.Parameters,
Expand Down Expand Up @@ -299,12 +366,12 @@ func getCFStackByName(svc cloudformationiface.CloudFormationAPI, stackName strin
func mapToManagedStack(stack *cloudformation.Stack) *Stack {
o, t := newStackOutput(stack.Outputs), convertCloudFormationTags(stack.Tags)
return &Stack{
name: aws.StringValue(stack.StackName),
dnsName: o.dnsName(),
scheme : o.scheme(),
targetGroupARN: o.targetGroupARN(),
certificateARN: t[certificateARNTag],
tags: convertCloudFormationTags(stack.Tags),
name: aws.StringValue(stack.StackName),
dnsName: o.dnsName(),
scheme: o.scheme(),
targetGroupARN: o.targetGroupARN(),
CertificateARNs: strings.Split(t[certificateARNsTag], ","),
tags: convertCloudFormationTags(stack.Tags),
}
}

Expand Down
14 changes: 7 additions & 7 deletions aws/cf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func TestFindingManagedStacks(t *testing.T) {
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+"test-cluster", resourceLifecycleOwned),
cfTag(certificateARNTag, "cert-arn"),
cfTag(certificateARNsTag, "cert-arn"),
},
Outputs: []*cloudformation.Output{
{OutputKey: aws.String(outputLoadBalancerDNSName), OutputValue: aws.String("example-notready.com")},
Expand All @@ -201,7 +201,7 @@ func TestFindingManagedStacks(t *testing.T) {
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+"test-cluster", resourceLifecycleOwned),
cfTag(certificateARNTag, "cert-arn"),
cfTag(certificateARNsTag, "cert-arn"),
},
Outputs: []*cloudformation.Output{
{OutputKey: aws.String(outputLoadBalancerDNSName), OutputValue: aws.String("example.com")},
Expand Down Expand Up @@ -239,14 +239,14 @@ func TestFindingManagedStacks(t *testing.T) {
},
[]*Stack{
{
name: "managed-stack",
dnsName: "example.com",
certificateARN: "cert-arn",
targetGroupARN: "tg-arn",
name: "managed-stack",
dnsName: "example.com",
CertificateARNs: []string{"cert-arn"},
targetGroupARN: "tg-arn",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
certificateARNTag: "cert-arn",
certificateARNsTag: "cert-arn",
},
},
},
Expand Down
11 changes: 5 additions & 6 deletions aws/ingress-cf-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ Parameters:
TargetGroupVPCIDParameter:
Type: AWS::EC2::VPC::Id
Description: The VPCID for the TargetGroup
ListenerCertificateParameter:
ListenerCertificatesParameter:
Type: String
Description: The HTTPS Listener certificate ARN (IAM/ACM)
Description: The HTTPS Listener certificate ARNs (IAM/ACM)
Default: ""

Conditions:
CreateHTTPSListener: !Not [ !Equals [ !Ref ListenerCertificateParameter, "" ] ]
CreateHTTPSListener: !Not [ !Equals [ !Ref ListenerCertificatesParameter, "" ] ]

Resources:
HTTPListener:
Type : AWS::ElasticLoadBalancingV2::Listener
Expand All @@ -55,8 +55,7 @@ Resources:
LoadBalancerArn: !Ref LB
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref ListenerCertificateParameter
Certificates: []

LB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Expand Down
25 changes: 11 additions & 14 deletions aws/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ var (
squeezeDashesRegex = regexp.MustCompile("[-]{2,}")
)

// Normalize returns a normalized name which replaces invalid characters from the ClusterID with '-' and appends the
// last 7 chars of the SHA1 hash of the certificateARN. If the length of the normalized clusterID exceeds 24 chars, the
// it is truncated so that its concatenation with a '-' char and the hash part don't exceed 32 chars.
func (n *awsResourceNamer) Normalize(clusterID, certificateARN string) string {
// Normalize returns a normalized name which replaces invalid characters from
// the ClusterID with '-' and appends the last 7 chars of the SHA1 hash of the
// first certificateARN. If the length of the normalized clusterID exceeds 24
// chars, the it is truncated so that its concatenation with a '-' char and the
// hash part don't exceed 32 chars.
func (n *awsResourceNamer) Normalize(clusterID string, certificateARNs []string) string {
hasher := sha1.New()
if certificateARN == "" {
if len(certificateARNs) == 0 {
binary.Write(hasher, binary.BigEndian, emptyARN)
} else {
hasher.Write([]byte(certificateARN))
hasher.Write([]byte(certificateARNs[0]))
}
hash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
hashLen := len(hash)
Expand All @@ -69,14 +71,9 @@ func (n *awsResourceNamer) Normalize(clusterID, certificateARN string) string {
}

var (
stackNamer = &awsResourceNamer{maxLen: maxStackNameLen}
loadBalancerNamer = &awsResourceNamer{maxLen: maxLoadBalancerNameLen}
stackNamer = &awsResourceNamer{maxLen: maxStackNameLen}
)

func normalizeStackName(clusterID, certificateARN string) string {
return stackNamer.Normalize(clusterID, certificateARN)
}

func normalizeLoadBalancerName(clusterID, certificateARN string) string {
return loadBalancerNamer.Normalize(clusterID, certificateARN)
func normalizeStackName(clusterID string, certificateARNs []string) string {
return stackNamer.Normalize(clusterID, certificateARNs)
}
Loading

0 comments on commit 65fd11b

Please sign in to comment.