diff --git a/controllers/service/service_controller.go b/controllers/service/service_controller.go index 2ed7612b01..681f28128c 100644 --- a/controllers/service/service_controller.go +++ b/controllers/service/service_controller.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy" elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" + "sigs.k8s.io/aws-load-balancer-controller/pkg/ingress" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" @@ -43,10 +44,11 @@ func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorde annotationParser := annotations.NewSuffixAnnotationParser(serviceAnnotationPrefix) trackingProvider := tracking.NewDefaultProvider(serviceTagPrefix, controllerConfig.ClusterName) serviceUtils := service.NewServiceUtils(annotationParser, serviceFinalizer, controllerConfig.ServiceConfig.LoadBalancerClass, controllerConfig.FeatureGates) + certDiscovery := ingress.NewACMCertDiscovery(cloud.ACM(), logger) modelBuilder := service.NewDefaultModelBuilder(annotationParser, subnetsResolver, vpcInfoProvider, cloud.VpcID(), trackingProvider, elbv2TaggingManager, cloud.EC2(), controllerConfig.FeatureGates, controllerConfig.ClusterName, controllerConfig.DefaultTags, controllerConfig.ExternalManagedTags, controllerConfig.DefaultSSLPolicy, controllerConfig.DefaultTargetType, controllerConfig.FeatureGates.Enabled(config.EnableIPTargetType), serviceUtils, - backendSGProvider, sgResolver, controllerConfig.EnableBackendSecurityGroup, controllerConfig.DisableRestrictedSGRules, logger) + backendSGProvider, sgResolver, controllerConfig.EnableBackendSecurityGroup, controllerConfig.DisableRestrictedSGRules, certDiscovery, logger) stackMarshaller := deploy.NewDefaultStackMarshaller() stackDeployer := deploy.NewDefaultStackDeployer(cloud, k8sClient, networkingSGManager, networkingSGReconciler, elbv2TaggingManager, controllerConfig, serviceTagPrefix, logger) return &serviceReconciler{ diff --git a/docs/guide/service/annotations.md b/docs/guide/service/annotations.md index f56d0419c8..f3105b7b18 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -11,7 +11,7 @@ ## Annotations !!!warning These annotations are specific to the kubernetes [service resources reconciled](#lb-type) by the AWS Load Balancer Controller. Although the list was initially derived from the k8s in-tree `kube-controller-manager`, this - documentation is not an accurate reference for the services reconciled by the in-tree controller. + documentation is not an accurate reference for the services reconciled by the in-tree controller. | Name | Type | Default | Notes | |--------------------------------------------------------------------------------------------------|-------------------------|---------------------------|--------------------------------------------------------| @@ -29,6 +29,7 @@ | [service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix](#deprecated-attributes)| string | | deprecated, in favor of [aws-load-balancer-attributes](#load-balancer-attributes)| | [service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled](#deprecated-attributes)| boolean | false | deprecated, in favor of [aws-load-balancer-attributes](#load-balancer-attributes)| | [service.beta.kubernetes.io/aws-load-balancer-ssl-cert](#ssl-cert) | stringList | | | +| [service.beta.kubernetes.io/aws-load-balancer-ssl-domains](#ssl-domains) | stringList | | | | [service.beta.kubernetes.io/aws-load-balancer-ssl-ports](#ssl-ports) | stringList | | | | [service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy](#ssl-negotiation-policy) | string | ELBSecurityPolicy-2016-08 | | | [service.beta.kubernetes.io/aws-load-balancer-backend-protocol](#backend-protocol) | string | | | @@ -49,9 +50,9 @@ | [service.beta.kubernetes.io/aws-load-balancer-alpn-policy](#alpn-policy) | string | | | | [service.beta.kubernetes.io/aws-load-balancer-target-node-labels](#target-node-labels) | stringMap | | | | [service.beta.kubernetes.io/aws-load-balancer-attributes](#load-balancer-attributes) | stringMap | | | -| [service.beta.kubernetes.io/aws-load-balancer-security-groups](#security-groups) | stringList | | | +| [service.beta.kubernetes.io/aws-load-balancer-security-groups](#security-groups) | stringList | | | | [service.beta.kubernetes.io/aws-load-balancer-manage-backend-security-group-rules](#manage-backend-sg-rules) | boolean | true | If `service.beta.kubernetes.io/aws-load-balancer-security-groups` is specified, this must also be explicitly specified otherwise it defaults to `false`. | -| [service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic](#update-security-settings) | string | | +| [service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic](#update-security-settings) | string | | ## Traffic Routing Traffic Routing can be controlled with following annotations: @@ -70,7 +71,7 @@ Traffic Routing can be controlled with following annotations: !!!tip This annotation specifies the controller used to provision LoadBalancers (as specified in [legacy-cloud-provider](#legacy-cloud-provider)). Refer to [lb-scheme](#lb-scheme) to specify whether the LoadBalancer is internet-facing or internal. - + !!!note "" - [Deprecated] For type `nlb-ip`, the controller will provision an NLB with targets registered by IP address. This value is supported for backwards compatibility. - For type `external`, the NLB target type depends on the [nlb-target-type](#nlb-target-type) annotation. @@ -242,11 +243,11 @@ for proxy protocol v2 configuration. !!!warning "" Only attributes defined in the annotation will be updated. To unset any AWS defaults(e.g. Disabling access logs after having them enabled once), the values need to be explicitly set to the original values(`access_logs.s3.enabled=false`) and omitting them is not sufficient. Custom attributes set in this annotation's config map will be overriden by annotation-specific attributes. For backwards compatibility, existing annotations for the individual load balancer attributes get precedence in case of ties. - + !!!note "" - If `deletion_protection.enabled=true` is in the annotation, the controller will not be able to delete the NLB during reconciliation. Once the attribute gets edited to `deletion_protection.enabled=false` during reconciliation, the deployer will force delete the resource. - Please note, if the deletion protection is not enabled via annotation (e.g. via AWS console), the controller still deletes the underlying resource. - + !!!example - enable access log to s3 ``` @@ -272,7 +273,7 @@ for proxy protocol v2 configuration. service.beta.kubernetes.io/aws-load-balancer-access-log-enabled service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix - service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled + service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled ``` @@ -385,6 +386,17 @@ You can configure TLS support via the following annotations: service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-2:xxxxx:certificate/xxxxxxx ``` +- `service.beta.kubernetes.io/aws-load-balancer-ssl-domains` specifies the domain names for which the controller will automatically discover TLS certificates. + + !!!note "" + The `service.beta.kubernetes.io/aws-load-balancer-ssl-cert` annotation takes precedence + over the `service.beta.kubernetes.io/aws-load-balancer-ssl-domains` annotation. + + !!!example + ``` + service.beta.kubernetes.io/aws-load-balancer-ssl-domains: my-nlb.example.com + ``` + - `service.beta.kubernetes.io/aws-load-balancer-ssl-ports` specifies the frontend ports with TLS listeners. !!!note "" @@ -434,7 +446,7 @@ Load balancer access can be controlled via following annotations: This annotation will be ignored in case preserve client IP is not enabled. - preserve client IP is disabled by default for `IP` targets - preserve client IP is enabled by default for `instance` targets - + !!!warning "" Preserve client IP has no effect on traffic converted from IPv4 to IPv6 and on traffic converted from IPv6 to IPv4. The source IP of this type of traffic is always the private IP address of the Network Load Balancer. - This could cause the clients that have their traffic converted to bypass the specified CIDRs that are allowed to access the NLB. @@ -495,7 +507,7 @@ Load balancer access can be controlled via following annotations: ``` service.beta.kubernetes.io/aws-load-balancer-security-groups: sg-xxxx, nameOfSg1, nameOfSg2 ``` - + - `service.beta.kubernetes.io/aws-load-balancer-manage-backend-security-group-rules` specifies whether the controller should automatically add the ingress rules to the instance/ENI security group. !!!warning "" @@ -506,7 +518,7 @@ Load balancer access can be controlled via following annotations: service.beta.kubernetes.io/aws-load-balancer-manage-backend-security-group-rules: "false" ``` -- `service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic` specifies whether to apply security group rules to traffic sent to the load balancer through AWS PrivateLink. +- `service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic` specifies whether to apply security group rules to traffic sent to the load balancer through AWS PrivateLink. !!!example ``` @@ -517,8 +529,8 @@ Load balancer access can be controlled via following annotations: ## Legacy Cloud Provider The AWS Load Balancer Controller manages Kubernetes Services in a compatible way with the AWS cloud provider's legacy service controller. -- For users on v2.5.0+, The AWS LBC provides a mutating webhook for service resources to set the `spec.loadBalancerCLass` field for Serive of type LoadBalancer, effectively making the AWS LBC the default controller for Service of type LoadBalancer. +- For users on v2.5.0+, The AWS LBC provides a mutating webhook for service resources to set the `spec.loadBalancerCLass` field for Serive of type LoadBalancer, effectively making the AWS LBC the default controller for Service of type LoadBalancer. Users can disable this feature and revert to using the AWS Cloud Controller Manager as the default service controller by setting the helm chart value `enableServiceMutatorWebhook` to false with `--set enableServiceMutatorWebhook=false` . - For users on older versions, the annotation `service.beta.kubernetes.io/aws-load-balancer-type` is used to determine which controller reconciles the service. If the annotation value is `nlb-ip` or `external`, - recent versions of the legacy cloud provider ignore the Service resource so that the AWS LBC can take over. For all other values of the annotation, the legacy cloud provider will handle the service. + recent versions of the legacy cloud provider ignore the Service resource so that the AWS LBC can take over. For all other values of the annotation, the legacy cloud provider will handle the service. Note that this annotation should be specified during service creation and not edited later. Support for the annotation was added to the legacy cloud provider in Kubernetes v1.20, and is backported to v1.18.18+ and v1.19.10+. diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index 493e0427ef..6707137976 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -63,6 +63,7 @@ const ( SvcLBSuffixAccessLogS3BucketName = "aws-load-balancer-access-log-s3-bucket-name" SvcLBSuffixAccessLogS3BucketPrefix = "aws-load-balancer-access-log-s3-bucket-prefix" SvcLBSuffixCrossZoneLoadBalancingEnabled = "aws-load-balancer-cross-zone-load-balancing-enabled" + SvcLBSuffixSSLDomains = "aws-load-balancer-ssl-domains" SvcLBSuffixSSLCertificate = "aws-load-balancer-ssl-cert" SvcLBSuffixSSLPorts = "aws-load-balancer-ssl-ports" SvcLBSuffixSSLNegotiationPolicy = "aws-load-balancer-ssl-negotiation-policy" diff --git a/pkg/ingress/model_builder.go b/pkg/ingress/model_builder.go index ec753247de..003242c716 100644 --- a/pkg/ingress/model_builder.go +++ b/pkg/ingress/model_builder.go @@ -110,7 +110,7 @@ type defaultModelBuilder struct { logger logr.Logger } -// build mode stack for a IngressGroup. +// Build builds mode stack for a IngressGroup. func (b *defaultModelBuilder) Build(ctx context.Context, ingGroup Group) (core.Stack, *elbv2model.LoadBalancer, []types.NamespacedName, bool, error) { stack := core.NewDefaultStack(core.StackID(ingGroup.ID)) task := &defaultModelBuildTask{ diff --git a/pkg/service/model_build_listener.go b/pkg/service/model_build_listener.go index d320fe5eaf..1fe4d238a4 100644 --- a/pkg/service/model_build_listener.go +++ b/pkg/service/model_build_listener.go @@ -108,15 +108,32 @@ func (t *defaultModelBuildTask) buildSSLNegotiationPolicy(_ context.Context) *st return &t.defaultSSLPolicy } -func (t *defaultModelBuildTask) buildListenerCertificates(_ context.Context) []elbv2model.Certificate { +func (t *defaultModelBuildTask) buildListenerCertificates(ctx context.Context) ([]elbv2model.Certificate, error) { var rawCertificateARNs []string - _ = t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSSLCertificate, &rawCertificateARNs, t.service.Annotations) - + var rawSSLDomains []string var certificates []elbv2model.Certificate - for _, cert := range rawCertificateARNs { - certificates = append(certificates, elbv2model.Certificate{CertificateARN: aws.String(cert)}) + + if t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSSLCertificate, &rawCertificateARNs, t.service.Annotations) { + for _, cert := range rawCertificateARNs { + certificates = append(certificates, elbv2model.Certificate{CertificateARN: aws.String(cert)}) + } + return certificates, nil + } + + // auto-discover ACM certs only if the ssl-domains annotation exists ssl-cert annotations is not present + // which means ssl-cert takes precedence over the auto-discovered cert/ss-domains annotation + if t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSSLDomains, &rawSSLDomains, t.service.Annotations) { + autoDiscoveredCertARNs, err := t.certDiscovery.Discover(ctx, rawSSLDomains) + if err != nil { + return nil, err + } + for _, cert := range autoDiscoveredCertARNs { + certificates = append(certificates, elbv2model.Certificate{ + CertificateARN: aws.String(cert), + }) + } } - return certificates + return certificates, nil } func validateTLSPortsSet(rawTLSPorts []string, ports []corev1.ServicePort) error { @@ -192,7 +209,11 @@ type listenerConfig struct { } func (t *defaultModelBuildTask) buildListenerConfig(ctx context.Context) (*listenerConfig, error) { - certificates := t.buildListenerCertificates(ctx) + certificates, err := t.buildListenerCertificates(ctx) + if err != nil { + return nil, err + } + tlsPortsSet, err := t.buildTLSPortsSet(ctx) if err != nil { return nil, err diff --git a/pkg/service/model_builder.go b/pkg/service/model_builder.go index 961254209b..aea06077a2 100644 --- a/pkg/service/model_builder.go +++ b/pkg/service/model_builder.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/config" elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" + "sigs.k8s.io/aws-load-balancer-controller/pkg/ingress" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" @@ -41,7 +42,7 @@ func NewDefaultModelBuilder(annotationParser annotations.Parser, subnetsResolver elbv2TaggingManager elbv2deploy.TaggingManager, ec2Client services.EC2, featureGates config.FeatureGates, clusterName string, defaultTags map[string]string, externalManagedTags []string, defaultSSLPolicy string, defaultTargetType string, enableIPTargetType bool, serviceUtils ServiceUtils, backendSGProvider networking.BackendSGProvider, sgResolver networking.SecurityGroupResolver, enableBackendSG bool, - disableRestrictedSGRules bool, logger logr.Logger) *defaultModelBuilder { + disableRestrictedSGRules bool, certDiscovery ingress.CertDiscovery, logger logr.Logger) *defaultModelBuilder { return &defaultModelBuilder{ annotationParser: annotationParser, subnetsResolver: subnetsResolver, @@ -50,6 +51,7 @@ func NewDefaultModelBuilder(annotationParser annotations.Parser, subnetsResolver elbv2TaggingManager: elbv2TaggingManager, featureGates: featureGates, serviceUtils: serviceUtils, + certDiscovery: certDiscovery, clusterName: clusterName, vpcID: vpcID, defaultTags: defaultTags, @@ -78,6 +80,7 @@ type defaultModelBuilder struct { elbv2TaggingManager elbv2deploy.TaggingManager featureGates config.FeatureGates serviceUtils ServiceUtils + certDiscovery ingress.CertDiscovery ec2Client services.EC2 enableBackendSG bool disableRestrictedSGRules bool @@ -165,6 +168,7 @@ type defaultModelBuildTask struct { featureGates config.FeatureGates serviceUtils ServiceUtils enableIPTargetType bool + certDiscovery ingress.CertDiscovery ec2Client services.EC2 logger logr.Logger diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 4b598d80a9..1f2358123c 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" + "sigs.k8s.io/aws-load-balancer-controller/pkg/ingress" "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -6459,9 +6460,10 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { } else { enableIPTargetType = *tt.enableIPTargetType } + certDiscovery := ingress.NewMockCertDiscovery(ctrl) builder := NewDefaultModelBuilder(annotationParser, subnetsResolver, vpcInfoProvider, "vpc-xxx", trackingProvider, elbv2TaggingManager, ec2Client, featureGates, "my-cluster", nil, nil, "ELBSecurityPolicy-2016-08", defaultTargetType, enableIPTargetType, serviceUtils, - backendSGProvider, sgResolver, tt.enableBackendSG, tt.disableRestrictedSGRules, logr.New(&log.NullLogSink{})) + backendSGProvider, sgResolver, tt.enableBackendSG, tt.disableRestrictedSGRules, certDiscovery, logr.New(&log.NullLogSink{})) ctx := context.Background() stack, _, _, err := builder.Build(ctx, tt.svc) if tt.wantError {