diff --git a/.changelog/33767.txt b/.changelog/33767.txt new file mode 100644 index 00000000000..3473ce48394 --- /dev/null +++ b/.changelog/33767.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_lb: Add `enforce_security_group_inbound_rules_on_private_link_traffic` argument +``` + +```release-note:enhancement +data-source/aws_lb: Add `enforce_security_group_inbound_rules_on_private_link_traffic` attribute +``` \ No newline at end of file diff --git a/internal/service/elbv2/load_balancer.go b/internal/service/elbv2/load_balancer.go index 018b4306795..1416fb267e0 100644 --- a/internal/service/elbv2/load_balancer.go +++ b/internal/service/elbv2/load_balancer.go @@ -29,6 +29,7 @@ import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -170,6 +171,13 @@ func ResourceLoadBalancer() *schema.Resource { Default: false, DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, + "enforce_security_group_inbound_rules_on_private_link_traffic": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(elbv2.EnforceSecurityGroupInboundRulesOnPrivateLinkTrafficEnum_Values(), false), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumNetwork), + }, "idle_timeout": { Type: schema.TypeInt, Optional: true, @@ -595,13 +603,20 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } } - if d.HasChange("security_groups") { + if d.HasChanges("enforce_security_group_inbound_rules_on_private_link_traffic", "security_groups") { sgs := flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)) params := &elbv2.SetSecurityGroupsInput{ LoadBalancerArn: aws.String(d.Id()), SecurityGroups: sgs, } + + if v := d.Get("load_balancer_type"); v == elbv2.LoadBalancerTypeEnumNetwork { + if v, ok := d.GetOk("enforce_security_group_inbound_rules_on_private_link_traffic"); ok { + params.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = aws.String(v.(string)) + } + } + _, err := conn.SetSecurityGroupsWithContext(ctx, params) if err != nil { return sdkdiag.AppendErrorf(diags, "failure Setting LB Security Groups: %s", err) @@ -889,14 +904,10 @@ func getLBNameFromARN(arn string) (string, error) { return matches[1], nil } -// flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs -// for the ALB based on the AvailabilityZones structure returned by the API. func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string { - var result []string - for _, az := range availabilityZones { - result = append(result, aws.StringValue(az.SubnetId)) - } - return result + return tfslices.ApplyToAll(availabilityZones, func(v *elbv2.AvailabilityZone) string { + return aws.StringValue(v.SubnetId) + }) } func flattenSubnetMappingsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []map[string]interface{} { @@ -939,6 +950,7 @@ func flattenResource(ctx context.Context, d *schema.ResourceData, meta interface d.Set("arn_suffix", SuffixFromARN(lb.LoadBalancerArn)) d.Set("customer_owned_ipv4_pool", lb.CustomerOwnedIpv4Pool) d.Set("dns_name", lb.DNSName) + d.Set("enforce_security_group_inbound_rules_on_private_link_traffic", lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) d.Set("internal", aws.StringValue(lb.Scheme) == elbv2.LoadBalancerSchemeEnumInternal) d.Set("ip_address_type", lb.IpAddressType) d.Set("load_balancer_type", lb.Type) diff --git a/internal/service/elbv2/load_balancer_data_source.go b/internal/service/elbv2/load_balancer_data_source.go index cc368fb76e0..d5c5ebd80ab 100644 --- a/internal/service/elbv2/load_balancer_data_source.go +++ b/internal/service/elbv2/load_balancer_data_source.go @@ -103,6 +103,10 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "enforce_security_group_inbound_rules_on_private_link_traffic": { + Type: schema.TypeString, + Computed: true, + }, "idle_timeout": { Type: schema.TypeInt, Computed: true, @@ -234,28 +238,25 @@ func dataSourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, met } lb := results[0] - d.SetId(aws.StringValue(lb.LoadBalancerArn)) - d.Set("arn", lb.LoadBalancerArn) d.Set("arn_suffix", SuffixFromARN(lb.LoadBalancerArn)) - d.Set("name", lb.LoadBalancerName) - d.Set("internal", lb.Scheme != nil && aws.StringValue(lb.Scheme) == "internal") - d.Set("security_groups", flex.FlattenStringList(lb.SecurityGroups)) - d.Set("vpc_id", lb.VpcId) - d.Set("zone_id", lb.CanonicalHostedZoneId) + d.Set("customer_owned_ipv4_pool", lb.CustomerOwnedIpv4Pool) d.Set("dns_name", lb.DNSName) + d.Set("enforce_security_group_inbound_rules_on_private_link_traffic", lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) d.Set("ip_address_type", lb.IpAddressType) + d.Set("name", lb.LoadBalancerName) + d.Set("internal", aws.StringValue(lb.Scheme) == "internal") d.Set("load_balancer_type", lb.Type) - d.Set("customer_owned_ipv4_pool", lb.CustomerOwnedIpv4Pool) - - if err := d.Set("subnets", flattenSubnetsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting subnets: %s", err) - } - + d.Set("security_groups", aws.StringValueSlice(lb.SecurityGroups)) if err := d.Set("subnet_mapping", flattenSubnetMappingsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { return sdkdiag.AppendErrorf(diags, "setting subnet_mapping: %s", err) } + if err := d.Set("subnets", flattenSubnetsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting subnets: %s", err) + } + d.Set("vpc_id", lb.VpcId) + d.Set("zone_id", lb.CanonicalHostedZoneId) attributesResp, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, &elbv2.DescribeLoadBalancerAttributesInput{ LoadBalancerArn: aws.String(d.Id()), diff --git a/internal/service/elbv2/load_balancer_data_source_test.go b/internal/service/elbv2/load_balancer_data_source_test.go index ca77fe5329e..0c178097032 100644 --- a/internal/service/elbv2/load_balancer_data_source_test.go +++ b/internal/service/elbv2/load_balancer_data_source_test.go @@ -45,6 +45,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName, "ip_address_type", resourceName, "ip_address_type"), resource.TestCheckResourceAttrPair(dataSourceName, "subnet_mapping.#", resourceName, "subnet_mapping.#"), resource.TestCheckResourceAttrPair(dataSourceName, "desync_mitigation_mode", resourceName, "desync_mitigation_mode"), + resource.TestCheckResourceAttrPair(dataSourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic"), resource.TestCheckResourceAttrPair(dataSourceName2, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName2, "internal", resourceName, "internal"), resource.TestCheckResourceAttrPair(dataSourceName2, "subnets.#", resourceName, "subnets.#"), @@ -61,6 +62,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName2, "ip_address_type", resourceName, "ip_address_type"), resource.TestCheckResourceAttrPair(dataSourceName2, "subnet_mapping.#", resourceName, "subnet_mapping.#"), resource.TestCheckResourceAttrPair(dataSourceName2, "desync_mitigation_mode", resourceName, "desync_mitigation_mode"), + resource.TestCheckResourceAttrPair(dataSourceName2, "enforce_security_group_inbound_rules_on_private_link_traffic", resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic"), resource.TestCheckResourceAttrPair(dataSourceName3, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName3, "internal", resourceName, "internal"), resource.TestCheckResourceAttrPair(dataSourceName3, "subnets.#", resourceName, "subnets.#"), @@ -77,6 +79,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName3, "ip_address_type", resourceName, "ip_address_type"), resource.TestCheckResourceAttrPair(dataSourceName3, "subnet_mapping.#", resourceName, "subnet_mapping.#"), resource.TestCheckResourceAttrPair(dataSourceName3, "desync_mitigation_mode", resourceName, "desync_mitigation_mode"), + resource.TestCheckResourceAttrPair(dataSourceName3, "enforce_security_group_inbound_rules_on_private_link_traffic", resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic"), resource.TestCheckResourceAttrPair(dataSourceName3, "enable_tls_version_and_cipher_suite_headers", resourceName, "enable_tls_version_and_cipher_suite_headers"), resource.TestCheckResourceAttrPair(dataSourceName3, "enable_xff_client_port", resourceName, "enable_xff_client_port"), resource.TestCheckResourceAttrPair(dataSourceName3, "xff_header_processing_mode", resourceName, "xff_header_processing_mode"), diff --git a/internal/service/elbv2/load_balancer_test.go b/internal/service/elbv2/load_balancer_test.go index b29d4a2683c..b0c4ab376fb 100644 --- a/internal/service/elbv2/load_balancer_test.go +++ b/internal/service/elbv2/load_balancer_test.go @@ -915,6 +915,7 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updatedSecurityGroups(t *t Check: resource.ComposeAggregateTestCheckFunc( testAccCheckLoadBalancerExists(ctx, resourceName, &pre), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), ), }, { @@ -922,6 +923,7 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updatedSecurityGroups(t *t Check: resource.ComposeAggregateTestCheckFunc( testAccCheckLoadBalancerExists(ctx, resourceName, &post), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "2"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), testAccCheckLoadBalancerNotRecreated(&pre, &post), ), }, @@ -1389,6 +1391,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSecurityGroups(t *testin Check: resource.ComposeAggregateTestCheckFunc( testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), ), }, { @@ -1397,6 +1400,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSecurityGroups(t *testin testAccCheckLoadBalancerExists(ctx, resourceName, &lb2), testAccCheckLoadBalancerRecreated(&lb2, &lb1), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), ), }, { @@ -1405,6 +1409,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSecurityGroups(t *testin testAccCheckLoadBalancerExists(ctx, resourceName, &lb3), testAccCheckLoadBalancerNotRecreated(&lb3, &lb2), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "2"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), ), }, { @@ -1413,6 +1418,67 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSecurityGroups(t *testin testAccCheckLoadBalancerExists(ctx, resourceName, &lb4), testAccCheckLoadBalancerRecreated(&lb4, &lb3), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", ""), + ), + }, + }, + }) +} + +func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing.T) { + ctx := acctest.Context(t) + var lb1 elbv2.LoadBalancer + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckLoadBalancerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "off"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off")), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccLoadBalancerConfig_nlbSecurityGroups(rName, 1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), + ), + }, + { + Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "on"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "on"), + ), + }, + { + Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "off"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), + ), + }, + { + Config: testAccLoadBalancerConfig_nlbSecurityGroups(rName, 1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), ), }, }, @@ -2656,6 +2722,45 @@ resource "aws_lb" "test" { `, rName, n)) } +func testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName string, n int, enforcePrivateLink string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +resource "aws_security_group" "test" { + count = 3 + + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = %[1]q + } +} + +resource "aws_lb" "test" { + internal = true + load_balancer_type = "network" + name = %[1]q + subnets = aws_subnet.test[*].id + security_groups = slice(aws_security_group.test[*].id, 0, %[2]d) + + enforce_security_group_inbound_rules_on_private_link_traffic = %[3]q +} +`, rName, n, enforcePrivateLink)) +} + func testAccLoadBalancerConfig_nlbSubnets(rName string, subnetCount int) string { return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, subnetCount), fmt.Sprintf(` resource "aws_lb" "test" { diff --git a/website/docs/r/lb.html.markdown b/website/docs/r/lb.html.markdown index 011686179ae..fd43f7b625e 100644 --- a/website/docs/r/lb.html.markdown +++ b/website/docs/r/lb.html.markdown @@ -112,6 +112,7 @@ This resource supports the following arguments: * `enable_tls_version_and_cipher_suite_headers` - (Optional) Indicates whether the two headers (`x-amzn-tls-version` and `x-amzn-tls-cipher-suite`), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. Only valid for Load Balancers of type `application`. Defaults to `false` * `enable_xff_client_port` - (Optional) Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in `application` load balancers. Defaults to `false`. * `enable_waf_fail_open` - (Optional) Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. Defaults to `false`. +* `enforce_security_group_inbound_rules_on_private_link_traffic` - (Optional) Indicates whether inbound security group rules are enforced for traffic originating from a PrivateLink. Only valid for Load Balancers of type `network`. The possible values are `on` and `off`. * `idle_timeout` - (Optional) The time in seconds that the connection is allowed to be idle. Only valid for Load Balancers of type `application`. Default: 60. * `internal` - (Optional) If true, the LB will be internal. Defaults to `false`. * `ip_address_type` - (Optional) The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack`.