diff --git a/docs/guide/service/annotations.md b/docs/guide/service/annotations.md index e9c494288..df6170963 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -51,6 +51,45 @@ | [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-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`. | +| Name | Type | Default | Notes | +|--------------------------------------------------------------------------------------------------|-------------------------|---------------------------|--------------------------------------------------------| +| [service.beta.kubernetes.io/load-balancer-source-ranges](#lb-source-ranges) | stringList | | | +| [service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection](#shield-advanced-protection) | boolean | false | | +| [service.beta.kubernetes.io/aws-load-balancer-security-group-prefix-lists](#lb-security-group-prefix-lists) | stringList | | | +| [service.beta.kubernetes.io/aws-load-balancer-type](#lb-type) | string | | | +| [service.beta.kubernetes.io/aws-load-balancer-nlb-target-type](#nlb-target-type) | string | | default `instance` in case of LoadBalancerClass | +| [service.beta.kubernetes.io/aws-load-balancer-name](#load-balancer-name) | string | | | +| [service.beta.kubernetes.io/aws-load-balancer-internal](#lb-internal) | boolean | false | deprecated, in favor of [aws-load-balancer-scheme](#lb-scheme)| +| [service.beta.kubernetes.io/aws-load-balancer-scheme](#lb-scheme) | string | internal | | +| [service.beta.kubernetes.io/aws-load-balancer-proxy-protocol](#proxy-protocol-v2) | string | | Set to `"*"` to enable | +| [service.beta.kubernetes.io/aws-load-balancer-ip-address-type](#ip-address-type) | string | ipv4 | ipv4 \| dualstack | +| [service.beta.kubernetes.io/aws-load-balancer-access-log-enabled](#deprecated-attributes) | boolean | false | deprecated, in favor of [aws-load-balancer-attributes](#load-balancer-attributes)| +| [service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name](#deprecated-attributes) | string | | deprecated, in favor of [aws-load-balancer-attributes](#load-balancer-attributes)| +| [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-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 | | | +| [service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags](#additional-resource-tags) | stringMap | | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol](#healthcheck-protocol) | string | TCP | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-port ](#healthcheck-port) | integer \| traffic-port | traffic-port | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-path](#healthcheck-path) | string | "/" for HTTP(S) protocols | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold](#healthcheck-healthy-threshold) | integer | 3 | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold](#healthcheck-unhealthy-threshold) | integer | 3 | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout](#healthcheck-timeout) | integer | 10 | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval](#healthcheck-interval) | integer | 10 | | +| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-success-codes](#healthcheck-success-codes) | string | 200-399 | | +| [service.beta.kubernetes.io/aws-load-balancer-eip-allocations](#eip-allocations) | stringList | | internet-facing lb only. Length must match the number of subnets| +| [service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses](#private-ipv4-addresses) | stringList | | internal lb only. Length must match the number of subnets | +| [service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses](#ipv6-addresses) | stringList | | dualstack lb only. Length must match the number of subnets | +| [service.beta.kubernetes.io/aws-load-balancer-target-group-attributes](#target-group-attributes) | stringMap | | | +| [service.beta.kubernetes.io/aws-load-balancer-subnets](#subnets) | stringList | | | +| [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-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-listener-attributes.${Protocol}-${Port}](#listener-attributes) | stringMap | | | [service.beta.kubernetes.io/aws-load-balancer-multi-cluster-target-group](#multi-cluster-target-group) | boolean | false | If specified, the controller will only operate on targets that exist within the cluster, ignoring targets from other sources. | @@ -580,6 +619,20 @@ Load balancer access can be controlled via following annotations: ``` +- `service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection` turns on / off the AWS Shield Advanced protection for the network load balancer. + + !!!note "" + When this annotation is absent, the controller will keep LoadBalancer shield protection settings unchanged. + To disable shield protection, explicitly set the annotation value to 'false'. + + !!!example + - enable shield protection + ```service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection: 'true' + ``` + - disable shield protection + ```service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection: 'false' + ``` + ## Legacy Cloud Provider The AWS Load Balancer Controller manages Kubernetes Services in a compatible way with the AWS cloud provider's legacy service controller. diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index 3bad8a76b..49424f231 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -61,6 +61,7 @@ const ( // NLB annotation suffixes // prefixes service.beta.kubernetes.io, service.kubernetes.io SvcLBSuffixSourceRanges = "load-balancer-source-ranges" + SvcLBSuffixShieldAdvancedProtection = "aws-load-balancer-nlb-shield-advanced-protection" SvcLBSuffixLoadBalancerType = "aws-load-balancer-type" SvcLBSuffixTargetType = "aws-load-balancer-nlb-target-type" SvcLBSuffixLoadBalancerName = "aws-load-balancer-name" diff --git a/pkg/service/model_build_load_balancer_addons.go b/pkg/service/model_build_load_balancer_addons.go new file mode 100644 index 000000000..a8cc0eba5 --- /dev/null +++ b/pkg/service/model_build_load_balancer_addons.go @@ -0,0 +1,37 @@ +package service + +import ( + "context" + + "sigs.k8s.io/aws-load-balancer-controller/pkg/annotations" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + shieldmodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/shield" +) + +func (t *defaultModelBuildTask) buildLoadBalancerAddOns(ctx context.Context, lbARN core.StringToken) error { + if _, err := t.buildShieldProtection(ctx, lbARN); err != nil { + return err + } + return nil +} + +func (t *defaultModelBuildTask) buildShieldProtection(_ context.Context, lbARN core.StringToken) (*shieldmodel.Protection, error) { + explicitEnableProtections := make(map[bool]struct{}) + rawEnableProtection := false + exists, err := t.annotationParser.ParseBoolAnnotation(annotations.SvcLBSuffixShieldAdvancedProtection, &rawEnableProtection, t.service.Annotations) + if err != nil { + return nil, err + } + if exists { + explicitEnableProtections[rawEnableProtection] = struct{}{} + } + if len(explicitEnableProtections) == 0 { + return nil, nil + } + _, enableProtection := explicitEnableProtections[true] + protection := shieldmodel.NewProtection(t.stack, resourceIDLoadBalancer, shieldmodel.ProtectionSpec{ + Enabled: enableProtection, + ResourceARN: lbARN, + }) + return protection, nil +} diff --git a/pkg/service/model_build_load_balancer_addons_test.go b/pkg/service/model_build_load_balancer_addons_test.go new file mode 100644 index 000000000..5b2ea8815 --- /dev/null +++ b/pkg/service/model_build_load_balancer_addons_test.go @@ -0,0 +1,114 @@ +package service + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/aws-load-balancer-controller/pkg/annotations" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + shieldmodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/shield" +) + +func Test_defaultModelBuildTask_buildShieldProtection(t *testing.T) { + type args struct { + lbARN core.StringToken + } + tests := []struct { + testName string + svc *corev1.Service + args args + want *shieldmodel.Protection + wantError bool + }{ + { + testName: "when shield-advanced-protection annotation is not specified", + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + }, + args: args{ + lbARN: core.LiteralStringToken("awesome-lb-arn"), + }, + want: nil, + wantError: false, + }, + { + testName: "when shield-advanced-protection annotation set to true", + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection": "true", + }, + }, + }, + args: args{ + lbARN: core.LiteralStringToken("awesome-lb-arn"), + }, + want: &shieldmodel.Protection{ + Spec: shieldmodel.ProtectionSpec{ + Enabled: true, + ResourceARN: core.LiteralStringToken("awesome-lb-arn"), + }, + }, + wantError: false, + }, + { + testName: "when shield-advanced-protection annotation set to false", + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection": "false", + }, + }, + }, + args: args{ + lbARN: core.LiteralStringToken("awesome-lb-arn"), + }, + want: &shieldmodel.Protection{ + Spec: shieldmodel.ProtectionSpec{ + Enabled: false, + ResourceARN: core.LiteralStringToken("awesome-lb-arn"), + }, + }, + wantError: false, + }, + { + testName: "when shield-advanced-protection annotation has non boolean value", + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-nlb-shield-advanced-protection": "FalSe1", + }, + }, + }, + args: args{ + lbARN: core.LiteralStringToken("awesome-lb-arn"), + }, + wantError: true, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + stack := core.NewDefaultStack(core.StackID{Name: "awesome-stack"}) + annotationParser := annotations.NewSuffixAnnotationParser("service.beta.kubernetes.io") + task := &defaultModelBuildTask{ + service: tt.svc, + annotationParser: annotationParser, + stack: stack, + } + got, err := task.buildShieldProtection(context.Background(), tt.args.lbARN) + if tt.wantError { + assert.Error(t, err) + } else { + opts := cmpopts.IgnoreTypes(core.ResourceMeta{}) + assert.True(t, cmp.Equal(tt.want, got, opts), "diff", cmp.Diff(tt.want, got, opts)) + } + }) + } +} diff --git a/pkg/service/model_builder.go b/pkg/service/model_builder.go index 9bd68c8cb..2b16a1ba8 100644 --- a/pkg/service/model_builder.go +++ b/pkg/service/model_builder.go @@ -2,10 +2,11 @@ package service import ( "context" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "strconv" "sync" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/go-logr/logr" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -249,6 +250,9 @@ func (t *defaultModelBuildTask) buildModel(ctx context.Context) error { if err != nil { return err } + if err := t.buildLoadBalancerAddOns(ctx, t.loadBalancer.LoadBalancerARN()); err != nil { + return err + } return nil }