From 9cf770e456ee3351407d1ab6e83febfe7ef8c3d3 Mon Sep 17 00:00:00 2001 From: Frank Yang Date: Tue, 11 Aug 2020 23:21:23 +0800 Subject: [PATCH] feat(aws): add PolicyNames for ELB to change listener's security policy --- docs/cluster_spec.md | 6 +- k8s/crds/kops.k8s.io_clusters.yaml | 6 ++ pkg/apis/kops/cluster.go | 2 + pkg/apis/kops/v1alpha2/cluster.go | 2 + .../kops/v1alpha2/zz_generated.conversion.go | 2 + .../kops/v1alpha2/zz_generated.deepcopy.go | 5 ++ pkg/apis/kops/zz_generated.deepcopy.go | 5 ++ pkg/model/awsmodel/api_loadbalancer.go | 3 + upup/pkg/fi/cloudup/awstasks/load_balancer.go | 70 +++++++++++++++---- 9 files changed, 85 insertions(+), 16 deletions(-) diff --git a/docs/cluster_spec.md b/docs/cluster_spec.md index 8d559bd40cfde..bdaddfb634ce7 100644 --- a/docs/cluster_spec.md +++ b/docs/cluster_spec.md @@ -2,7 +2,7 @@ The `Cluster` resource contains the specification of the cluster itself. -The complete list of keys can be found at the [Cluster](https://pkg.go.dev/k8s.io/kops/pkg/apis/kops#ClusterSpec) reference page. +The complete list of keys can be found at the [Cluster](https://pkg.go.dev/k8s.io/kops/pkg/apis/kops#ClusterSpec) reference page. On this page, we will expand on the more important configuration keys. @@ -48,7 +48,7 @@ spec: idleTimeoutSeconds: 300 ``` -You can use a valid SSL Certificate for your API Server Load Balancer. Currently, only AWS is supported: +You can use a valid SSL Certificate for your API Server Load Balancer. Also, you can change listener's [security policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies) by `listenerPolices`. Currently, only AWS is supported: ```yaml spec: @@ -56,6 +56,8 @@ spec: loadBalancer: type: Public sslCertificate: arn:aws:acm:::certificate/ + listenerPolicies: + - ELBSecurityPolicy-TLS-1-2-2017-01 ``` *Openstack only* diff --git a/k8s/crds/kops.k8s.io_clusters.yaml b/k8s/crds/kops.k8s.io_clusters.yaml index 1d9a87450049e..df98bb85c10d5 100644 --- a/k8s/crds/kops.k8s.io_clusters.yaml +++ b/k8s/crds/kops.k8s.io_clusters.yaml @@ -96,6 +96,12 @@ spec: loadbalancer. format: int64 type: integer + listenerPolices: + description: ListenerPolices allows you to overwrite ELB listener's + Security Policy + items: + type: string + type: array securityGroupOverride: description: SecurityGroupOverride overrides the default Kops created SG for the load balancer. diff --git a/pkg/apis/kops/cluster.go b/pkg/apis/kops/cluster.go index f2e070539bae8..82d575c9bc040 100644 --- a/pkg/apis/kops/cluster.go +++ b/pkg/apis/kops/cluster.go @@ -365,6 +365,8 @@ type LoadBalancerAccessSpec struct { UseForInternalApi bool `json:"useForInternalApi,omitempty"` // SSLCertificate allows you to specify the ACM cert to be used the LB SSLCertificate string `json:"sslCertificate,omitempty"` + // ListenerPolices allows you to overwrite ELB listener's Security Policy + ListenerPolices []string `json:"listenerPolices,omitempty"` // CrossZoneLoadBalancing allows you to enable the cross zone load balancing CrossZoneLoadBalancing *bool `json:"crossZoneLoadBalancing,omitempty"` } diff --git a/pkg/apis/kops/v1alpha2/cluster.go b/pkg/apis/kops/v1alpha2/cluster.go index 78e31e6070b1a..719bb6097baa1 100644 --- a/pkg/apis/kops/v1alpha2/cluster.go +++ b/pkg/apis/kops/v1alpha2/cluster.go @@ -366,6 +366,8 @@ type LoadBalancerAccessSpec struct { UseForInternalApi bool `json:"useForInternalApi,omitempty"` // SSLCertificate allows you to specify the ACM cert to be used the LB SSLCertificate string `json:"sslCertificate,omitempty"` + // ListenerPolices allows you to overwrite ELB listener's Security Policy + ListenerPolices []string `json:"listenerPolices,omitempty"` // CrossZoneLoadBalancing allows you to enable the cross zone load balancing CrossZoneLoadBalancing *bool `json:"crossZoneLoadBalancing,omitempty"` } diff --git a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go index fed10f5b455aa..5d079c190eda0 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go @@ -4519,6 +4519,7 @@ func autoConvert_v1alpha2_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec( out.AdditionalSecurityGroups = in.AdditionalSecurityGroups out.UseForInternalApi = in.UseForInternalApi out.SSLCertificate = in.SSLCertificate + out.ListenerPolices = in.ListenerPolices out.CrossZoneLoadBalancing = in.CrossZoneLoadBalancing return nil } @@ -4535,6 +4536,7 @@ func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha2_LoadBalancerAccessSpec( out.AdditionalSecurityGroups = in.AdditionalSecurityGroups out.UseForInternalApi = in.UseForInternalApi out.SSLCertificate = in.SSLCertificate + out.ListenerPolices = in.ListenerPolices out.CrossZoneLoadBalancing = in.CrossZoneLoadBalancing return nil } diff --git a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go index 4969cbf279b65..2cfcee063d86b 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go @@ -3049,6 +3049,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ListenerPolices != nil { + in, out := &in.ListenerPolices, &out.ListenerPolices + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.CrossZoneLoadBalancing != nil { in, out := &in.CrossZoneLoadBalancing, &out.CrossZoneLoadBalancing *out = new(bool) diff --git a/pkg/apis/kops/zz_generated.deepcopy.go b/pkg/apis/kops/zz_generated.deepcopy.go index 4f33528e5efa0..5667f9aa5bf32 100644 --- a/pkg/apis/kops/zz_generated.deepcopy.go +++ b/pkg/apis/kops/zz_generated.deepcopy.go @@ -3247,6 +3247,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ListenerPolices != nil { + in, out := &in.ListenerPolices, &out.ListenerPolices + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.CrossZoneLoadBalancing != nil { in, out := &in.CrossZoneLoadBalancing, &out.CrossZoneLoadBalancing *out = new(bool) diff --git a/pkg/model/awsmodel/api_loadbalancer.go b/pkg/model/awsmodel/api_loadbalancer.go index 437018e0f2377..268b25651723e 100644 --- a/pkg/model/awsmodel/api_loadbalancer.go +++ b/pkg/model/awsmodel/api_loadbalancer.go @@ -111,6 +111,9 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error { if lbSpec.SSLCertificate != "" { listeners["443"] = &awstasks.LoadBalancerListener{InstancePort: 443, SSLCertificateID: lbSpec.SSLCertificate} + if len(lbSpec.ListenerPolices) != 0 { + listeners["443"].PolicyNames = lbSpec.ListenerPolices + } } if lbSpec.SecurityGroupOverride != nil { diff --git a/upup/pkg/fi/cloudup/awstasks/load_balancer.go b/upup/pkg/fi/cloudup/awstasks/load_balancer.go index d443c18e6fb97..1247f0abd677f 100644 --- a/upup/pkg/fi/cloudup/awstasks/load_balancer.go +++ b/upup/pkg/fi/cloudup/awstasks/load_balancer.go @@ -78,24 +78,31 @@ func (e *LoadBalancer) CompareWithID() *string { type LoadBalancerListener struct { InstancePort int SSLCertificateID string + PolicyNames []string } -func (e *LoadBalancerListener) mapToAWS(loadBalancerPort int64) *elb.Listener { - l := &elb.Listener{ - LoadBalancerPort: aws.Int64(loadBalancerPort), - InstancePort: aws.Int64(int64(e.InstancePort)), +func (e *LoadBalancerListener) mapToAWS(loadBalancerPort int64) *elb.ListenerDescription { + ld := &elb.ListenerDescription{ + Listener: &elb.Listener{ + LoadBalancerPort: aws.Int64(loadBalancerPort), + InstancePort: aws.Int64(int64(e.InstancePort)), + }, + PolicyNames: []*string{}, } if e.SSLCertificateID != "" { - l.Protocol = aws.String("SSL") - l.InstanceProtocol = aws.String("SSL") - l.SSLCertificateId = aws.String(e.SSLCertificateID) + ld.Listener.Protocol = aws.String("SSL") + ld.Listener.InstanceProtocol = aws.String("SSL") + ld.Listener.SSLCertificateId = aws.String(e.SSLCertificateID) + for _, p := range e.PolicyNames { + ld.PolicyNames = append(ld.PolicyNames, aws.String(p)) + } } else { - l.Protocol = aws.String("TCP") - l.InstanceProtocol = aws.String("TCP") + ld.Listener.Protocol = aws.String("TCP") + ld.Listener.InstanceProtocol = aws.String("TCP") } - return l + return ld } var _ fi.HasDependencies = &LoadBalancerListener{} @@ -506,8 +513,8 @@ func (_ *LoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LoadBalan if err != nil { return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort) } - awsListener := listener.mapToAWS(loadBalancerPortInt) - request.Listeners = append(request.Listeners, awsListener) + awsListenerDescription := listener.mapToAWS(loadBalancerPortInt) + request.Listeners = append(request.Listeners, awsListenerDescription.Listener) } klog.V(2).Infof("Creating ELB with Name:%q", loadBalancerName) @@ -529,6 +536,24 @@ func (_ *LoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LoadBalan return fmt.Errorf("Unable to find newly created ELB %q", loadBalancerName) } e.HostedZoneId = lb.CanonicalHostedZoneNameID + + // Set ELB listener's Security Policy + if l, ok := e.Listeners["443"]; ok && len(l.PolicyNames) != 0 { + policyNames := []*string{} + for _, p := range l.PolicyNames { + policyNames = append(policyNames, aws.String(p)) + } + + klog.V(2).Infof("Updating LoadBalancer listener's security policy") + _, err = t.Cloud.ELB().SetLoadBalancerPoliciesOfListener(&elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerPort: aws.Int64(443), + PolicyNames: policyNames, + }) + if err != nil { + return fmt.Errorf("error updating load balancer listener's security policy: %v", err) + } + } } else { loadBalancerName = fi.StringValue(a.LoadBalancerName) @@ -604,8 +629,8 @@ func (_ *LoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LoadBalan if err != nil { return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort) } - awsListener := listener.mapToAWS(loadBalancerPortInt) - request.Listeners = append(request.Listeners, awsListener) + awsListenerDescription := listener.mapToAWS(loadBalancerPortInt) + request.Listeners = append(request.Listeners, awsListenerDescription.Listener) } klog.V(2).Infof("Creating LoadBalancer listeners") @@ -614,6 +639,23 @@ func (_ *LoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LoadBalan if err != nil { return fmt.Errorf("error creating LoadBalancerListeners: %v", err) } + + if l, ok := changes.Listeners["443"]; ok && len(l.PolicyNames) != 0 { + policyNames := []*string{} + for _, p := range l.PolicyNames { + policyNames = append(policyNames, aws.String(p)) + } + + klog.V(2).Infof("Updating LoadBalancer listener's security policy") + _, err = t.Cloud.ELB().SetLoadBalancerPoliciesOfListener(&elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerPort: aws.Int64(443), + PolicyNames: policyNames, + }) + if err != nil { + return fmt.Errorf("error updating load balancer listener's security policy: %v", err) + } + } } }