From d477e96c3862e8224e81a47e10cc9d533fa3d76d Mon Sep 17 00:00:00 2001 From: Raffaele Di Fazio Date: Fri, 6 Jul 2018 09:29:54 +0200 Subject: [PATCH] Added initial implementation of ACM cert for Kubernetes API ELB --- cmd/kops/create.go | 1 + cmd/kops/create_cluster.go | 8 +++++ pkg/apis/kops/cluster.go | 1 + pkg/apis/kops/v1alpha1/cluster.go | 1 + .../kops/v1alpha1/zz_generated.conversion.go | 2 ++ pkg/apis/kops/v1alpha2/cluster.go | 1 + .../kops/v1alpha2/zz_generated.conversion.go | 2 ++ pkg/client/simple/vfsclientset/commonvfs.go | 2 +- pkg/kubeconfig/create_kubecfg.go | 3 +- pkg/model/awsmodel/api_loadbalancer.go | 14 +++++--- pkg/validation/validate_cluster.go | 1 - upup/pkg/fi/cloudup/awstasks/load_balancer.go | 34 ++++++------------- 12 files changed, 39 insertions(+), 31 deletions(-) diff --git a/cmd/kops/create.go b/cmd/kops/create.go index 991c9dd0ba..74e67c2163 100644 --- a/cmd/kops/create.go +++ b/cmd/kops/create.go @@ -158,6 +158,7 @@ func RunCreate(f *util.Factory, out io.Writer, c *CreateOptions) error { case *kopsapi.Cluster: // Adding a PerformAssignments() call here as the user might be trying to use // the new `-f` feature, with an old cluster definition. + err = cloudup.PerformAssignments(v) if err != nil { return fmt.Errorf("error populating configuration: %v", err) diff --git a/cmd/kops/create_cluster.go b/cmd/kops/create_cluster.go index 187376700c..489e8ec1cb 100644 --- a/cmd/kops/create_cluster.go +++ b/cmd/kops/create_cluster.go @@ -123,6 +123,9 @@ type CreateClusterOptions struct { // Specify API loadbalancer as public or internal APILoadBalancerType string + // Specify the SSL certificate to use for the API loadbalancer. Currently only supported in AWS. + APISSLCertificate string + // Allow custom public master name MasterPublicName string @@ -327,6 +330,7 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command { cmd.Flags().StringVar(&options.NodeTenancy, "node-tenancy", options.NodeTenancy, "The tenancy of the node group on AWS. Can be either default or dedicated.") cmd.Flags().StringVar(&options.APILoadBalancerType, "api-loadbalancer-type", options.APILoadBalancerType, "Sets the API loadbalancer type to either 'public' or 'internal'") + cmd.Flags().StringVar(&options.APISSLCertificate, "api-ssl-certificate", options.APISSLCertificate, "Sets the SSL Certificate to use for the API server loadbalancer. Currently only supported in AWS.") // Allow custom public master name cmd.Flags().StringVar(&options.MasterPublicName, "master-public-name", options.MasterPublicName, "Sets the public master public name") @@ -1052,6 +1056,10 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e } } + if c.APISSLCertificate != "" { + cluster.Spec.API.LoadBalancer.SSLCertificate = c.APISSLCertificate + } + // Use Strict IAM policy and allow AWS ECR by default when creating a new cluster cluster.Spec.IAM = &api.IAMSpec{ AllowContainerRegistry: true, diff --git a/pkg/apis/kops/cluster.go b/pkg/apis/kops/cluster.go index 102f9f082e..2e4f999841 100644 --- a/pkg/apis/kops/cluster.go +++ b/pkg/apis/kops/cluster.go @@ -282,6 +282,7 @@ type LoadBalancerAccessSpec struct { Type LoadBalancerType `json:"type,omitempty"` IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"` AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"` + SSLCertificate string `json:"sslCertificate,omitempty"` } // KubeDNSConfig defines the kube dns configuration diff --git a/pkg/apis/kops/v1alpha1/cluster.go b/pkg/apis/kops/v1alpha1/cluster.go index b279132490..db8a50276f 100644 --- a/pkg/apis/kops/v1alpha1/cluster.go +++ b/pkg/apis/kops/v1alpha1/cluster.go @@ -281,6 +281,7 @@ type LoadBalancerAccessSpec struct { Type LoadBalancerType `json:"type,omitempty"` IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"` AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"` + SSLCertificate string `json:"sslCertificate,omitempty"` } // KubeDNSConfig defines the kube dns configuration diff --git a/pkg/apis/kops/v1alpha1/zz_generated.conversion.go b/pkg/apis/kops/v1alpha1/zz_generated.conversion.go index 2ef6b4b0a4..29a9c2cafe 100644 --- a/pkg/apis/kops/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/kops/v1alpha1/zz_generated.conversion.go @@ -2462,6 +2462,7 @@ func autoConvert_v1alpha1_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec( out.Type = kops.LoadBalancerType(in.Type) out.IdleTimeoutSeconds = in.IdleTimeoutSeconds out.AdditionalSecurityGroups = in.AdditionalSecurityGroups + out.SSLCertificate = in.SSLCertificate return nil } @@ -2474,6 +2475,7 @@ func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha1_LoadBalancerAccessSpec( out.Type = LoadBalancerType(in.Type) out.IdleTimeoutSeconds = in.IdleTimeoutSeconds out.AdditionalSecurityGroups = in.AdditionalSecurityGroups + out.SSLCertificate = in.SSLCertificate return nil } diff --git a/pkg/apis/kops/v1alpha2/cluster.go b/pkg/apis/kops/v1alpha2/cluster.go index d4335696cd..b8bdea1153 100644 --- a/pkg/apis/kops/v1alpha2/cluster.go +++ b/pkg/apis/kops/v1alpha2/cluster.go @@ -282,6 +282,7 @@ type LoadBalancerAccessSpec struct { Type LoadBalancerType `json:"type,omitempty"` IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"` AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"` + SSLCertificate string `json:"sslCertificate,omitempty"` } type KubeDNSConfig struct { diff --git a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go index 401b9407ff..757b025b3e 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go @@ -2726,6 +2726,7 @@ func autoConvert_v1alpha2_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec( out.Type = kops.LoadBalancerType(in.Type) out.IdleTimeoutSeconds = in.IdleTimeoutSeconds out.AdditionalSecurityGroups = in.AdditionalSecurityGroups + out.SSLCertificate = in.SSLCertificate return nil } @@ -2738,6 +2739,7 @@ func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha2_LoadBalancerAccessSpec( out.Type = LoadBalancerType(in.Type) out.IdleTimeoutSeconds = in.IdleTimeoutSeconds out.AdditionalSecurityGroups = in.AdditionalSecurityGroups + out.SSLCertificate = in.SSLCertificate return nil } diff --git a/pkg/client/simple/vfsclientset/commonvfs.go b/pkg/client/simple/vfsclientset/commonvfs.go index 2a9bd13854..11d0e491d2 100644 --- a/pkg/client/simple/vfsclientset/commonvfs.go +++ b/pkg/client/simple/vfsclientset/commonvfs.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kops/pkg/acls" - kops "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/kopscodecs" "k8s.io/kops/util/pkg/vfs" diff --git a/pkg/kubeconfig/create_kubecfg.go b/pkg/kubeconfig/create_kubecfg.go index f0fe516562..4efc167f42 100644 --- a/pkg/kubeconfig/create_kubecfg.go +++ b/pkg/kubeconfig/create_kubecfg.go @@ -85,7 +85,8 @@ func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.Se b.Context = clusterName - { + // add the CA Cert to the kubeconfig only if we didn't specify a SSL cert for the LB + if cluster.Spec.API.LoadBalancer.SSLCertificate == "" { cert, _, _, err := keyStore.FindKeypair(fi.CertificateId_CA) if err != nil { return nil, fmt.Errorf("error fetching CA keypair: %v", err) diff --git a/pkg/model/awsmodel/api_loadbalancer.go b/pkg/model/awsmodel/api_loadbalancer.go index f1cbe87873..440120d704 100644 --- a/pkg/model/awsmodel/api_loadbalancer.go +++ b/pkg/model/awsmodel/api_loadbalancer.go @@ -102,6 +102,14 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error { idleTimeout = time.Second * time.Duration(*lbSpec.IdleTimeoutSeconds) } + listeners := map[string]*awstasks.LoadBalancerListener{ + "443": {InstancePort: 443}, + } + + if lbSpec.SSLCertificate != "" { + listeners["443"] = &awstasks.LoadBalancerListener{InstancePort: 443, SSLCertificateID: lbSpec.SSLCertificate} + } + elb = &awstasks.LoadBalancer{ Name: s("api." + b.ClusterName()), Lifecycle: b.Lifecycle, @@ -110,10 +118,8 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error { SecurityGroups: []*awstasks.SecurityGroup{ b.LinkToELBSecurityGroup("api"), }, - Subnets: elbSubnets, - Listeners: map[string]*awstasks.LoadBalancerListener{ - "443": {InstancePort: 443}, - }, + Subnets: elbSubnets, + Listeners: listeners, // Configure fast-recovery health-checks HealthCheck: &awstasks.LoadBalancerHealthCheck{ diff --git a/pkg/validation/validate_cluster.go b/pkg/validation/validate_cluster.go index 659c31440f..a7c9cd09a1 100644 --- a/pkg/validation/validate_cluster.go +++ b/pkg/validation/validate_cluster.go @@ -71,7 +71,6 @@ func hasPlaceHolderIP(clusterName string) (bool, error) { if err != nil { return true, fmt.Errorf("unable to parse Kubernetes cluster API URL: %v", err) } - hostAddrs, err := net.LookupHost(apiAddr.Hostname()) if err != nil { return true, fmt.Errorf("unable to resolve Kubernetes cluster API URL dns: %v", err) diff --git a/upup/pkg/fi/cloudup/awstasks/load_balancer.go b/upup/pkg/fi/cloudup/awstasks/load_balancer.go index c6dcedbbab..fbff40f546 100644 --- a/upup/pkg/fi/cloudup/awstasks/load_balancer.go +++ b/upup/pkg/fi/cloudup/awstasks/load_balancer.go @@ -58,12 +58,12 @@ type LoadBalancer struct { Scheme *string - HealthCheck *LoadBalancerHealthCheck - AccessLog *LoadBalancerAccessLog - //AdditionalAttributes []*LoadBalancerAdditionalAttribute + HealthCheck *LoadBalancerHealthCheck + AccessLog *LoadBalancerAccessLog ConnectionDraining *LoadBalancerConnectionDraining ConnectionSettings *LoadBalancerConnectionSettings CrossZoneLoadBalancing *LoadBalancerCrossZoneLoadBalancing + SSLCertificateID string } var _ fi.CompareWithID = &LoadBalancer{} @@ -73,17 +73,17 @@ func (e *LoadBalancer) CompareWithID() *string { } type LoadBalancerListener struct { - InstancePort int + InstancePort int + SSLCertificateID string } func (e *LoadBalancerListener) mapToAWS(loadBalancerPort int64) *elb.Listener { return &elb.Listener{ LoadBalancerPort: aws.Int64(loadBalancerPort), - - Protocol: aws.String("TCP"), - - InstanceProtocol: aws.String("TCP"), + Protocol: aws.String("SSL"), + InstanceProtocol: aws.String("SSL"), InstancePort: aws.Int64(int64(e.InstancePort)), + SSLCertificateId: aws.String(e.SSLCertificateID), } } @@ -334,16 +334,6 @@ func (e *LoadBalancer) Find(c *fi.Context) (*LoadBalancer, error) { actual.AccessLog.S3BucketPrefix = lbAttributes.AccessLog.S3BucketPrefix } - // We don't map AdditionalAttributes - yet - //var additionalAttributes []*LoadBalancerAdditionalAttribute - //for index, additionalAttribute := range lbAttributes.AdditionalAttributes { - // additionalAttributes[index] = &LoadBalancerAdditionalAttribute{ - // Key: additionalAttribute.Key, - // Value: additionalAttribute.Value, - // } - //} - //actual.AdditionalAttributes = additionalAttributes - actual.ConnectionDraining = &LoadBalancerConnectionDraining{} if lbAttributes.ConnectionDraining.Enabled != nil { actual.ConnectionDraining.Enabled = lbAttributes.ConnectionDraining.Enabled @@ -381,7 +371,7 @@ func (e *LoadBalancer) Find(c *fi.Context) (*LoadBalancer, error) { // 1. We don't want to force a rename of the ELB, because that is a destructive operation // 2. We were creating ELBs with insufficiently qualified names previously if fi.StringValue(e.LoadBalancerName) != fi.StringValue(actual.LoadBalancerName) { - glog.V(2).Infof("Resuing existing load balancer with name: %q", actual.LoadBalancerName) + glog.V(2).Infof("Reusing existing load balancer with name: %q", actual.LoadBalancerName) e.LoadBalancerName = actual.LoadBalancerName } @@ -453,11 +443,7 @@ func (s *LoadBalancer) CheckChanges(a, e, changes *LoadBalancer) error { return fi.RequiredField("ConnectionDraining.Enabled") } } - //if e.ConnectionSettings != nil { - // if e.ConnectionSettings.IdleTimeout == nil { - // return fi.RequiredField("ConnectionSettings.IdleTimeout") - // } - //} + if e.CrossZoneLoadBalancing != nil { if e.CrossZoneLoadBalancing.Enabled == nil { return fi.RequiredField("CrossZoneLoadBalancing.Enabled")