From cb382930935901268414ba65f45990c94a25df12 Mon Sep 17 00:00:00 2001 From: Micah Hausler Date: Mon, 23 Oct 2017 14:47:33 -0400 Subject: [PATCH] Added service annotation for AWS ELB SSL policy --- pkg/cloudprovider/providers/aws/aws.go | 34 +++++++++ pkg/cloudprovider/providers/aws/aws_fakes.go | 8 ++ .../providers/aws/aws_loadbalancer.go | 75 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/pkg/cloudprovider/providers/aws/aws.go b/pkg/cloudprovider/providers/aws/aws.go index 65c360f7e09..8c70d222dac 100644 --- a/pkg/cloudprovider/providers/aws/aws.go +++ b/pkg/cloudprovider/providers/aws/aws.go @@ -129,6 +129,11 @@ const ServiceAnnotationLoadBalancerCertificate = "service.beta.kubernetes.io/aws // listeners. Defaults to '*' (all). const ServiceAnnotationLoadBalancerSSLPorts = "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" +// ServiceAnnotationLoadBalancerSSLNegotiationPolicy is the annotation used on +// the service to specify a SSL negotiation settings for the HTTPS/SSL listeners +// of your load balancer. Defaults to AWS's default +const ServiceAnnotationLoadBalancerSSLNegotiationPolicy = "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy" + // ServiceAnnotationLoadBalancerBEProtocol is the annotation used on the service // to specify the protocol spoken by the backend (pod) behind a listener. // If `http` (default) or `https`, an HTTPS listener that terminates the @@ -259,6 +264,8 @@ type ELB interface { DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) CreateLoadBalancerPolicy(*elb.CreateLoadBalancerPolicyInput) (*elb.CreateLoadBalancerPolicyOutput, error) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancerPoliciesForBackendServerInput) (*elb.SetLoadBalancerPoliciesForBackendServerOutput, error) + SetLoadBalancerPoliciesOfListener(input *elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error) + DescribeLoadBalancerPolicies(input *elb.DescribeLoadBalancerPoliciesInput) (*elb.DescribeLoadBalancerPoliciesOutput, error) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) @@ -3016,6 +3023,20 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n return nil, err } + if sslPolicyName, ok := annotations[ServiceAnnotationLoadBalancerSSLNegotiationPolicy]; ok { + err := c.ensureSSLNegotiationPolicy(loadBalancer, sslPolicyName) + if err != nil { + return nil, err + } + + for _, port := range c.getLoadBalancerTLSPorts(loadBalancer) { + err := c.setSSLNegotiationPolicy(loadBalancerName, sslPolicyName, port) + if err != nil { + return nil, err + } + } + } + if path, healthCheckNodePort := service.GetServiceHealthCheckPathPort(apiService); path != "" { glog.V(4).Infof("service %v (%v) needs health checks on :%d%s)", apiService.Name, loadBalancerName, healthCheckNodePort, path) err = c.ensureLoadBalancerHealthCheck(loadBalancer, "HTTP", healthCheckNodePort, path) @@ -3407,6 +3428,19 @@ func (c *Cloud) UpdateLoadBalancer(clusterName string, service *v1.Service, node return fmt.Errorf("Load balancer not found") } + if sslPolicyName, ok := service.Annotations[ServiceAnnotationLoadBalancerSSLNegotiationPolicy]; ok { + err := c.ensureSSLNegotiationPolicy(lb, sslPolicyName) + if err != nil { + return err + } + for _, port := range c.getLoadBalancerTLSPorts(lb) { + err := c.setSSLNegotiationPolicy(loadBalancerName, sslPolicyName, port) + if err != nil { + return err + } + } + } + err = c.ensureLoadBalancerInstances(aws.StringValue(lb.LoadBalancerName), lb.Instances, instances) if err != nil { return nil diff --git a/pkg/cloudprovider/providers/aws/aws_fakes.go b/pkg/cloudprovider/providers/aws/aws_fakes.go index 069da837db4..1c9ab44194f 100644 --- a/pkg/cloudprovider/providers/aws/aws_fakes.go +++ b/pkg/cloudprovider/providers/aws/aws_fakes.go @@ -352,6 +352,14 @@ func (elb *FakeELB) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancer panic("Not implemented") } +func (elb *FakeELB) SetLoadBalancerPoliciesOfListener(input *elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DescribeLoadBalancerPolicies(input *elb.DescribeLoadBalancerPoliciesInput) (*elb.DescribeLoadBalancerPoliciesOutput, error) { + panic("Not implemented") +} + func (elb *FakeELB) DescribeLoadBalancerAttributes(*elb.DescribeLoadBalancerAttributesInput) (*elb.DescribeLoadBalancerAttributesOutput, error) { panic("Not implemented") } diff --git a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go index 03eefb296e0..bd2182588dc 100644 --- a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go +++ b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" "github.com/golang/glog" @@ -33,6 +34,8 @@ import ( const ProxyProtocolPolicyName = "k8s-proxyprotocol-enabled" +const SSLNegotiationPolicyNameFormat = "k8s-SSLNegotiationPolicy-%s" + // getLoadBalancerAdditionalTags converts the comma separated list of key-value // pairs in the ServiceAnnotationLoadBalancerAdditionalTags annotation and returns // it as a map. @@ -471,6 +474,78 @@ func (c *Cloud) ensureLoadBalancerInstances(loadBalancerName string, lbInstances return nil } +func (c *Cloud) getLoadBalancerTLSPorts(loadBalancer *elb.LoadBalancerDescription) []int64 { + ports := []int64{} + + for _, listenerDescription := range loadBalancer.ListenerDescriptions { + protocol := aws.StringValue(listenerDescription.Listener.Protocol) + if protocol == "SSL" || protocol == "HTTPS" { + ports = append(ports, aws.Int64Value(listenerDescription.Listener.LoadBalancerPort)) + } + } + return ports +} + +func (c *Cloud) ensureSSLNegotiationPolicy(loadBalancer *elb.LoadBalancerDescription, policyName string) error { + glog.V(2).Info("Describing load balancer policies on load balancer") + result, err := c.elb.DescribeLoadBalancerPolicies(&elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: loadBalancer.LoadBalancerName, + PolicyNames: []*string{ + aws.String(fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)), + }, + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case "PolicyNotFound": + // TODO change from string to `elb.ErrCodePolicyNotFoundException` once the AWS SDK is updated + default: + return fmt.Errorf("error describing security policies on load balancer: %q", err) + } + } + } + + if len(result.PolicyDescriptions) > 0 { + return nil + } + + glog.V(2).Infof("Creating SSL negotiation policy '%s' on load balancer", fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)) + // there is an upper limit of 98 policies on an ELB, we're pretty safe from + // running into it + _, err = c.elb.CreateLoadBalancerPolicy(&elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: loadBalancer.LoadBalancerName, + PolicyName: aws.String(fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)), + PolicyTypeName: aws.String("SSLNegotiationPolicyType"), + PolicyAttributes: []*elb.PolicyAttribute{ + { + AttributeName: aws.String("Reference-Security-Policy"), + AttributeValue: aws.String(policyName), + }, + }, + }) + if err != nil { + return fmt.Errorf("error creating security policy on load balancer: %q", err) + } + return nil +} + +func (c *Cloud) setSSLNegotiationPolicy(loadBalancerName, sslPolicyName string, port int64) error { + policyName := fmt.Sprintf(SSLNegotiationPolicyNameFormat, sslPolicyName) + request := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerPort: aws.Int64(port), + PolicyNames: []*string{ + aws.String(policyName), + }, + } + glog.V(2).Infof("Setting SSL negotiation policy '%s' on load balancer", policyName) + _, err := c.elb.SetLoadBalancerPoliciesOfListener(request) + if err != nil { + return fmt.Errorf("error setting SSL negotiation policy '%s' on load balancer: %q", policyName, err) + } + return nil +} + func (c *Cloud) createProxyProtocolPolicy(loadBalancerName string) error { request := &elb.CreateLoadBalancerPolicyInput{ LoadBalancerName: aws.String(loadBalancerName),