diff --git a/.changelog/14764.txt b/.changelog/14764.txt new file mode 100644 index 000000000000..d2a67400fd9d --- /dev/null +++ b/.changelog/14764.txt @@ -0,0 +1,15 @@ +```release-note:enhancement +data-source/aws_elb: Add `desync_mitigation_mode` attribute +``` + +```release-note:enhancement +data-source/aws_lb: Add `desync_mitigation_mode` attribute +``` + +```release-note:enhancement +resource/aws_elb: Add `desync_mitigation_mode` argument +``` + +```release-note:enhancement +resource/aws_lb: Add `desync_mitigation_mode` argument +``` diff --git a/internal/service/elb/load_balancer.go b/internal/service/elb/load_balancer.go index 0c6f106ac490..7dab0295d353 100644 --- a/internal/service/elb/load_balancer.go +++ b/internal/service/elb/load_balancer.go @@ -255,6 +255,17 @@ func ResourceLoadBalancer() *schema.Resource { Computed: true, }, + "desync_mitigation_mode": { + Type: schema.TypeString, + Optional: true, + Default: "defensive", + ValidateFunc: validation.StringInSlice([]string{ + "monitor", + "defensive", + "strictest", + }, false), + }, + "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -453,6 +464,13 @@ func flattenLoadBalancerEResource(d *schema.ResourceData, ec2conn *ec2.EC2, elbc } } + for _, attr := range lbAttrs.AdditionalAttributes { + switch aws.StringValue(attr.Key) { + case "elb.http.desyncmitigationmode": + d.Set("desync_mitigation_mode", aws.StringValue(attr.Value)) + } + } + tags, err := ListTags(elbconn, d.Id()) if err != nil { @@ -580,10 +598,16 @@ func resourceLoadBalancerUpdate(d *schema.ResourceData, meta interface{}) error } } - if d.HasChanges("cross_zone_load_balancing", "idle_timeout", "access_logs") { + if d.HasChanges("cross_zone_load_balancing", "idle_timeout", "access_logs", "desync_mitigation_mode") { attrs := elb.ModifyLoadBalancerAttributesInput{ LoadBalancerName: aws.String(d.Get("name").(string)), LoadBalancerAttributes: &elb.LoadBalancerAttributes{ + AdditionalAttributes: []*elb.AdditionalAttribute{ + { + Key: aws.String("elb.http.desyncmitigationmode"), + Value: aws.String(d.Get("desync_mitigation_mode").(string)), + }, + }, CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{ Enabled: aws.Bool(d.Get("cross_zone_load_balancing").(bool)), }, diff --git a/internal/service/elb/load_balancer_data_source.go b/internal/service/elb/load_balancer_data_source.go index 405963ad920e..9d9a56abbf44 100644 --- a/internal/service/elb/load_balancer_data_source.go +++ b/internal/service/elb/load_balancer_data_source.go @@ -188,6 +188,11 @@ func DataSourceLoadBalancer() *schema.Resource { Set: schema.HashString, }, + "desync_mitigation_mode": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), "zone_id": { @@ -304,6 +309,13 @@ func dataSourceLoadBalancerRead(d *schema.ResourceData, meta interface{}) error } } + for _, attr := range lbAttrs.AdditionalAttributes { + switch aws.StringValue(attr.Key) { + case "elb.http.desyncmitigationmode": + d.Set("desync_mitigation_mode", aws.StringValue(attr.Value)) + } + } + tags, err := ListTags(conn, d.Id()) if err != nil { diff --git a/internal/service/elb/load_balancer_data_source_test.go b/internal/service/elb/load_balancer_data_source_test.go index 197829eda7da..0ee52aa85c41 100644 --- a/internal/service/elb/load_balancer_data_source_test.go +++ b/internal/service/elb/load_balancer_data_source_test.go @@ -29,6 +29,7 @@ func TestAccELBLoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttr(dataSourceName, "internal", "true"), resource.TestCheckResourceAttr(dataSourceName, "subnets.#", "2"), resource.TestCheckResourceAttr(dataSourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "desync_mitigation_mode", "defensive"), resource.TestCheckResourceAttr(dataSourceName, "tags.%", "2"), resource.TestCheckResourceAttr(dataSourceName, "tags.Name", rName), resource.TestCheckResourceAttr(dataSourceName, "tags.TestName", t.Name()), diff --git a/internal/service/elb/load_balancer_test.go b/internal/service/elb/load_balancer_test.go index 7efb89a61d8f..61d350e081ba 100644 --- a/internal/service/elb/load_balancer_test.go +++ b/internal/service/elb/load_balancer_test.go @@ -44,6 +44,7 @@ func TestAccELBLoadBalancer_basic(t *testing.T) { "lb_protocol": "http", }), resource.TestCheckResourceAttr(resourceName, "cross_zone_load_balancing", "true"), + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "defensive"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -667,6 +668,71 @@ func TestAccELBLoadBalancer_securityGroups(t *testing.T) { }) } +func TestAccELBLoadBalancer_desyncMitigationMode(t *testing.T) { + resourceName := "aws_elb.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLoadBalancerConfigDesyncMitigationMode, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "strictest"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccELBLoadBalancer_desyncMitigationMode_update(t *testing.T) { + resourceName := "aws_elb.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLoadBalancerConfigDesyncMitigationMode_update_default, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "defensive"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccLoadBalancerConfigDesyncMitigationMode_update_monitor, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "monitor"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccLoadBalancerConfigDesyncMitigationMode_update_default, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "defensive"), + ), + }, + }, + }) +} + // Unit test for listeners hash func TestLoadBalancerListenerHash(t *testing.T) { cases := map[string]struct { @@ -1839,3 +1905,72 @@ resource "aws_internet_gateway" "gw" { } } ` + +const testAccLoadBalancerConfigDesyncMitigationMode = ` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_elb" "test" { + availability_zones = [data.aws_availability_zones.available.names[0]] + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + desync_mitigation_mode = "strictest" +} +` + +const testAccLoadBalancerConfigDesyncMitigationMode_update_default = ` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_elb" "test" { + availability_zones = [data.aws_availability_zones.available.names[0]] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} +` + +const testAccLoadBalancerConfigDesyncMitigationMode_update_monitor = ` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_elb" "test" { + availability_zones = [data.aws_availability_zones.available.names[0]] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + desync_mitigation_mode = "monitor" +} +` diff --git a/internal/service/elbv2/load_balancer.go b/internal/service/elbv2/load_balancer.go index 66789030e76d..819801ee6cbf 100644 --- a/internal/service/elbv2/load_balancer.go +++ b/internal/service/elbv2/load_balancer.go @@ -258,6 +258,18 @@ func ResourceLoadBalancer() *schema.Resource { Computed: true, }, + "desync_mitigation_mode": { + Type: schema.TypeString, + Optional: true, + Default: "defensive", + ValidateFunc: validation.StringInSlice([]string{ + "monitor", + "defensive", + "strictest", + }, false), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), + }, + "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -270,6 +282,12 @@ func suppressIfLBType(t string) schema.SchemaDiffSuppressFunc { } } +func suppressIfLBTypeNot(t string) schema.SchemaDiffSuppressFunc { + return func(k string, old string, new string, d *schema.ResourceData) bool { + return d.Get("load_balancer_type").(string) != t + } +} + func resourceLoadBalancerCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).ELBV2Conn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig @@ -483,6 +501,13 @@ func resourceLoadBalancerUpdate(d *schema.ResourceData, meta interface{}) error }) } + if d.HasChange("desync_mitigation_mode") || d.IsNewResource() { + attributes = append(attributes, &elbv2.LoadBalancerAttribute{ + Key: aws.String("routing.http.desync_mitigation_mode"), + Value: aws.String(d.Get("desync_mitigation_mode").(string)), + }) + } + case elbv2.LoadBalancerTypeEnumGateway, elbv2.LoadBalancerTypeEnumNetwork: if d.HasChange("enable_cross_zone_load_balancing") || d.IsNewResource() { attributes = append(attributes, &elbv2.LoadBalancerAttribute{ @@ -824,6 +849,10 @@ func flattenResource(d *schema.ResourceData, meta interface{}, lb *elbv2.LoadBal crossZoneLbEnabled := aws.StringValue(attr.Value) == "true" log.Printf("[DEBUG] Setting NLB Cross Zone Load Balancing Enabled: %t", crossZoneLbEnabled) d.Set("enable_cross_zone_load_balancing", crossZoneLbEnabled) + case "routing.http.desync_mitigation_mode": + desyncMitigationMode := aws.StringValue(attr.Value) + log.Printf("[DEBUG] Setting ALB Desync Mitigation Mode: %s", desyncMitigationMode) + d.Set("desync_mitigation_mode", desyncMitigationMode) } } diff --git a/internal/service/elbv2/load_balancer_data_source.go b/internal/service/elbv2/load_balancer_data_source.go index 4361460b2914..4c1df97c5c58 100644 --- a/internal/service/elbv2/load_balancer_data_source.go +++ b/internal/service/elbv2/load_balancer_data_source.go @@ -158,6 +158,11 @@ func DataSourceLoadBalancer() *schema.Resource { Computed: true, }, + "desync_mitigation_mode": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), }, } @@ -298,6 +303,9 @@ func dataSourceLoadBalancerRead(d *schema.ResourceData, meta interface{}) error case "load_balancing.cross_zone.enabled": crossZoneLbEnabled := aws.StringValue(attr.Value) == "true" d.Set("enable_cross_zone_load_balancing", crossZoneLbEnabled) + case "routing.http.desync_mitigation_mode": + desyncMitigationMode := aws.StringValue(attr.Value) + d.Set("desync_mitigation_mode", desyncMitigationMode) } } diff --git a/internal/service/elbv2/load_balancer_data_source_test.go b/internal/service/elbv2/load_balancer_data_source_test.go index 7c3936f93c4c..cd2e2c963842 100644 --- a/internal/service/elbv2/load_balancer_data_source_test.go +++ b/internal/service/elbv2/load_balancer_data_source_test.go @@ -40,6 +40,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), 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(dataSourceName2, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName2, "internal", resourceName, "internal"), resource.TestCheckResourceAttrPair(dataSourceName2, "subnets.#", resourceName, "subnets.#"), @@ -55,6 +56,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName2, "arn", resourceName, "arn"), 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(dataSourceName3, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName3, "internal", resourceName, "internal"), resource.TestCheckResourceAttrPair(dataSourceName3, "subnets.#", resourceName, "subnets.#"), @@ -70,6 +72,7 @@ func TestAccELBV2LoadBalancerDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName3, "arn", resourceName, "arn"), 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"), ), }, }, @@ -200,6 +203,8 @@ resource "aws_lb" "test" { idle_timeout = 30 enable_deletion_protection = false + desync_mitigation_mode = "defensive" + tags = { Name = %[1]q Config = "Basic" diff --git a/internal/service/elbv2/load_balancer_test.go b/internal/service/elbv2/load_balancer_test.go index 51b5fc2797ca..5a1fd1b210a3 100644 --- a/internal/service/elbv2/load_balancer_test.go +++ b/internal/service/elbv2/load_balancer_test.go @@ -92,6 +92,7 @@ func TestAccELBV2LoadBalancer_ALB_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.Name", "TestAccAWSALB_basic"), resource.TestCheckResourceAttrSet(resourceName, "vpc_id"), resource.TestCheckResourceAttrSet(resourceName, "zone_id"), + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "defensive"), ), }, }, @@ -1158,6 +1159,55 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancerSubnet_change(t *testing.T) { }) } +func TestAccELBV2LoadBalancer_updateDesyncMitigationMode(t *testing.T) { + var pre, mid, post elbv2.LoadBalancer + lbName := fmt.Sprintf("testaccawsalb-desync-%s", sdkacctest.RandString(4)) + resourceName := "aws_lb.lb_test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBConfig_desyncMitigationMode(lbName, "strictest"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(resourceName, &pre), + testAccCheckLoadBalancerAttribute(resourceName, "routing.http.desync_mitigation_mode", "strictest"), + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "strictest"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLBConfig_desyncMitigationMode(lbName, "monitor"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(resourceName, &mid), + testAccCheckLoadBalancerAttribute(resourceName, "routing.http.desync_mitigation_mode", "monitor"), + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "monitor"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLBConfig_desyncMitigationMode(lbName, "defensive"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLoadBalancerExists(resourceName, &post), + testAccCheckLoadBalancerAttribute(resourceName, "routing.http.desync_mitigation_mode", "defensive"), + resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "defensive"), + ), + }, + }, + }) +} + func testAccChecklbARNs(pre, post *elbv2.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { if aws.StringValue(pre.LoadBalancerArn) != aws.StringValue(post.LoadBalancerArn) { @@ -2974,3 +3024,81 @@ resource "aws_security_group" "alb_test" { } `, lbName)) } + +func testAccAWSLBConfig_desyncMitigationMode(lbName string, mode string) string { + return fmt.Sprintf(` +resource "aws_lb" "lb_test" { + name = "%s" + internal = true + security_groups = [aws_security_group.alb_test.id] + subnets = aws_subnet.alb_test.*.id + + idle_timeout = 30 + enable_deletion_protection = false + + desync_mitigation_mode = %q + + tags = { + Name = "TestAccAWSALB_desync" + } +} + +variable "subnets" { + default = ["10.0.1.0/24", "10.0.2.0/24"] + type = list +} + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "alb_test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "terraform-testacc-lb-desync" + } +} + +resource "aws_subnet" "alb_test" { + count = 2 + vpc_id = aws_vpc.alb_test.id + cidr_block = element(var.subnets, count.index) + map_public_ip_on_launch = true + availability_zone = element(data.aws_availability_zones.available.names, count.index) + + tags = { + Name = "tf-acc-lb-desync-${count.index}" + } +} + +resource "aws_security_group" "alb_test" { + name = "allow_all_alb_test_desync" + description = "Used for ALB Testing" + vpc_id = aws_vpc.alb_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 = "TestAccAWSALB_desync" + } +} +`, lbName, mode) +} diff --git a/website/docs/r/elb.html.markdown b/website/docs/r/elb.html.markdown index fd94e01dd21a..dcfc96484cea 100644 --- a/website/docs/r/elb.html.markdown +++ b/website/docs/r/elb.html.markdown @@ -88,6 +88,7 @@ The following arguments are supported: * `idle_timeout` - (Optional) The time in seconds that the connection is allowed to be idle. Default: `60` * `connection_draining` - (Optional) Boolean to enable connection draining. Default: `false` * `connection_draining_timeout` - (Optional) The time in seconds to allow for connections to drain. Default: `300` +* `desync_mitigation_mode` - (Optional) Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest`. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. Exactly one of `availability_zones` or `subnets` must be specified: this diff --git a/website/docs/r/lb.html.markdown b/website/docs/r/lb.html.markdown index 1fca97bb7c81..b648a44a6a5f 100644 --- a/website/docs/r/lb.html.markdown +++ b/website/docs/r/lb.html.markdown @@ -123,6 +123,7 @@ for load balancers of type `network` will force a recreation of the resource. * `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`. * `customer_owned_ipv4_pool` - (Optional) The ID of the customer owned ipv4 pool to use for this load balancer. * `ip_address_type` - (Optional) The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack` +* `desync_mitigation_mode` - (Optional) Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest`. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. Access Logs (`access_logs`) support the following: