From 93dcaddc480dcc030d5074bd6e65261cebc8bbc6 Mon Sep 17 00:00:00 2001 From: Frank Yang Date: Thu, 19 Nov 2020 16:07:21 +0800 Subject: [PATCH] feat(aws): add PolicyNames for ELB to change listener's security policy --- docs/cluster_spec.md | 5 ++++- k8s/crds/kops.k8s.io_clusters.yaml | 3 +++ pkg/apis/kops/cluster.go | 2 ++ pkg/apis/kops/v1alpha2/cluster.go | 2 ++ .../kops/v1alpha2/zz_generated.conversion.go | 2 ++ pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go | 5 +++++ pkg/apis/kops/validation/aws.go | 16 ++++++++++++++++ pkg/apis/kops/zz_generated.deepcopy.go | 5 +++++ pkg/model/awsmodel/api_loadbalancer.go | 9 +++++++-- .../update_cluster/complex/cloudformation.json | 3 ++- .../complex/in-legacy-v1alpha2.yaml | 1 + .../update_cluster/complex/in-v1alpha2.yaml | 1 + .../update_cluster/complex/kubernetes.tf | 1 + .../fi/cloudup/awstasks/network_load_balancer.go | 15 +++++++++++++++ 14 files changed, 66 insertions(+), 4 deletions(-) diff --git a/docs/cluster_spec.md b/docs/cluster_spec.md index 4ae5ee2cb7752..05a15b55dc5e1 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. @@ -49,6 +49,8 @@ spec: You can use a valid SSL Certificate for your API Server Load Balancer. Currently, only AWS is supported. +Also, you can change listener's [security policy](https://docs.aws.amazon.com/sdk-for-go/api/service/elbv2/#CreateListenerInput) by `sslPolicy`. Currently, only AWS Network Load Balancer is supported. + Note that when using `sslCertificate`, client certificate authentication, such as with the credentials generated via `kOps export kubecfg`, will not work through the load balancer. As of kOps 1.19, a `kubecfg` that bypasses the load balancer may be created with the `--internal` flag to `kops update cluster` or `kOps export kubecfg`. Security groups may need to be opened to allow access from the clients to the master instances' port TCP/443, for example by using the `additionalSecurityGroups` field on the master instance groups. ```yaml @@ -57,6 +59,7 @@ spec: loadBalancer: type: Public sslCertificate: arn:aws:acm:::certificate/ + sslPolicy: 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 7cb199308944c..806138ec3bbbd 100644 --- a/k8s/crds/kops.k8s.io_clusters.yaml +++ b/k8s/crds/kops.k8s.io_clusters.yaml @@ -89,6 +89,9 @@ spec: sslCertificate: description: SSLCertificate allows you to specify the ACM cert to be used the LB type: string + sslPolicy: + description: SSLPolicy allows you to overwrite the LB listener's Security Policy + type: string type: description: Type of load balancer to create may Public or Internal. type: string diff --git a/pkg/apis/kops/cluster.go b/pkg/apis/kops/cluster.go index 868bae5f32951..5c197fa711062 100644 --- a/pkg/apis/kops/cluster.go +++ b/pkg/apis/kops/cluster.go @@ -389,6 +389,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"` + // SSLPolicy allows you to overwrite the LB listener's Security Policy + SSLPolicy *string `json:"sslPolicy,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 0bef5ebf16e11..e49a02b632ae3 100644 --- a/pkg/apis/kops/v1alpha2/cluster.go +++ b/pkg/apis/kops/v1alpha2/cluster.go @@ -391,6 +391,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"` + // SSLPolicy allows you to overwrite the LB listener's Security Policy + SSLPolicy *string `json:"sslPolicy,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 18bbc9447c91d..26d59a64cb32f 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go @@ -4692,6 +4692,7 @@ func autoConvert_v1alpha2_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec( out.AdditionalSecurityGroups = in.AdditionalSecurityGroups out.UseForInternalApi = in.UseForInternalApi out.SSLCertificate = in.SSLCertificate + out.SSLPolicy = in.SSLPolicy out.CrossZoneLoadBalancing = in.CrossZoneLoadBalancing return nil } @@ -4709,6 +4710,7 @@ func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha2_LoadBalancerAccessSpec( out.AdditionalSecurityGroups = in.AdditionalSecurityGroups out.UseForInternalApi = in.UseForInternalApi out.SSLCertificate = in.SSLCertificate + out.SSLPolicy = in.SSLPolicy 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 8c815e8899caf..89c48b5b3d039 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go @@ -3163,6 +3163,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.SSLPolicy != nil { + in, out := &in.SSLPolicy, &out.SSLPolicy + *out = new(string) + **out = **in + } if in.CrossZoneLoadBalancing != nil { in, out := &in.CrossZoneLoadBalancing, &out.CrossZoneLoadBalancing *out = new(bool) diff --git a/pkg/apis/kops/validation/aws.go b/pkg/apis/kops/validation/aws.go index f18f17a617f3a..82837412055f5 100644 --- a/pkg/apis/kops/validation/aws.go +++ b/pkg/apis/kops/validation/aws.go @@ -34,6 +34,7 @@ func awsValidateCluster(c *kops.Cluster) field.ErrorList { if c.Spec.API != nil { if c.Spec.API.LoadBalancer != nil { allErrs = append(allErrs, awsValidateAdditionalSecurityGroups(field.NewPath("spec", "api", "loadBalancer", "additionalSecurityGroups"), c.Spec.API.LoadBalancer.AdditionalSecurityGroups)...) + allErrs = append(allErrs, awsValidateSSLPolicy(field.NewPath("spec", "api", "loadBalancer", "sslPolicy"), c.Spec.API.LoadBalancer)...) } } @@ -142,3 +143,18 @@ func awsValidateMixedInstancesPolicy(path *field.Path, spec *kops.MixedInstances return errs } + +func awsValidateSSLPolicy(fieldPath *field.Path, spec *kops.LoadBalancerAccessSpec) field.ErrorList { + allErrs := field.ErrorList{} + + if spec.SSLPolicy != nil { + if spec.Class != kops.LoadBalancerClassNetwork { + allErrs = append(allErrs, field.Forbidden(fieldPath, "sslPolicy should be specified with Network Load Balancer")) + } + if spec.SSLCertificate == "" { + allErrs = append(allErrs, field.Forbidden(fieldPath, "sslPolicy should not be specified without SSLCertificate")) + } + } + + return allErrs +} diff --git a/pkg/apis/kops/zz_generated.deepcopy.go b/pkg/apis/kops/zz_generated.deepcopy.go index 2b6c2130f85b8..f721a3fd9fafd 100644 --- a/pkg/apis/kops/zz_generated.deepcopy.go +++ b/pkg/apis/kops/zz_generated.deepcopy.go @@ -3361,6 +3361,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.SSLPolicy != nil { + in, out := &in.SSLPolicy, &out.SSLPolicy + *out = new(string) + **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 5b27c213b87d0..2820d788f3f7a 100644 --- a/pkg/model/awsmodel/api_loadbalancer.go +++ b/pkg/model/awsmodel/api_loadbalancer.go @@ -119,11 +119,16 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error { if lbSpec.SSLCertificate != "" { listeners["443"].SSLCertificateID = lbSpec.SSLCertificate nlbListeners[0].Port = 8443 - nlbListeners = append(nlbListeners, &awstasks.NetworkLoadBalancerListener{ + + nlbListener := &awstasks.NetworkLoadBalancerListener{ Port: 443, TargetGroupName: b.NLBTargetGroupName("tls"), SSLCertificateID: lbSpec.SSLCertificate, - }) + } + if lbSpec.SSLPolicy != nil { + nlbListener.SSLPolicy = *lbSpec.SSLPolicy + } + nlbListeners = append(nlbListeners, nlbListener) } if lbSpec.SecurityGroupOverride != nil { diff --git a/tests/integration/update_cluster/complex/cloudformation.json b/tests/integration/update_cluster/complex/cloudformation.json index fec163264daf5..fb81f31e88554 100644 --- a/tests/integration/update_cluster/complex/cloudformation.json +++ b/tests/integration/update_cluster/complex/cloudformation.json @@ -1192,7 +1192,8 @@ "Ref": "AWSElasticLoadBalancingV2LoadBalancerapicomplexexamplecom" }, "Port": 443, - "Protocol": "TLS" + "Protocol": "TLS", + "SslPolicy": "ELBSecurityPolicy-2016-08" } }, "AWSElasticLoadBalancingV2Listenerapicomplexexamplecom8443": { diff --git a/tests/integration/update_cluster/complex/in-legacy-v1alpha2.yaml b/tests/integration/update_cluster/complex/in-legacy-v1alpha2.yaml index 1d5d49c890393..a4fb75d1cab0a 100644 --- a/tests/integration/update_cluster/complex/in-legacy-v1alpha2.yaml +++ b/tests/integration/update_cluster/complex/in-legacy-v1alpha2.yaml @@ -13,6 +13,7 @@ spec: crossZoneLoadBalancing: true class: Network sslCertificate: arn:aws:acm:us-test-1:000000000000:certificate/123456789012-1234-1234-1234-12345678 + sslPolicy: ELBSecurityPolicy-2016-08 kubernetesApiAccess: - 1.1.1.0/24 - 2001:0:8500::/40 diff --git a/tests/integration/update_cluster/complex/in-v1alpha2.yaml b/tests/integration/update_cluster/complex/in-v1alpha2.yaml index 12ac5e92333bb..79be9361eda43 100644 --- a/tests/integration/update_cluster/complex/in-v1alpha2.yaml +++ b/tests/integration/update_cluster/complex/in-v1alpha2.yaml @@ -13,6 +13,7 @@ spec: crossZoneLoadBalancing: true class: Network sslCertificate: arn:aws:acm:us-test-1:000000000000:certificate/123456789012-1234-1234-1234-12345678 + sslPolicy: ELBSecurityPolicy-2016-08 kubernetesApiAccess: - 1.1.1.0/24 - 2001:0:8500::/40 diff --git a/tests/integration/update_cluster/complex/kubernetes.tf b/tests/integration/update_cluster/complex/kubernetes.tf index 804ad1fe2eca8..8a89f92ba4dc3 100644 --- a/tests/integration/update_cluster/complex/kubernetes.tf +++ b/tests/integration/update_cluster/complex/kubernetes.tf @@ -431,6 +431,7 @@ resource "aws_lb_listener" "api-complex-example-com-443" { load_balancer_arn = aws_lb.api-complex-example-com.id port = 443 protocol = "TLS" + ssl_policy = "ELBSecurityPolicy-2016-08" } resource "aws_lb_listener" "api-complex-example-com-8443" { diff --git a/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go b/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go index 1f26612e59d18..a6d4cd8839ccf 100644 --- a/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go +++ b/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go @@ -80,6 +80,7 @@ type NetworkLoadBalancerListener struct { Port int TargetGroupName string SSLCertificateID string + SSLPolicy string } func (e *NetworkLoadBalancerListener) mapToAWS(targetGroups []*TargetGroup, loadBalancerArn string) (*elbv2.CreateListenerInput, error) { @@ -110,6 +111,9 @@ func (e *NetworkLoadBalancerListener) mapToAWS(targetGroups []*TargetGroup, load CertificateArn: aws.String(e.SSLCertificateID), }) l.Protocol = aws.String(elbv2.ProtocolEnumTls) + if e.SSLPolicy != "" { + l.SslPolicy = aws.String(e.SSLPolicy) + } } else { l.Protocol = aws.String(elbv2.ProtocolEnumTcp) } @@ -369,6 +373,9 @@ func (e *NetworkLoadBalancer) Find(c *fi.Context) (*NetworkLoadBalancer, error) actualListener.Port = int(aws.Int64Value(l.Port)) if len(l.Certificates) != 0 { actualListener.SSLCertificateID = aws.StringValue(l.Certificates[0].CertificateArn) // What if there is more then one certificate, can we just grab the default certificate? we don't set it as default, we only set the one. + if l.SslPolicy != nil { + actualListener.SSLPolicy = aws.StringValue(l.SslPolicy) + } } // This will need to be rearranged when we recognized multiple listeners and target groups per NLB @@ -689,6 +696,7 @@ type terraformNetworkLoadBalancerListener struct { Port int64 `json:"port" cty:"port"` Protocol string `json:"protocol" cty:"protocol"` CertificateARN *string `json:"certificate_arn,omitempty" cty:"certificate_arn"` + SSLPolicy *string `json:"ssl_policy,omitempty" cty:"ssl_policy"` DefaultAction []terraformNetworkLoadBalancerListenerAction `json:"default_action" cty:"default_action"` } @@ -730,6 +738,9 @@ func (_ *NetworkLoadBalancer) RenderTerraform(t *terraform.TerraformTarget, a, e if listener.SSLCertificateID != "" { listenerTF.CertificateARN = &listener.SSLCertificateID listenerTF.Protocol = elbv2.ProtocolEnumTls + if listener.SSLPolicy != "" { + listenerTF.SSLPolicy = &listener.SSLPolicy + } } else { listenerTF.Protocol = elbv2.ProtocolEnumTcp } @@ -764,6 +775,7 @@ type cloudformationNetworkLoadBalancerListener struct { LoadBalancerARN *cloudformation.Literal `json:"LoadBalancerArn"` Port int64 `json:"Port"` Protocol string `json:"Protocol"` + SSLPolicy *string `json:"SslPolicy,omitempty"` } type cloudformationNetworkLoadBalancerListenerCertificate struct { @@ -811,6 +823,9 @@ func (_ *NetworkLoadBalancer) RenderCloudformation(t *cloudformation.Cloudformat {CertificateArn: listener.SSLCertificateID}, } listenerCF.Protocol = elbv2.ProtocolEnumTls + if listener.SSLPolicy != "" { + listenerCF.SSLPolicy = &listener.SSLPolicy + } } else { listenerCF.Protocol = elbv2.ProtocolEnumTcp }