diff --git a/.go-version b/.go-version index 428abfd24..f124bfa15 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.21.8 +1.21.9 diff --git a/controllers/service/service_controller.go b/controllers/service/service_controller.go index eb6d82381..1ad6227fe 100644 --- a/controllers/service/service_controller.go +++ b/controllers/service/service_controller.go @@ -47,7 +47,7 @@ func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorde 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) + backendSGProvider, sgResolver, controllerConfig.EnableBackendSecurityGroup, controllerConfig.DisableRestrictedSGRules, 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 cd4e1a787..aa01011b7 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -50,6 +50,7 @@ | [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 | | +| [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: @@ -488,6 +489,14 @@ 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. + + !!!example + ``` + service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic: "off" + ``` + + ## 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/main.go b/main.go index 6115ea0b6..88cc1755a 100644 --- a/main.go +++ b/main.go @@ -165,7 +165,7 @@ func main() { corewebhook.NewServiceMutator(controllerCFG.ServiceConfig.LoadBalancerClass, ctrl.Log).SetupWithManager(mgr) elbv2webhook.NewIngressClassParamsValidator().SetupWithManager(mgr) elbv2webhook.NewTargetGroupBindingMutator(cloud.ELBV2(), ctrl.Log).SetupWithManager(mgr) - elbv2webhook.NewTargetGroupBindingValidator(mgr.GetClient(), cloud.ELBV2(), ctrl.Log).SetupWithManager(mgr) + elbv2webhook.NewTargetGroupBindingValidator(mgr.GetClient(), cloud.ELBV2(), cloud.VpcID(), ctrl.Log).SetupWithManager(mgr) networkingwebhook.NewIngressValidator(mgr.GetClient(), controllerCFG.IngressConfig, ctrl.Log).SetupWithManager(mgr) //+kubebuilder:scaffold:builder diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index c3e686e08..5ab585afd 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -50,39 +50,40 @@ const ( // NLB annotation suffixes // prefixes service.beta.kubernetes.io, service.kubernetes.io - SvcLBSuffixSourceRanges = "load-balancer-source-ranges" - SvcLBSuffixLoadBalancerType = "aws-load-balancer-type" - SvcLBSuffixTargetType = "aws-load-balancer-nlb-target-type" - SvcLBSuffixLoadBalancerName = "aws-load-balancer-name" - SvcLBSuffixScheme = "aws-load-balancer-scheme" - SvcLBSuffixInternal = "aws-load-balancer-internal" - SvcLBSuffixProxyProtocol = "aws-load-balancer-proxy-protocol" - SvcLBSuffixIPAddressType = "aws-load-balancer-ip-address-type" - SvcLBSuffixAccessLogEnabled = "aws-load-balancer-access-log-enabled" - 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" - SvcLBSuffixSSLCertificate = "aws-load-balancer-ssl-cert" - SvcLBSuffixSSLPorts = "aws-load-balancer-ssl-ports" - SvcLBSuffixSSLNegotiationPolicy = "aws-load-balancer-ssl-negotiation-policy" - SvcLBSuffixBEProtocol = "aws-load-balancer-backend-protocol" - SvcLBSuffixAdditionalTags = "aws-load-balancer-additional-resource-tags" - SvcLBSuffixHCHealthyThreshold = "aws-load-balancer-healthcheck-healthy-threshold" - SvcLBSuffixHCUnhealthyThreshold = "aws-load-balancer-healthcheck-unhealthy-threshold" - SvcLBSuffixHCTimeout = "aws-load-balancer-healthcheck-timeout" - SvcLBSuffixHCInterval = "aws-load-balancer-healthcheck-interval" - SvcLBSuffixHCProtocol = "aws-load-balancer-healthcheck-protocol" - SvcLBSuffixHCPort = "aws-load-balancer-healthcheck-port" - SvcLBSuffixHCPath = "aws-load-balancer-healthcheck-path" - SvcLBSuffixHCSuccessCodes = "aws-load-balancer-healthcheck-success-codes" - SvcLBSuffixTargetGroupAttributes = "aws-load-balancer-target-group-attributes" - SvcLBSuffixSubnets = "aws-load-balancer-subnets" - SvcLBSuffixEIPAllocations = "aws-load-balancer-eip-allocations" - SvcLBSuffixPrivateIpv4Addresses = "aws-load-balancer-private-ipv4-addresses" - SvcLBSuffixIpv6Addresses = "aws-load-balancer-ipv6-addresses" - SvcLBSuffixALPNPolicy = "aws-load-balancer-alpn-policy" - SvcLBSuffixTargetNodeLabels = "aws-load-balancer-target-node-labels" - SvcLBSuffixLoadBalancerAttributes = "aws-load-balancer-attributes" - SvcLBSuffixLoadBalancerSecurityGroups = "aws-load-balancer-security-groups" - SvcLBSuffixManageSGRules = "aws-load-balancer-manage-backend-security-group-rules" + SvcLBSuffixSourceRanges = "load-balancer-source-ranges" + SvcLBSuffixLoadBalancerType = "aws-load-balancer-type" + SvcLBSuffixTargetType = "aws-load-balancer-nlb-target-type" + SvcLBSuffixLoadBalancerName = "aws-load-balancer-name" + SvcLBSuffixScheme = "aws-load-balancer-scheme" + SvcLBSuffixInternal = "aws-load-balancer-internal" + SvcLBSuffixProxyProtocol = "aws-load-balancer-proxy-protocol" + SvcLBSuffixIPAddressType = "aws-load-balancer-ip-address-type" + SvcLBSuffixAccessLogEnabled = "aws-load-balancer-access-log-enabled" + 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" + SvcLBSuffixSSLCertificate = "aws-load-balancer-ssl-cert" + SvcLBSuffixSSLPorts = "aws-load-balancer-ssl-ports" + SvcLBSuffixSSLNegotiationPolicy = "aws-load-balancer-ssl-negotiation-policy" + SvcLBSuffixBEProtocol = "aws-load-balancer-backend-protocol" + SvcLBSuffixAdditionalTags = "aws-load-balancer-additional-resource-tags" + SvcLBSuffixHCHealthyThreshold = "aws-load-balancer-healthcheck-healthy-threshold" + SvcLBSuffixHCUnhealthyThreshold = "aws-load-balancer-healthcheck-unhealthy-threshold" + SvcLBSuffixHCTimeout = "aws-load-balancer-healthcheck-timeout" + SvcLBSuffixHCInterval = "aws-load-balancer-healthcheck-interval" + SvcLBSuffixHCProtocol = "aws-load-balancer-healthcheck-protocol" + SvcLBSuffixHCPort = "aws-load-balancer-healthcheck-port" + SvcLBSuffixHCPath = "aws-load-balancer-healthcheck-path" + SvcLBSuffixHCSuccessCodes = "aws-load-balancer-healthcheck-success-codes" + SvcLBSuffixTargetGroupAttributes = "aws-load-balancer-target-group-attributes" + SvcLBSuffixSubnets = "aws-load-balancer-subnets" + SvcLBSuffixEIPAllocations = "aws-load-balancer-eip-allocations" + SvcLBSuffixPrivateIpv4Addresses = "aws-load-balancer-private-ipv4-addresses" + SvcLBSuffixIpv6Addresses = "aws-load-balancer-ipv6-addresses" + SvcLBSuffixALPNPolicy = "aws-load-balancer-alpn-policy" + SvcLBSuffixTargetNodeLabels = "aws-load-balancer-target-node-labels" + SvcLBSuffixLoadBalancerAttributes = "aws-load-balancer-attributes" + SvcLBSuffixLoadBalancerSecurityGroups = "aws-load-balancer-security-groups" + SvcLBSuffixManageSGRules = "aws-load-balancer-manage-backend-security-group-rules" + SvcLBSuffixEnforceSGInboundRulesOnPrivateLinkTraffic = "aws-load-balancer-inbound-sg-rules-on-private-link-traffic" ) diff --git a/pkg/deploy/elbv2/load_balancer_manager.go b/pkg/deploy/elbv2/load_balancer_manager.go index 92add6ce9..ee4bfc30a 100644 --- a/pkg/deploy/elbv2/load_balancer_manager.go +++ b/pkg/deploy/elbv2/load_balancer_manager.go @@ -3,6 +3,7 @@ package elbv2 import ( "context" "fmt" + awssdk "github.com/aws/aws-sdk-go/aws" elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" "github.com/go-logr/logr" @@ -75,6 +76,12 @@ func (m *defaultLoadBalancerManager) Create(ctx context.Context, resLB *elbv2mod return elbv2model.LoadBalancerStatus{}, err } + if resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork && resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink != nil { + if err := m.updateSDKLoadBalancerWithSecurityGroups(ctx, resLB, sdkLB); err != nil { + return elbv2model.LoadBalancerStatus{}, err + } + } + return buildResLoadBalancerStatus(sdkLB), nil } @@ -186,27 +193,39 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSecurityGroups(ctx } desiredSecurityGroups := sets.NewString(awssdk.StringValueSlice(securityGroups)...) currentSecurityGroups := sets.NewString(awssdk.StringValueSlice(sdkLB.LoadBalancer.SecurityGroups)...) - if desiredSecurityGroups.Equal(currentSecurityGroups) { + + isEnforceSGInboundRulesOnPrivateLinkUpdated, currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic := isEnforceSGInboundRulesOnPrivateLinkUpdated(resLB, sdkLB) + if desiredSecurityGroups.Equal(currentSecurityGroups) && !isEnforceSGInboundRulesOnPrivateLinkUpdated { return nil } + var changeDescriptions []string + + if !desiredSecurityGroups.Equal(currentSecurityGroups) { + changeSecurityGroupsDesc := fmt.Sprintf("%v => %v", currentSecurityGroups.List(), desiredSecurityGroups.List()) + changeDescriptions = append(changeDescriptions, "changeSecurityGroups", changeSecurityGroupsDesc) + } + req := &elbv2sdk.SetSecurityGroupsInput{ LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, SecurityGroups: securityGroups, } - changeDesc := fmt.Sprintf("%v => %v", currentSecurityGroups.List(), desiredSecurityGroups.List()) - m.logger.Info("modifying loadBalancer securityGroups", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - "change", changeDesc) + + if isEnforceSGInboundRulesOnPrivateLinkUpdated { + changeEnforceSecurityGroupInboundRulesOnPrivateLinkTrafficDesc := fmt.Sprintf("%v => %v", currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) + changeDescriptions = append(changeDescriptions, "changeEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic", changeEnforceSecurityGroupInboundRulesOnPrivateLinkTrafficDesc) + req.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = &desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic + } + if _, err := m.elbv2Client.SetSecurityGroupsWithContext(ctx, req); err != nil { return err } m.logger.Info("modified loadBalancer securityGroups", "stackID", resLB.Stack().StackID(), "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) + "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), + "changeSecurityGroups", changeDescriptions, + ) return nil } @@ -298,3 +317,25 @@ func buildResLoadBalancerStatus(sdkLB LoadBalancerWithTags) elbv2model.LoadBalan DNSName: awssdk.StringValue(sdkLB.LoadBalancer.DNSName), } } + +func isEnforceSGInboundRulesOnPrivateLinkUpdated(resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) (bool, string, string) { + + if resLB.Spec.Type != elbv2model.LoadBalancerTypeNetwork || resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink == nil { + return false, "", "" + } + + desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic := string(*resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink) + + var currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic string + + if sdkLB.LoadBalancer.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic != nil { + currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = awssdk.StringValue(sdkLB.LoadBalancer.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) + } + + if desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic == currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic { + return false, "", "" + } + + return true, currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic + +} diff --git a/pkg/model/elbv2/load_balancer.go b/pkg/model/elbv2/load_balancer.go index 368a42a9d..d3d344bce 100644 --- a/pkg/model/elbv2/load_balancer.go +++ b/pkg/model/elbv2/load_balancer.go @@ -2,6 +2,7 @@ package elbv2 import ( "context" + "github.com/pkg/errors" "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" ) @@ -86,6 +87,13 @@ const ( IPAddressTypeDualStack IPAddressType = "dualstack" ) +type SecurityGroupsInboundRulesOnPrivateLinkStatus string + +const ( + SecurityGroupsInboundRulesOnPrivateLinkOn SecurityGroupsInboundRulesOnPrivateLinkStatus = "on" + SecurityGroupsInboundRulesOnPrivateLinkOff SecurityGroupsInboundRulesOnPrivateLinkStatus = "off" +) + type LoadBalancerScheme string const ( @@ -143,6 +151,10 @@ type LoadBalancerSpec struct { // +optional SecurityGroups []core.StringToken `json:"securityGroups,omitempty"` + // [Network Load Balancers] The status of the security groups inbound rules on private link. + // +optional + SecurityGroupsInboundRulesOnPrivateLink *SecurityGroupsInboundRulesOnPrivateLinkStatus `json:"securityGroupsInboundRulesOnPrivateLink,omitempty"` + // [Application Load Balancers on Outposts] The ID of the customer-owned address pool (CoIP pool). // +optional CustomerOwnedIPv4Pool *string `json:"customerOwnedIPv4Pool,omitempty"` diff --git a/pkg/service/model_build_load_balancer.go b/pkg/service/model_build_load_balancer.go index 9e5040a52..1a645205d 100644 --- a/pkg/service/model_build_load_balancer.go +++ b/pkg/service/model_build_load_balancer.go @@ -77,6 +77,11 @@ func (t *defaultModelBuildTask) buildLoadBalancerSpec(ctx context.Context, schem if err != nil { return elbv2model.LoadBalancerSpec{}, err } + securityGroupsInboundRulesOnPrivateLink, err := t.buildSecurityGroupsInboundRulesOnPrivateLink(ctx) + if err != nil { + return elbv2model.LoadBalancerSpec{}, err + } + spec := elbv2model.LoadBalancerSpec{ Name: name, Type: elbv2model.LoadBalancerTypeNetwork, @@ -87,6 +92,11 @@ func (t *defaultModelBuildTask) buildLoadBalancerSpec(ctx context.Context, schem LoadBalancerAttributes: lbAttributes, Tags: tags, } + + if securityGroupsInboundRulesOnPrivateLink != nil { + spec.SecurityGroupsInboundRulesOnPrivateLink = securityGroupsInboundRulesOnPrivateLink + } + return spec, nil } @@ -177,6 +187,23 @@ func (t *defaultModelBuildTask) buildLoadBalancerIPAddressType(_ context.Context } } +func (t *defaultModelBuildTask) buildSecurityGroupsInboundRulesOnPrivateLink(_ context.Context) (*elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus, error) { + var rawSecurityGroupsInboundRulesOnPrivateLink string + if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixEnforceSGInboundRulesOnPrivateLinkTraffic, &rawSecurityGroupsInboundRulesOnPrivateLink, t.service.Annotations); !exists { + return nil, nil + } + securityGroupsInboundRulesOnPrivateLink := elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus(rawSecurityGroupsInboundRulesOnPrivateLink) + + switch securityGroupsInboundRulesOnPrivateLink { + case elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOn: + return &securityGroupsInboundRulesOnPrivateLink, nil + case elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff: + return &securityGroupsInboundRulesOnPrivateLink, nil + default: + return nil, errors.Errorf("Invalid value for securityGroupsInboundRulesOnPrivateLink status: %v, value must be one of [%v, %v]", securityGroupsInboundRulesOnPrivateLink, string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOn), string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff)) + } +} + func (t *defaultModelBuildTask) buildLoadBalancerScheme(ctx context.Context) (elbv2model.LoadBalancerScheme, error) { scheme, explicitSchemeSpecified, err := t.buildLoadBalancerSchemeViaAnnotation(ctx) if err != nil { diff --git a/pkg/service/model_builder.go b/pkg/service/model_builder.go index 94ea62ee4..961254209 100644 --- a/pkg/service/model_builder.go +++ b/pkg/service/model_builder.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/go-logr/logr" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -40,7 +41,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) *defaultModelBuilder { + disableRestrictedSGRules bool, logger logr.Logger) *defaultModelBuilder { return &defaultModelBuilder{ annotationParser: annotationParser, subnetsResolver: subnetsResolver, @@ -61,6 +62,7 @@ func NewDefaultModelBuilder(annotationParser annotations.Parser, subnetsResolver ec2Client: ec2Client, enableBackendSG: enableBackendSG, disableRestrictedSGRules: disableRestrictedSGRules, + logger: logger, } } @@ -87,6 +89,7 @@ type defaultModelBuilder struct { defaultSSLPolicy string defaultTargetType elbv2model.TargetType enableIPTargetType bool + logger logr.Logger } func (b *defaultModelBuilder) Build(ctx context.Context, service *corev1.Service) (core.Stack, *elbv2model.LoadBalancer, bool, error) { @@ -107,6 +110,7 @@ func (b *defaultModelBuilder) Build(ctx context.Context, service *corev1.Service ec2Client: b.ec2Client, enableBackendSG: b.enableBackendSG, disableRestrictedSGRules: b.disableRestrictedSGRules, + logger: b.logger, service: service, stack: stack, @@ -162,6 +166,7 @@ type defaultModelBuildTask struct { serviceUtils ServiceUtils enableIPTargetType bool ec2Client services.EC2 + logger logr.Logger service *corev1.Service diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 2c14c4950..4b598d80a 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/go-logr/logr" "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -21,6 +22,7 @@ import ( "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/networking" + "sigs.k8s.io/controller-runtime/pkg/log" ) func Test_defaultModelBuilderTask_Build(t *testing.T) { @@ -127,7 +129,8 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { Namespace: "default", UID: "bdca2bd0-bfc6-449a-88a3-03451f05f18c", Annotations: map[string]string{ - "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", + "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", + "service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic": "on", }, }, Spec: corev1.ServiceSpec{ @@ -184,6 +187,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "name":"k8s-default-nlbipsvc-6b0ba8ff70", "type":"network", "scheme":"internal", + "securityGroupsInboundRulesOnPrivateLink":"on", "ipAddressType":"ipv4", "subnetMapping":[ { @@ -274,9 +278,10 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { Namespace: "default", UID: "bdca2bd0-bfc6-449a-88a3-03451f05f18c", Annotations: map[string]string{ - "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", - "service.beta.kubernetes.io/aws-load-balancer-ip-address-type": "dualstack", - "service.beta.kubernetes.io/aws-load-balancer-scheme": "internet-facing", + "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", + "service.beta.kubernetes.io/aws-load-balancer-ip-address-type": "dualstack", + "service.beta.kubernetes.io/aws-load-balancer-scheme": "internet-facing", + "service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic": "on", }, }, Spec: corev1.ServiceSpec{ @@ -332,6 +337,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "name":"k8s-default-nlbipsvc-4d831c6ca6", "type":"network", "scheme":"internet-facing", + "securityGroupsInboundRulesOnPrivateLink":"on", "ipAddressType":"dualstack", "subnetMapping":[ { @@ -422,15 +428,16 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { Name: "nlb-ip-svc", Namespace: "default", Annotations: map[string]string{ - "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", - "service.beta.kubernetes.io/aws-load-balancer-scheme": "internal", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol": "HTTP", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port": "8888", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-path": "/healthz", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval": "10", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout": "30", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold": "2", - "service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold": "2", + "service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip", + "service.beta.kubernetes.io/aws-load-balancer-scheme": "internal", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol": "HTTP", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port": "8888", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-path": "/healthz", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval": "10", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout": "30", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold": "2", + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold": "2", + "service.beta.kubernetes.io/aws-load-balancer-inbound-sg-rules-on-private-link-traffic": "off", }, UID: "7ab4be33-11c2-4a7b-b655-7add8affab36", }, @@ -517,6 +524,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "name":"k8s-default-nlbipsvc-518cdfc227", "type":"network", "scheme":"internal", + "securityGroupsInboundRulesOnPrivateLink":"off", "ipAddressType":"ipv4", "subnetMapping":[ { @@ -6453,7 +6461,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { } 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) + backendSGProvider, sgResolver, tt.enableBackendSG, tt.disableRestrictedSGRules, logr.New(&log.NullLogSink{})) ctx := context.Background() stack, _, _, err := builder.Build(ctx, tt.svc) if tt.wantError { diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index 58629f3b2..1b0ce5c82 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -21,11 +21,12 @@ import ( const apiPathValidateELBv2TargetGroupBinding = "/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding" // NewTargetGroupBindingValidator returns a validator for TargetGroupBinding CRD. -func NewTargetGroupBindingValidator(k8sClient client.Client, elbv2Client services.ELBV2, logger logr.Logger) *targetGroupBindingValidator { +func NewTargetGroupBindingValidator(k8sClient client.Client, elbv2Client services.ELBV2, vpcID string, logger logr.Logger) *targetGroupBindingValidator { return &targetGroupBindingValidator{ k8sClient: k8sClient, elbv2Client: elbv2Client, logger: logger, + vpcID: vpcID, } } @@ -35,6 +36,7 @@ type targetGroupBindingValidator struct { k8sClient client.Client elbv2Client services.ELBV2 logger logr.Logger + vpcID string } func (v *targetGroupBindingValidator) Prototype(_ admission.Request) (runtime.Object, error) { @@ -112,7 +114,8 @@ func (v *targetGroupBindingValidator) checkImmutableFields(tgb *elbv2api.TargetG changedImmutableFields = append(changedImmutableFields, "spec.ipAddressType") } if (tgb.Spec.VpcID != "" && oldTGB.Spec.VpcID != "" && (tgb.Spec.VpcID) != (oldTGB.Spec.VpcID)) || - (tgb.Spec.VpcID == "") != (oldTGB.Spec.VpcID == "") { + (oldTGB.Spec.VpcID != "" && tgb.Spec.VpcID == "") || + (oldTGB.Spec.VpcID == "" && tgb.Spec.VpcID != "" && tgb.Spec.VpcID != v.vpcID) { changedImmutableFields = append(changedImmutableFields, "spec.vpcID") } if len(changedImmutableFields) != 0 { diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index 1cf07a45a..b083bc4ca 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -39,6 +39,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { } instanceTargetType := elbv2api.TargetTypeInstance ipTargetType := elbv2api.TargetTypeIP + clusterVpcID := "vpcid-02" tests := []struct { name string fields fields @@ -194,7 +195,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { TargetGroupArn: awssdk.String("tg-2"), TargetType: awssdk.String("instance"), IpAddressType: awssdk.String("ipv6"), - VpcId: awssdk.String("vpcid-02"), + VpcId: &clusterVpcID, }, }, }, @@ -207,7 +208,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { TargetGroupArn: awssdk.String("tg-2"), TargetType: awssdk.String("instance"), IpAddressType: awssdk.String("ipv6"), - VpcId: awssdk.String("vpcid-02"), + VpcId: &clusterVpcID, }, }, }, @@ -219,7 +220,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { TargetGroupARN: "tg-2", TargetType: &instanceTargetType, IPAddressType: &targetGroupIPAddressTypeIPv6, - VpcID: "vpcid-02", + VpcID: clusterVpcID, }, }, }, @@ -238,7 +239,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { TargetGroupArn: awssdk.String("tg-2"), TargetType: awssdk.String("instance"), IpAddressType: awssdk.String("ipv6"), - VpcId: awssdk.String("vpcid-02"), + VpcId: &clusterVpcID, }, }, }, @@ -251,7 +252,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { TargetGroupArn: awssdk.String("tg-2"), TargetType: awssdk.String("instance"), IpAddressType: awssdk.String("ipv6"), - VpcId: awssdk.String("vpcid-02"), + VpcId: &clusterVpcID, }, }, }, @@ -479,6 +480,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { } instanceTargetType := elbv2api.TargetTypeInstance ipTargetType := elbv2api.TargetTypeIP + clusterVpcID := "cluster-vpc-id" tests := []struct { name string args args @@ -746,11 +748,31 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.vpcID"), }, + { + name: "VpcID modified from nil to cluster vpc-id is allowed", + args: args{ + tgb: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-2", + TargetType: &ipTargetType, + VpcID: clusterVpcID, + }, + }, + oldTGB: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-2", + TargetType: &ipTargetType, + }, + }, + }, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &targetGroupBindingValidator{ logger: logr.New(&log.NullLogSink{}), + vpcID: clusterVpcID, } err := v.checkImmutableFields(tt.args.tgb, tt.args.oldTGB) if tt.wantErr != nil {