Skip to content

Commit

Permalink
Merge pull request #31873 from FabianPonce/fix/vpc-endpoint-dns
Browse files Browse the repository at this point in the history
ec2: fix aws_vpc_endpoint missing exposed dns_option
  • Loading branch information
ewbankkit authored Jun 12, 2023
2 parents 258dd32 + 78cf059 commit ba85a49
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .changelog/31873.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_vpc_endpoint: Add `private_dns_only_for_inbound_resolver_endpoint` attribute to the `dns_options` configuration block
```
38 changes: 28 additions & 10 deletions internal/service/ec2/vpc_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ func ResourceVPCEndpoint() *schema.Resource {
Optional: true,
ValidateFunc: validation.StringInSlice(ec2.DnsRecordIpType_Values(), false),
},
"private_dns_only_for_inbound_resolver_endpoint": {
Type: schema.TypeBool,
Optional: true,
},
},
},
},
Expand Down Expand Up @@ -209,7 +213,7 @@ func resourceVPCEndpointCreate(ctx context.Context, d *schema.ResourceData, meta
policy, err := structure.NormalizeJsonString(v)

if err != nil {
return sdkdiag.AppendErrorf(diags, "policy contains invalid JSON: %s", err)
return sdkdiag.AppendFromErr(diags, err)
}

input.PolicyDocument = aws.String(policy)
Expand Down Expand Up @@ -244,12 +248,12 @@ func resourceVPCEndpointCreate(ctx context.Context, d *schema.ResourceData, meta

if d.Get("auto_accept").(bool) && aws.StringValue(vpce.State) == vpcEndpointStatePendingAcceptance {
if err := vpcEndpointAccept(ctx, conn, d.Id(), aws.StringValue(vpce.ServiceName), d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "creating EC2 VPC Endpoint (%s): %s", serviceName, err)
return sdkdiag.AppendFromErr(diags, err)
}
}

if _, err = WaitVPCEndpointAvailable(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "creating EC2 VPC Endpoint (%s): waiting for completion: %s", serviceName, err)
return sdkdiag.AppendErrorf(diags, "waiting for EC2 VPC Endpoint (%s) create: %s", serviceName, err)
}

// For partitions not supporting tag-on-create, attempt tag after create.
Expand Down Expand Up @@ -293,7 +297,6 @@ func resourceVPCEndpointRead(ctx context.Context, d *schema.ResourceData, meta i
Resource: fmt.Sprintf("vpc-endpoint/%s", d.Id()),
}.String()
serviceName := aws.StringValue(vpce.ServiceName)

d.Set("arn", arn)
if err := d.Set("dns_entry", flattenDNSEntries(vpce.DnsEntries)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting dns_entry: %s", err)
Expand Down Expand Up @@ -337,13 +340,13 @@ func resourceVPCEndpointRead(ctx context.Context, d *schema.ResourceData, meta i
policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), aws.StringValue(vpce.PolicyDocument))

if err != nil {
return sdkdiag.AppendErrorf(diags, "while setting policy (%s), encountered: %s", policyToSet, err)
return sdkdiag.AppendFromErr(diags, err)
}

policyToSet, err = structure.NormalizeJsonString(policyToSet)

if err != nil {
return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policyToSet, err)
return sdkdiag.AppendFromErr(diags, err)
}

d.Set("policy", policyToSet)
Expand All @@ -359,18 +362,25 @@ func resourceVPCEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta

if d.HasChange("auto_accept") && d.Get("auto_accept").(bool) && d.Get("state").(string) == vpcEndpointStatePendingAcceptance {
if err := vpcEndpointAccept(ctx, conn, d.Id(), d.Get("service_name").(string), d.Timeout(schema.TimeoutUpdate)); err != nil {
return sdkdiag.AppendErrorf(diags, "updating EC2 VPC Endpoint (%s): %s", d.Get("service_name").(string), err)
return sdkdiag.AppendFromErr(diags, err)
}
}

if d.HasChanges("dns_options", "ip_address_type", "policy", "private_dns_enabled", "security_group_ids", "route_table_ids", "subnet_ids") {
privateDNSEnabled := d.Get("private_dns_enabled").(bool)
input := &ec2.ModifyVpcEndpointInput{
VpcEndpointId: aws.String(d.Id()),
}

if d.HasChange("dns_options") {
if v, ok := d.GetOk("dns_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
input.DnsOptions = expandDNSOptionsSpecification(v.([]interface{})[0].(map[string]interface{}))
tfMap := v.([]interface{})[0].(map[string]interface{})
apiObject := expandDNSOptionsSpecification(tfMap)
if privateDNSEnabled {
// Always send PrivateDnsOnlyForInboundResolverEndpoint on update.
apiObject.PrivateDnsOnlyForInboundResolverEndpoint = aws.Bool(tfMap["private_dns_only_for_inbound_resolver_endpoint"].(bool))
}
input.DnsOptions = apiObject
}
}

Expand All @@ -379,7 +389,7 @@ func resourceVPCEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta
}

if d.HasChange("private_dns_enabled") {
input.PrivateDnsEnabled = aws.Bool(d.Get("private_dns_enabled").(bool))
input.PrivateDnsEnabled = aws.Bool(privateDNSEnabled)
}

input.AddRouteTableIds, input.RemoveRouteTableIds = flattenAddAndRemoveStringLists(d, "route_table_ids")
Expand All @@ -393,7 +403,7 @@ func resourceVPCEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta
policy, err := structure.NormalizeJsonString(d.Get("policy"))

if err != nil {
return sdkdiag.AppendErrorf(diags, "policy contains invalid JSON: %s", err)
return sdkdiag.AppendFromErr(diags, err)
}

if policy == "" {
Expand Down Expand Up @@ -482,6 +492,10 @@ func expandDNSOptionsSpecification(tfMap map[string]interface{}) *ec2.DnsOptions
apiObject.DnsRecordIpType = aws.String(v)
}

if v, ok := tfMap["private_dns_only_for_inbound_resolver_endpoint"].(bool); ok && v {
apiObject.PrivateDnsOnlyForInboundResolverEndpoint = aws.Bool(v)
}

return apiObject
}

Expand Down Expand Up @@ -532,6 +546,10 @@ func flattenDNSOptions(apiObject *ec2.DnsOptions) map[string]interface{} {
tfMap["dns_record_ip_type"] = aws.StringValue(v)
}

if v := apiObject.PrivateDnsOnlyForInboundResolverEndpoint; v != nil {
tfMap["private_dns_only_for_inbound_resolver_endpoint"] = aws.BoolValue(v)
}

return tfMap
}

Expand Down
97 changes: 97 additions & 0 deletions internal/service/ec2/vpc_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func TestAccVPCEndpoint_interfaceBasic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "0"),
resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"),
resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "network_interface_ids.#", "0"),
acctest.CheckResourceAttrAccountID(resourceName, "owner_id"),
Expand All @@ -103,6 +104,51 @@ func TestAccVPCEndpoint_interfaceBasic(t *testing.T) {
})
}

func TestAccVPCEndpoint_interfacePrivateDNS(t *testing.T) {
ctx := acctest.Context(t)
var endpoint ec2.VpcEndpoint
resourceName := "aws_vpc_endpoint.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckVPCEndpointDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccVPCEndpointConfig_interfacePrivateDNS(rName, true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint),
acctest.CheckResourceAttrGreaterThanValue(resourceName, "cidr_blocks.#", 0),
resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "0"),
resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "true"),
resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "true"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccVPCEndpointConfig_interfacePrivateDNS(rName, false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint),
acctest.CheckResourceAttrGreaterThanValue(resourceName, "cidr_blocks.#", 0),
resource.TestCheckResourceAttr(resourceName, "dns_entry.#", "0"),
resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"),
resource.TestCheckResourceAttr(resourceName, "private_dns_enabled", "true"),
),
},
},
})
}

func TestAccVPCEndpoint_disappears(t *testing.T) {
ctx := acctest.Context(t)
var endpoint ec2.VpcEndpoint
Expand Down Expand Up @@ -323,6 +369,7 @@ func TestAccVPCEndpoint_ipAddressType(t *testing.T) {
testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint),
resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "ipv4"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"),
resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"),
),
},
Expand All @@ -338,6 +385,7 @@ func TestAccVPCEndpoint_ipAddressType(t *testing.T) {
testAccCheckVPCEndpointExists(ctx, resourceName, &endpoint),
resource.TestCheckResourceAttr(resourceName, "dns_options.#", "1"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.dns_record_ip_type", "dualstack"),
resource.TestCheckResourceAttr(resourceName, "dns_options.0.private_dns_only_for_inbound_resolver_endpoint", "false"),
resource.TestCheckResourceAttr(resourceName, "ip_address_type", "dualstack"),
),
},
Expand Down Expand Up @@ -694,6 +742,51 @@ resource "aws_vpc_endpoint" "test" {
`, rName)
}

func testAccVPCEndpointConfig_interfacePrivateDNS(rName string, privateDNSOnlyForInboundResolverEndpoint bool) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = %[1]q
}
}
data "aws_region" "current" {}
resource "aws_vpc_endpoint" "gateway" {
vpc_id = aws_vpc.test.id
service_name = "com.amazonaws.${data.aws_region.current.name}.s3"
tags = {
Name = %[1]q
}
}
resource "aws_vpc_endpoint" "test" {
vpc_id = aws_vpc.test.id
service_name = "com.amazonaws.${data.aws_region.current.name}.s3"
private_dns_enabled = true
vpc_endpoint_type = "Interface"
ip_address_type = "ipv4"
dns_options {
dns_record_ip_type = "ipv4"
private_dns_only_for_inbound_resolver_endpoint = %[2]t
}
tags = {
Name = %[1]q
}
# To set PrivateDnsOnlyForInboundResolverEndpoint to true, the VPC vpc-abcd1234 must have a Gateway endpoint for the service.
depends_on = [aws_vpc_endpoint.gateway]
}
`, rName, privateDNSOnlyForInboundResolverEndpoint)
}

func testAccVPCEndpointConfig_ipAddressType(rName, addressType string) string {
return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_baseSupportedIPAddressTypes(rName), fmt.Sprintf(`
resource "aws_vpc_endpoint_service" "test" {
Expand All @@ -717,6 +810,10 @@ resource "aws_vpc_endpoint" "test" {
dns_options {
dns_record_ip_type = %[2]q
}
tags = {
Name = %[1]q
}
}
`, rName, addressType))
}
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/vpc_endpoint.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ If no security groups are specified, the VPC's [default security group](https://
### dns_options

* `dns_record_ip_type` - (Optional) The DNS records created for the endpoint. Valid values are `ipv4`, `dualstack`, `service-defined`, and `ipv6`.
* `private_dns_only_for_inbound_resolver_endpoint` - (Optional) Indicates whether to enable private DNS only for inbound endpoints. This option is available only for services that support both gateway and interface endpoints. It routes traffic that originates from the VPC to the gateway endpoint and traffic that originates from on-premises to the interface endpoint. Can only be specified if `private_dns_enabled` is `true`.

## Timeouts

Expand Down

0 comments on commit ba85a49

Please sign in to comment.