Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow SSL certs to be updated on ELB's in ECS backend #700

Merged
merged 5 commits into from
Dec 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pkg/lb/elb.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ func (m *ELBManager) CreateLoadBalancer(ctx context.Context, o CreateLoadBalance
}, nil
}

func (m *ELBManager) UpdateLoadBalancer(ctx context.Context, opts UpdateLoadBalancerOpts) error {
if opts.SSLCert != nil {
if err := m.updateSSLCert(ctx, opts.Name, *opts.SSLCert); err != nil {
return err
}
}

return nil
}

func (m *ELBManager) updateSSLCert(ctx context.Context, name, certID string) error {
_, err := m.elb.SetLoadBalancerListenerSSLCertificate(&elb.SetLoadBalancerListenerSSLCertificateInput{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should actually ensure that there's a 443 listener in case there was previously no cert attached.

LoadBalancerName: aws.String(name),
LoadBalancerPort: aws.Int64(443),
SSLCertificateId: aws.String(certID),
})
return err
}

// DestroyLoadBalancer destroys an ELB.
func (m *ELBManager) DestroyLoadBalancer(ctx context.Context, lb *LoadBalancer) error {
_, err := m.elb.DeleteLoadBalancer(&elb.DeleteLoadBalancerInput{
Expand Down
27 changes: 27 additions & 0 deletions pkg/lb/elb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ func TestELB_CreateLoadBalancer(t *testing.T) {
}
}

func TestELB_UpdateLoadBalancer(t *testing.T) {
h := awsutil.NewHandler([]awsutil.Cycle{
{
Request: awsutil.Request{
RequestURI: "/",
Body: `Action=SetLoadBalancerListenerSSLCertificate&LoadBalancerName=loadbalancer&LoadBalancerPort=443&SSLCertificateId=newcert&Version=2012-06-01`,
},
Response: awsutil.Response{
StatusCode: 200,
Body: `<?xml version="1.0"?>
<UpdateLoadBalancerResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
</UpdateLoadBalancerResponse>`,
},
},
})
m, s := newTestELBManager(h)
defer s.Close()

err := m.UpdateLoadBalancer(context.Background(), UpdateLoadBalancerOpts{
Name: "loadbalancer",
SSLCert: aws.String("newcert"),
})
if err != nil {
t.Fatal(err)
}
}

func buildLoadBalancerForDestroy() (*ELBManager, *httptest.Server, *LoadBalancer) {
h := awsutil.NewHandler([]awsutil.Cycle{
{
Expand Down
13 changes: 13 additions & 0 deletions pkg/lb/lb.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ type CreateLoadBalancerOpts struct {
SSLCert string
}

// UpdateLoadBalancerOpts are options that can be provided when updating an
// existing load balancer.
type UpdateLoadBalancerOpts struct {
// The name of the load balancer.
Name string

// The SSL Certificate
SSLCert *string
}

// LoadBalancer represents a load balancer.
type LoadBalancer struct {
// The name of the load balancer.
Expand Down Expand Up @@ -49,6 +59,9 @@ type Manager interface {
// CreateLoadBalancer creates a new LoadBalancer with the given options.
CreateLoadBalancer(context.Context, CreateLoadBalancerOpts) (*LoadBalancer, error)

// UpdateLoadBalancer updates an existing load balancer.
UpdateLoadBalancer(context.Context, UpdateLoadBalancerOpts) error

// DestroyLoadBalancer destroys a load balancer by name.
DestroyLoadBalancer(ctx context.Context, lb *LoadBalancer) error

Expand Down
54 changes: 36 additions & 18 deletions scheduler/ecs/lb.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ func (m *LBProcessManager) CreateProcess(ctx context.Context, app *scheduler.App
// want, we'll return an error. Users should manually destroy
// the app and re-create it with the proper exposure.
if l != nil {
if err = lbOk(p, l); err != nil {
var opts *lb.UpdateLoadBalancerOpts
opts, err = updateOpts(p, l)
if err != nil {
return err
}

if opts != nil {
if err = m.lb.UpdateLoadBalancer(ctx, *opts); err != nil {
return err
}
}
}

// If this app doesn't have a load balancer yet, create one.
Expand Down Expand Up @@ -121,7 +129,7 @@ func (e *LoadBalancerExposureError) Error() string {
lbExposure = "public"
}

return fmt.Sprintf("Process %s is %s, but load balancer is %s.", e.proc.Type, e.proc.Exposure, lbExposure)
return fmt.Sprintf("Process %s is %s, but load balancer is %s. An update would require me to delete the load balancer.", e.proc.Type, e.proc.Exposure, lbExposure)
}

// LoadBalancerPortMismatchError is returned when the port stored in the data store does not match the ELB instance port
Expand All @@ -131,21 +139,11 @@ type LoadBalancerPortMismatchError struct {
}

func (e *LoadBalancerPortMismatchError) Error() string {
return fmt.Sprintf("Process %s instance port is %d, but load balancer instance port is %d.", e.proc.Type, e.proc.Ports[0].Host, e.lb.InstancePort)
}

// SslCertMismatchError is returned when the ssl cert in the data store does not match the ssl cert on the ELB
type SslCertMismatchError struct {
proc *scheduler.Process
lb *lb.LoadBalancer
return fmt.Sprintf("Process %s instance port is %d, but load balancer instance port is %d.", e.proc.Type, *e.proc.Ports[0].Host, e.lb.InstancePort)
}

func (e *SslCertMismatchError) Error() string {
return fmt.Sprintf("Process ssl certificate (%s) does not match load balancer ssl certificate (%s).", e.proc.SSLCert, e.lb.SSLCert)
}

// lbOk checks if the load balancer is suitable for the process.
func lbOk(p *scheduler.Process, lb *lb.LoadBalancer) error {
// canUpdate checks if the load balancer is suitable for the process.
func canUpdate(p *scheduler.Process, lb *lb.LoadBalancer) error {
if p.Exposure == scheduler.ExposePublic && !lb.External {
return &LoadBalancerExposureError{p, lb}
}
Expand All @@ -158,9 +156,29 @@ func lbOk(p *scheduler.Process, lb *lb.LoadBalancer) error {
return &LoadBalancerPortMismatchError{p, lb}
}

if p.SSLCert != lb.SSLCert {
return &SslCertMismatchError{p, lb}
return nil
}

func updateOpts(p *scheduler.Process, b *lb.LoadBalancer) (*lb.UpdateLoadBalancerOpts, error) {
// This load balancer can't be updated to make it work for the process.
// Return an error.
if err := canUpdate(p, b); err != nil {
return nil, err
}

opts := lb.UpdateLoadBalancerOpts{
Name: b.Name,
}

return nil
// Requires an update to the Cert.
if p.SSLCert != b.SSLCert {
opts.SSLCert = &p.SSLCert
}

// Load balancer doesn't require an update.
if opts.SSLCert == nil {
return nil, nil
}

return &opts, nil
}
Loading