From d8912bcaba675e796b66841c4cedd16d7f5f57cf Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Tue, 12 Feb 2019 07:39:19 -0600 Subject: [PATCH 01/44] initial commit --- aws/resource_aws_ec2_client_vpn_endpoint.go | 166 ++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index dc0b5694a2b..9950630e1e6 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -1,12 +1,14 @@ package aws import ( + "bytes" "fmt" "log" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -100,6 +102,32 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, + "authorization_rule": { + Type: schema.TypeSet, + Optional: true, + Set: resourceAwsAuthRuleHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "target_network_cidr": { + Type: schema.TypeString, + Required: true, + }, + "access_group_id": { + Type: schema.TypeString, + Optional: true, + }, + "authorize_all_groups": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, "dns_name": { Type: schema.TypeString, Computed: true, @@ -191,6 +219,17 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac d.SetId(*resp.ClientVpnEndpointId) + if _, ok := d.GetOk("authorization_rule"); ok { + rules := addAuthorizationRules(d.Get("authorization_rule").(*schema.Set).List()) + + for _, r := range rules { + _, err := conn.AuthorizeClientVpnIngress(r) + if err != nil { + return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) + } + } + } + return resourceAwsEc2ClientVpnEndpointRead(d, meta) } @@ -235,6 +274,15 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ return err } + authRuleResult, err := conn.DescribeClientVpnAuthorizationRules(&ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + d.Set("authorization_rule", flattenAuthRules(authRuleResult.AuthorizationRules)) + return nil } @@ -309,6 +357,35 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) } + if d.HasChange("authorization_rule") { + o, n := d.GetChange("authorization_rule") + os := o.(*schema.Set) + ns := n.(*schema.Set) + + remove := removeAuthorizationRules(os.Difference(ns).List()) + add := addAuthorizationRules(ns.Difference(os).List()) + + if len(remove) > 0 { + for _, r := range remove { + log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) + _, err := conn.RevokeClientVpnIngress(r) + if err != nil { + return fmt.Errorf("Failure removing outdated Client VPN authorization rules: %s", err) + } + } + } + + if len(add) > 0 { + for _, r := range add { + log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) + _, err := conn.AuthorizeClientVpnIngress(r) + if err != nil { + return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) + } + } + } + } + return resourceAwsEc2ClientVpnEndpointRead(d, meta) } @@ -335,3 +412,92 @@ func flattenAuthOptsConfig(aopts []*ec2.ClientVpnAuthentication) []map[string]in m["type"] = *aopts[0].Type return []map[string]interface{}{m} } + +func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + l := map[string]interface{}{ + "target_network_cidr": *i.DestinationCidr, + } + + if i.Description != nil { + l["description"] = aws.String(*i.Description) + } + if i.GroupId != nil { + l["access_group_id"] = aws.String(*i.GroupId) + } + if i.AccessAll != nil { + l["authorize_all_groups"] = aws.Bool(*i.AccessAll) + } + + result = append(result, l) + } + return result +} + +func addAuthorizationRules(configured []interface{}) []*ec2.AuthorizeClientVpnIngressInput { + rules := make([]*ec2.AuthorizeClientVpnIngressInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + rule := &ec2.AuthorizeClientVpnIngressInput{} + + rule.ClientVpnEndpointId = aws.String(item["id"].(string)) + rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) + + if item["description"].(string) != "" { + rule.Description = aws.String(item["description"].(string)) + } + + if item["access_group_id"].(string) != "" { + rule.AccessGroupId = aws.String(item["access_group_id"].(string)) + } + + if item["authorize_all_groups"] != nil { + rule.AuthorizeAllGroups = aws.Bool(item["authorize_all_groups"].(bool)) + } + + rules = append(rules, rule) + } + + return rules +} + +func removeAuthorizationRules(configured []interface{}) []*ec2.RevokeClientVpnIngressInput { + rules := make([]*ec2.RevokeClientVpnIngressInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + rule := &ec2.RevokeClientVpnIngressInput{} + + rule.ClientVpnEndpointId = aws.String(item["id"].(string)) + rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) + + rules = append(rules, rule) + } + + return rules +} + +func resourceAwsAuthRuleHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["description"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["target_network_cidr"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["access_group_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["authorize_all_groups"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(bool))) + } + + return hashcode.String(buf.String()) +} From a97816ba31136127d400a4a6422cf0ac23e518d0 Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Tue, 12 Feb 2019 10:12:12 -0600 Subject: [PATCH 02/44] another progess commit --- ...source_aws_ec2_client_vpn_endpoint_test.go | 78 +++++++++++++++++++ .../r/ec2_client_vpn_endpoint.html.markdown | 10 +++ 2 files changed, 88 insertions(+) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 99caba0ede5..4e9173a4630 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -126,6 +126,38 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } +func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { + rStr := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersWithTLS, + CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnEndpointConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + ), + }, + + { + Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + ), + }, + + { + ResourceName: "aws_ec2_client_vpn_endpoint.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"dns_servers"}, + }, + }, + }) +} + func testAccCheckAwsEc2ClientVpnEndpointDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -383,3 +415,49 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } `, rName) } + +func testAccEc2ClientVpnEndpointWithAuthorizationRules(rName string) string { + return fmt.Sprintf(` +resource "tls_private_key" "example" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "example" { + key_algorithm = "RSA" + private_key_pem = "${tls_private_key.example.private_key_pem}" + + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } + + validity_period_hours = 12 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} + +resource "aws_acm_certificate" "cert" { + private_key = "${tls_private_key.example.private_key_pem}" + certificate_body = "${tls_self_signed_cert.example.cert_pem}" +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.cert.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" + } + + connection_log_options { + enabled = false + } +} +`, rName) +} \ No newline at end of file diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 452e62154dc..d0309dcb333 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -43,6 +43,7 @@ The following arguments are supported: * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. +* `authorization_rule` - (Optional) Information about granting access to networks via this Client VPN endpoint. Multiple definitions of this parameter can be supplied. ### `authentication_options` Argument Reference @@ -60,6 +61,15 @@ One of the following arguments must be supplied: * `cloudwatch_log_group` - (Optional) The name of the CloudWatch Logs log group. * `cloudwatch_log_stream` - (Optional) The name of the CloudWatch Logs log stream to which the connection data is published. +### `authorization_rule` Argument Reference + +One of the following arguments must be supplied: + +* `description` - (Optional) A brief description of the authorization rule. +* `target_network_cidr` - (Required) The IPv4 address range, in CIDR notation, of the network to which the authorization rule applies. +* `access_group_id` - (Optional) The ID of the Active Directory group to which the authorization rule grants access. +* `authorize_all_groups` - (Optional) Indicates whether the authorization rule grants access to all clients. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From ef5cba698e0f9657967f0ced17f6ab9668683c20 Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Thu, 14 Feb 2019 13:15:47 -0600 Subject: [PATCH 03/44] r/aws_ec2_client_vpn_endpoint: Reworked endpoint resource to include network associations and authorization rules as parameters --- aws/provider.go | 1 - aws/resource_aws_ec2_client_vpn_endpoint.go | 289 +++++++++++++++++- ...source_aws_ec2_client_vpn_endpoint_test.go | 125 +++++++- ..._aws_ec2_client_vpn_network_association.go | 175 ----------- ...ec2_client_vpn_network_association_test.go | 202 ------------ website/aws.erb | 4 - ...ient_vpn_network_association.html.markdown | 37 --- 7 files changed, 389 insertions(+), 444 deletions(-) delete mode 100644 aws/resource_aws_ec2_client_vpn_network_association.go delete mode 100644 aws/resource_aws_ec2_client_vpn_network_association_test.go delete mode 100644 website/docs/r/ec2_client_vpn_network_association.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 892003fe81d..ead5fcdaf50 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -434,7 +434,6 @@ func Provider() terraform.ResourceProvider { "aws_ebs_volume": resourceAwsEbsVolume(), "aws_ec2_capacity_reservation": resourceAwsEc2CapacityReservation(), "aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(), - "aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(), "aws_ec2_fleet": resourceAwsEc2Fleet(), "aws_ec2_transit_gateway": resourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_route": resourceAwsEc2TransitGatewayRoute(), diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 9950630e1e6..66d824efc4d 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -102,6 +102,29 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, + "network_association": { + Type: schema.TypeSet, + Optional: true, + Set: resourceAwsNetAssocHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_id": { + Type: schema.TypeString, + Required: true, + }, + "association_id": { + Type: schema.TypeString, + Computed: true, + }, + "security_groups": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, "authorization_rule": { Type: schema.TypeSet, Optional: true, @@ -123,7 +146,7 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { "authorize_all_groups": { Type: schema.TypeBool, Optional: true, - Default: false, + Default: true, }, }, }, @@ -219,8 +242,47 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac d.SetId(*resp.ClientVpnEndpointId) + if _, ok := d.GetOk("network_association"); ok { + networks, securityGroups := addNetworkAssociation(d.Id(), d.Get("network_association").(*schema.Set).List()) + + for i, n := range networks { + netResp, err := conn.AssociateClientVpnTargetNetwork(n) + if err != nil { + return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeAssociating}, + Target: []string{ec2.AssociationStatusCodeAssociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(netResp.AssociationId), d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", aws.StringValue(netResp.AssociationId)) + targetNetworkRaw, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) + } + + targetNetwork := targetNetworkRaw.(*ec2.TargetNetwork) + + if len(securityGroups[i]) > 0 { + sgReq := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{ + ClientVpnEndpointId: aws.String(d.Id()), + VpcId: targetNetwork.VpcId, + SecurityGroupIds: securityGroups[i], + } + + _, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(sgReq) + if err != nil { + return fmt.Errorf("Error applying security groups to Client VPN network association: %s", err) + } + } + } + } + if _, ok := d.GetOk("authorization_rule"); ok { - rules := addAuthorizationRules(d.Get("authorization_rule").(*schema.Set).List()) + rules := addAuthorizationRules(d.Id(), d.Get("authorization_rule").(*schema.Set).List()) for _, r := range rules { _, err := conn.AuthorizeClientVpnIngress(r) @@ -274,6 +336,15 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ return err } + netAssocResult, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + d.Set("network_association", flattenNetAssoc(netAssocResult.ClientVpnTargetNetworks)) + authRuleResult, err := conn.DescribeClientVpnAuthorizationRules(&ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(d.Id()), }) @@ -289,7 +360,40 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ func resourceAwsEc2ClientVpnEndpointDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - _, err := conn.DeleteClientVpnEndpoint(&ec2.DeleteClientVpnEndpointInput{ + netAssocResult, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + for _, n := range netAssocResult.ClientVpnTargetNetworks { + network := &ec2.DisassociateClientVpnTargetNetworkInput{} + + network.ClientVpnEndpointId = aws.String(d.Id()) + network.AssociationId = aws.String(*n.AssociationId) + + log.Printf("[DEBUG] Client VPN network association opts: %s", n) + _, err := conn.DisassociateClientVpnTargetNetwork(network) + if err != nil { + return fmt.Errorf("D Failure removing Client VPN network associations: %s \n %s", err, n) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeDisassociating}, + Target: []string{ec2.AssociationStatusCodeDisassociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(n.AssociationId), d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", aws.StringValue(n.AssociationId)) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) + } + } + + _, err = conn.DeleteClientVpnEndpoint(&ec2.DeleteClientVpnEndpointInput{ ClientVpnEndpointId: aws.String(d.Id()), }) if err != nil { @@ -357,20 +461,90 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) } + if d.HasChange("network_association") { + o, n := d.GetChange("network_association") + os := o.(*schema.Set) + ns := n.(*schema.Set) + + removeNets := removeNetworkAssociation(d.Id(), os.Difference(ns).List()) + addNets, addSGs := addNetworkAssociation(d.Id(), ns.Difference(os).List()) + + if len(removeNets) > 0 { + for _, r := range removeNets { + log.Printf("[DEBUG] Client VPN network authorization opts: %s", r) + _, err := conn.DisassociateClientVpnTargetNetwork(r) + if err != nil { + return fmt.Errorf("Failure removing outdated Client VPN network authorizations: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeDisassociating}, + Target: []string{ec2.AssociationStatusCodeDisassociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(r.AssociationId), d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", aws.StringValue(r.AssociationId)) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) + } + } + } + + if len(addNets) > 0 { + for i, a := range addNets { + log.Printf("[DEBUG] Client VPN network authorization opts: %s", a) + addResp, err := conn.AssociateClientVpnTargetNetwork(a) + if err != nil { + return fmt.Errorf("U Failure adding new Client VPN network authorizations: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeAssociating}, + Target: []string{ec2.AssociationStatusCodeAssociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(addResp.AssociationId), d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", aws.StringValue(addResp.AssociationId)) + targetNetworkRaw, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) + } + + targetNetwork := targetNetworkRaw.(*ec2.TargetNetwork) + + if len(addSGs[i]) > 0 { + sgReq := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{ + ClientVpnEndpointId: aws.String(d.Id()), + VpcId: targetNetwork.VpcId, + SecurityGroupIds: addSGs[i], + } + + _, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(sgReq) + if err != nil { + return fmt.Errorf("Error applying security groups to Client VPN network association: %s", err) + } + } + } + } + } + if d.HasChange("authorization_rule") { o, n := d.GetChange("authorization_rule") os := o.(*schema.Set) ns := n.(*schema.Set) - remove := removeAuthorizationRules(os.Difference(ns).List()) - add := addAuthorizationRules(ns.Difference(os).List()) + remove := removeAuthorizationRules(d.Id(), os.Difference(ns).List()) + add := addAuthorizationRules(d.Id(), ns.Difference(os).List()) if len(remove) > 0 { for _, r := range remove { log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) _, err := conn.RevokeClientVpnIngress(r) if err != nil { - return fmt.Errorf("Failure removing outdated Client VPN authorization rules: %s", err) + return fmt.Errorf("U Failure removing outdated Client VPN authorization rules: %s", err) } } } @@ -380,7 +554,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) _, err := conn.AuthorizeClientVpnIngress(r) if err != nil { - return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) + return fmt.Errorf("U Failure adding new Client VPN authorization rules: %s", err) } } } @@ -389,6 +563,29 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return resourceAwsEc2ClientVpnEndpointRead(d, meta) } +func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(cvepID), + AssociationIds: []*string{aws.String(cvnaID)}, + }) + + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + return 42, ec2.AssociationStatusCodeDisassociated, nil + } + + if err != nil { + return nil, "", err + } + + if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { + return 42, ec2.AssociationStatusCodeDisassociated, nil + } + + return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil + } +} + func flattenConnLoggingConfig(lopts *ec2.ConnectionLogResponseOptions) []map[string]interface{} { m := make(map[string]interface{}) if lopts.CloudwatchLogGroup != nil { @@ -413,6 +610,22 @@ func flattenAuthOptsConfig(aopts []*ec2.ClientVpnAuthentication) []map[string]in return []map[string]interface{}{m} } +func flattenNetAssoc(list []*ec2.TargetNetwork) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + l := map[string]interface{}{ + "association_id": *i.AssociationId, + } + + if i.SecurityGroups != nil { + l["security_groups"] = i.SecurityGroups + } + + result = append(result, l) + } + return result +} + func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) for _, i := range list { @@ -435,15 +648,16 @@ func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { return result } -func addAuthorizationRules(configured []interface{}) []*ec2.AuthorizeClientVpnIngressInput { +func addAuthorizationRules(eid string, configured []interface{}) []*ec2.AuthorizeClientVpnIngressInput { rules := make([]*ec2.AuthorizeClientVpnIngressInput, 0, len(configured)) for _, i := range configured { item := i.(map[string]interface{}) rule := &ec2.AuthorizeClientVpnIngressInput{} - rule.ClientVpnEndpointId = aws.String(item["id"].(string)) + rule.ClientVpnEndpointId = aws.String(eid) rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) + rule.AuthorizeAllGroups = aws.Bool(item["authorize_all_groups"].(bool)) if item["description"].(string) != "" { rule.Description = aws.String(item["description"].(string)) @@ -453,24 +667,20 @@ func addAuthorizationRules(configured []interface{}) []*ec2.AuthorizeClientVpnIn rule.AccessGroupId = aws.String(item["access_group_id"].(string)) } - if item["authorize_all_groups"] != nil { - rule.AuthorizeAllGroups = aws.Bool(item["authorize_all_groups"].(bool)) - } - rules = append(rules, rule) } return rules } -func removeAuthorizationRules(configured []interface{}) []*ec2.RevokeClientVpnIngressInput { +func removeAuthorizationRules(eid string, configured []interface{}) []*ec2.RevokeClientVpnIngressInput { rules := make([]*ec2.RevokeClientVpnIngressInput, 0, len(configured)) for _, i := range configured { item := i.(map[string]interface{}) rule := &ec2.RevokeClientVpnIngressInput{} - rule.ClientVpnEndpointId = aws.String(item["id"].(string)) + rule.ClientVpnEndpointId = aws.String(eid) rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) rules = append(rules, rule) @@ -501,3 +711,52 @@ func resourceAwsAuthRuleHash(v interface{}) int { return hashcode.String(buf.String()) } + +func addNetworkAssociation(eid string, configured []interface{}) ([]*ec2.AssociateClientVpnTargetNetworkInput, [][]*string) { + networks := make([]*ec2.AssociateClientVpnTargetNetworkInput, 0, len(configured)) + securityGroups := make([][]*string, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + network := &ec2.AssociateClientVpnTargetNetworkInput{} + + network.ClientVpnEndpointId = aws.String(eid) + network.SubnetId = aws.String(item["subnet_id"].(string)) + + networks = append(networks, network) + securityGroups = append(securityGroups, expandStringSet(item["security_groups"].(*schema.Set))) + } + + return networks, securityGroups +} + +func removeNetworkAssociation(eid string, configured []interface{}) []*ec2.DisassociateClientVpnTargetNetworkInput { + networks := make([]*ec2.DisassociateClientVpnTargetNetworkInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + network := &ec2.DisassociateClientVpnTargetNetworkInput{} + + network.ClientVpnEndpointId = aws.String(eid) + network.AssociationId = aws.String(item["association_id"].(string)) + + networks = append(networks, network) + } + + return networks +} + +func resourceAwsNetAssocHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["subnet_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["security_groups"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(*schema.Set).List())) + } + + return hashcode.String(buf.String()) +} diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 4e9173a4630..27e8aa89aaa 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -126,7 +126,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { +func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { rStr := acctest.RandString(5) resource.Test(t, resource.TestCase{ @@ -140,19 +140,35 @@ func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), ), }, - { - Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), + Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), Check: resource.ComposeTestCheckFunc( testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), ), }, + }, + }) +} +func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { + rStr := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersWithTLS, + CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, + Steps: []resource.TestStep{ { - ResourceName: "aws_ec2_client_vpn_endpoint.test", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"dns_servers"}, + Config: testAccEc2ClientVpnEndpointConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + ), + }, + { + Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + ), }, }, }) @@ -416,8 +432,23 @@ resource "aws_ec2_client_vpn_endpoint" "test" { `, rName) } -func testAccEc2ClientVpnEndpointWithAuthorizationRules(rName string) string { +func testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rName string) string { return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } +} +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + map_public_ip_on_launch = true + tags = { + Name = "tf-acc-subnet-%s" + } +} + resource "tls_private_key" "example" { algorithm = "RSA" } @@ -457,7 +488,81 @@ resource "aws_ec2_client_vpn_endpoint" "test" { connection_log_options { enabled = false + } + + network_association { + subnet_id = "${aws_subnet.test.id}" + } +} +`, rName, rName, rName) +} + +func testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } +} +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + map_public_ip_on_launch = true + tags = { + Name = "tf-acc-subnet-%s" + } +} + +resource "tls_private_key" "example" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "example" { + key_algorithm = "RSA" + private_key_pem = "${tls_private_key.example.private_key_pem}" + + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" } + + validity_period_hours = 12 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} + +resource "aws_acm_certificate" "cert" { + private_key = "${tls_private_key.example.private_key_pem}" + certificate_body = "${tls_self_signed_cert.example.cert_pem}" +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.cert.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" + } + + connection_log_options { + enabled = false + } + + network_association { + subnet_id = "${aws_subnet.test.id}" + } + + authorization_rule { + description = "example auth rule" + target_network_cidr = "10.1.1.0/24" + } +} +`, rName, rName, rName) } -`, rName) -} \ No newline at end of file diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go deleted file mode 100644 index 6210a0c0257..00000000000 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ /dev/null @@ -1,175 +0,0 @@ -package aws - -import ( - "fmt" - "log" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/helper/schema" -) - -func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, - Read: resourceAwsEc2ClientVpnNetworkAssociationRead, - Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, - - Schema: map[string]*schema.Schema{ - "client_vpn_endpoint_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "subnet_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "security_groups": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Computed: true, - }, - "status": { - Type: schema.TypeString, - Computed: true, - }, - "vpc_id": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func resourceAwsEc2ClientVpnNetworkAssociationCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - req := &ec2.AssociateClientVpnTargetNetworkInput{ - ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), - SubnetId: aws.String(d.Get("subnet_id").(string)), - } - - log.Printf("[DEBUG] Creating Client VPN network association: %#v", req) - resp, err := conn.AssociateClientVpnTargetNetwork(req) - if err != nil { - return fmt.Errorf("Error creating Client VPN network association: %s", err) - } - - d.SetId(*resp.AssociationId) - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeAssociating}, - Target: []string{ec2.AssociationStatusCodeAssociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), - Timeout: d.Timeout(schema.TimeoutCreate), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id()) - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) - } - - return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta) -} - -func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - var err error - - result, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), - AssociationIds: []*string{aws.String(d.Id())}, - }) - - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { - log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return fmt.Errorf("Error reading Client VPN network association: %s", err) - } - - if result == nil || len(result.ClientVpnTargetNetworks) == 0 || result.ClientVpnTargetNetworks[0] == nil { - log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if result.ClientVpnTargetNetworks[0].Status != nil && aws.StringValue(result.ClientVpnTargetNetworks[0].Status.Code) == ec2.AssociationStatusCodeDisassociated { - log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - d.Set("client_vpn_endpoint_id", result.ClientVpnTargetNetworks[0].ClientVpnEndpointId) - d.Set("status", result.ClientVpnTargetNetworks[0].Status.Code) - d.Set("subnet_id", result.ClientVpnTargetNetworks[0].TargetNetworkId) - d.Set("vpc_id", result.ClientVpnTargetNetworks[0].VpcId) - - if err := d.Set("security_groups", aws.StringValueSlice(result.ClientVpnTargetNetworks[0].SecurityGroups)); err != nil { - return fmt.Errorf("error setting security_groups: %s", err) - } - - return nil -} - -func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - _, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{ - ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), - AssociationId: aws.String(d.Id()), - }) - - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { - return nil - } - - if err != nil { - return fmt.Errorf("Error deleting Client VPN network association: %s", err) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeDisassociating}, - Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), - Timeout: d.Timeout(schema.TimeoutDelete), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", d.Id()) - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) - } - - return nil -} - -func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(cvepID), - AssociationIds: []*string{aws.String(cvnaID)}, - }) - - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { - return 42, ec2.AssociationStatusCodeDisassociated, nil - } - - if err != nil { - return nil, "", err - } - - if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { - return 42, ec2.AssociationStatusCodeDisassociated, nil - } - - return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil - } -} diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go deleted file mode 100644 index 7d1f13c96dc..00000000000 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { - var assoc1 ec2.TargetNetwork - rStr := acctest.RandString(5) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProvidersWithTLS, - CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), - ), - }, - }, - }) -} - -func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { - var assoc1 ec2.TargetNetwork - rStr := acctest.RandString(5) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProvidersWithTLS, - CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), - testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(&assoc1), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_ec2_client_vpn_network_association" { - continue - } - - resp, _ := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), - AssociationIds: []*string{aws.String(rs.Primary.ID)}, - }) - - for _, v := range resp.ClientVpnTargetNetworks { - if *v.AssociationId == rs.Primary.ID && !(*v.Status.Code == "Disassociated") { - return fmt.Errorf("[DESTROY ERROR] Client VPN network association (%s) not deleted", rs.Primary.ID) - } - } - } - - return nil -} - -func testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(targetNetwork *ec2.TargetNetwork) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - _, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{ - AssociationId: targetNetwork.AssociationId, - ClientVpnEndpointId: targetNetwork.ClientVpnEndpointId, - }) - - if err != nil { - return err - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeDisassociating}, - Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(targetNetwork.AssociationId), aws.StringValue(targetNetwork.ClientVpnEndpointId)), - Timeout: 10 * time.Minute, - } - - _, err = stateConf.WaitForState() - - return err - } -} - -func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2.TargetNetwork) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), - AssociationIds: []*string{aws.String(rs.Primary.ID)}, - }) - - if err != nil { - return fmt.Errorf("Error reading Client VPN network association (%s): %s", rs.Primary.ID, err) - } - - for _, a := range resp.ClientVpnTargetNetworks { - if *a.AssociationId == rs.Primary.ID && !(*a.Status.Code == "Disassociated") { - *assoc = *a - return nil - } - } - - return fmt.Errorf("Client VPN network association (%s) not found", rs.Primary.ID) - } -} - -func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-subnet-%s" - } -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - map_public_ip_on_launch = true - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" - } - - connection_log_options { - enabled = false - } -} - -resource "aws_ec2_client_vpn_network_association" "test" { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - subnet_id = "${aws_subnet.test.id}" -} -`, rName, rName, rName) -} diff --git a/website/aws.erb b/website/aws.erb index 6d76006cc06..ddcaac69adf 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1122,10 +1122,6 @@ aws_ec2_client_vpn_endpoint - > - aws_ec2_client_vpn_network_association - - > aws_ec2_fleet diff --git a/website/docs/r/ec2_client_vpn_network_association.html.markdown b/website/docs/r/ec2_client_vpn_network_association.html.markdown deleted file mode 100644 index 7533e2ffcd9..00000000000 --- a/website/docs/r/ec2_client_vpn_network_association.html.markdown +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: "aws" -page_title: "AWS: aws_ec2_client_vpn_network_association" -sidebar_current: "docs-aws-resource-ec2-client-vpn-network-association" -description: |- - Provides network associations for AWS Client VPN endpoints. ---- - -# aws_ec2_client_vpn_network_association - -Provides network associations for AWS Client VPN endpoints. For more information on usage, please see the -[AWS Client VPN Administrator's Guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html). - -## Example Usage - -```hcl -resource "aws_ec2_client_vpn_network_association" "example" { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}" - subnet_id = "${aws_subnet.example.id}" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `client_vpn_endpoint_id` - (Required) The ID of the Client VPN endpoint. -* `subnet_id` - (Required) The ID of the subnet to associate with the Client VPN endpoint. - -## Attributes Reference - -In addition to all arguments above, the following attributes are exported: - -* `id` - The unique ID of the target network association. -* `security_groups` - The IDs of the security groups applied to the target network association. -* `status` - The current state of the target network association. -* `vpc_id` - The ID of the VPC in which the target network (subnet) is located. From 598d1cbdce3eb9bab0ec2ab636a76bbd40ab1f4e Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Thu, 14 Feb 2019 14:06:31 -0600 Subject: [PATCH 04/44] updating doc with network_association --- aws/resource_aws_ec2_client_vpn_endpoint.go | 98 +++++++++---------- .../r/ec2_client_vpn_endpoint.html.markdown | 8 ++ 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 66d824efc4d..8fe987b9299 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -626,6 +626,55 @@ func flattenNetAssoc(list []*ec2.TargetNetwork) []map[string]interface{} { return result } +func addNetworkAssociation(eid string, configured []interface{}) ([]*ec2.AssociateClientVpnTargetNetworkInput, [][]*string) { + networks := make([]*ec2.AssociateClientVpnTargetNetworkInput, 0, len(configured)) + securityGroups := make([][]*string, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + network := &ec2.AssociateClientVpnTargetNetworkInput{} + + network.ClientVpnEndpointId = aws.String(eid) + network.SubnetId = aws.String(item["subnet_id"].(string)) + + networks = append(networks, network) + securityGroups = append(securityGroups, expandStringSet(item["security_groups"].(*schema.Set))) + } + + return networks, securityGroups +} + +func removeNetworkAssociation(eid string, configured []interface{}) []*ec2.DisassociateClientVpnTargetNetworkInput { + networks := make([]*ec2.DisassociateClientVpnTargetNetworkInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + network := &ec2.DisassociateClientVpnTargetNetworkInput{} + + network.ClientVpnEndpointId = aws.String(eid) + network.AssociationId = aws.String(item["association_id"].(string)) + + networks = append(networks, network) + } + + return networks +} + +func resourceAwsNetAssocHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["subnet_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["security_groups"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(*schema.Set).List())) + } + + return hashcode.String(buf.String()) +} + func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) for _, i := range list { @@ -711,52 +760,3 @@ func resourceAwsAuthRuleHash(v interface{}) int { return hashcode.String(buf.String()) } - -func addNetworkAssociation(eid string, configured []interface{}) ([]*ec2.AssociateClientVpnTargetNetworkInput, [][]*string) { - networks := make([]*ec2.AssociateClientVpnTargetNetworkInput, 0, len(configured)) - securityGroups := make([][]*string, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - network := &ec2.AssociateClientVpnTargetNetworkInput{} - - network.ClientVpnEndpointId = aws.String(eid) - network.SubnetId = aws.String(item["subnet_id"].(string)) - - networks = append(networks, network) - securityGroups = append(securityGroups, expandStringSet(item["security_groups"].(*schema.Set))) - } - - return networks, securityGroups -} - -func removeNetworkAssociation(eid string, configured []interface{}) []*ec2.DisassociateClientVpnTargetNetworkInput { - networks := make([]*ec2.DisassociateClientVpnTargetNetworkInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - network := &ec2.DisassociateClientVpnTargetNetworkInput{} - - network.ClientVpnEndpointId = aws.String(eid) - network.AssociationId = aws.String(item["association_id"].(string)) - - networks = append(networks, network) - } - - return networks -} - -func resourceAwsNetAssocHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["subnet_id"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["security_groups"]; ok { - buf.WriteString(fmt.Sprintf("%v-", v.(*schema.Set).List())) - } - - return hashcode.String(buf.String()) -} diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index d0309dcb333..c88967cb2ed 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -43,6 +43,7 @@ The following arguments are supported: * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. +* `network_association` - (Optional) Information about associating networks to this Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `authorization_rule` - (Optional) Information about granting access to networks via this Client VPN endpoint. Multiple definitions of this parameter can be supplied. ### `authentication_options` Argument Reference @@ -61,6 +62,13 @@ One of the following arguments must be supplied: * `cloudwatch_log_group` - (Optional) The name of the CloudWatch Logs log group. * `cloudwatch_log_stream` - (Optional) The name of the CloudWatch Logs log stream to which the connection data is published. +### `network_association` Argument Reference + +One of the following arguments must be supplied: + +* `subnet_id` - (Required) The ID of the subnet to associate with the Client VPN endpoint. +* `security_groups` - (Optional) The IDs of the security groups to apply to the associated target network. Up to 5 security groups can be applied to an associated target network. + ### `authorization_rule` Argument Reference One of the following arguments must be supplied: From 6638498c6c14b7cdf66ccf75380e7adc625f95b9 Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Thu, 14 Feb 2019 20:59:50 -0600 Subject: [PATCH 05/44] more acc test tweaks --- aws/resource_aws_ec2_client_vpn_endpoint.go | 6 +- ...source_aws_ec2_client_vpn_endpoint_test.go | 211 ++++-------------- 2 files changed, 42 insertions(+), 175 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 8fe987b9299..ae9853152a9 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -497,7 +497,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac log.Printf("[DEBUG] Client VPN network authorization opts: %s", a) addResp, err := conn.AssociateClientVpnTargetNetwork(a) if err != nil { - return fmt.Errorf("U Failure adding new Client VPN network authorizations: %s", err) + return fmt.Errorf("Failure adding new Client VPN network authorizations: %s", err) } stateConf := &resource.StateChangeConf{ @@ -544,7 +544,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) _, err := conn.RevokeClientVpnIngress(r) if err != nil { - return fmt.Errorf("U Failure removing outdated Client VPN authorization rules: %s", err) + return fmt.Errorf("Failure removing outdated Client VPN authorization rules: %s", err) } } } @@ -554,7 +554,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) _, err := conn.AuthorizeClientVpnIngress(r) if err != nil { - return fmt.Errorf("U Failure adding new Client VPN authorization rules: %s", err) + return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) } } } diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 27e8aa89aaa..fb8814a4d26 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -207,8 +207,9 @@ func testAccCheckAwsEc2ClientVpnEndpointExists(name string) resource.TestCheckFu } } -func testAccEc2ClientVpnEndpointConfig(rName string) string { - return fmt.Sprintf(` +const testAccEc2ClientVpnEndpointBaseConfig = ` +data "aws_availability_zones" "available" {} + resource "tls_private_key" "example" { algorithm = "RSA" } @@ -235,14 +236,17 @@ resource "aws_acm_certificate" "cert" { private_key = "${tls_private_key.example.private_key_pem}" certificate_body = "${tls_self_signed_cert.example.cert_pem}" } +` +func testAccEc2ClientVpnEndpointConfig(rName string) string { + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" authentication_options { - type = "certificate-authentication" + type = "certificate-authentication" root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" } @@ -254,34 +258,7 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } func testAccEc2ClientVpnEndpointConfigWithLogGroup(rName string) string { - return fmt.Sprintf(` -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_cloudwatch_log_group" "lg" { name = "terraform-testacc-clientvpn-loggroup-%s" } @@ -292,18 +269,18 @@ resource "aws_cloudwatch_log_stream" "ls" { } resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" authentication_options { - type = "certificate-authentication" + type = "certificate-authentication" root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" } connection_log_options { - enabled = true - cloudwatch_log_group = "${aws_cloudwatch_log_group.lg.name}" + enabled = true + cloudwatch_log_group = "${aws_cloudwatch_log_group.lg.name}" cloudwatch_log_stream = "${aws_cloudwatch_log_stream.ls.name}" } } @@ -311,43 +288,16 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } func testAccEc2ClientVpnEndpointConfigWithDNSServers(rName string) string { - return fmt.Sprintf(` -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" dns_servers = ["8.8.8.8", "8.8.4.4"] authentication_options { - type = "certificate-authentication" + type = "certificate-authentication" root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" } @@ -359,9 +309,7 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } func testAccEc2ClientVpnEndpointConfigWithMicrosoftAD(rName string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" {} - + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" } @@ -379,49 +327,22 @@ resource "aws_subnet" "test2" { } resource "aws_directory_service_directory" "test" { - name = "corp.notexample.com" + name = "corp.notexample.com" password = "SuperSecretPassw0rd" - type = "MicrosoftAD" + type = "MicrosoftAD" vpc_settings { vpc_id = "${aws_vpc.test.id}" subnet_ids = ["${aws_subnet.test1.id}", "${aws_subnet.test2.id}"] } } -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" authentication_options { - type = "directory-service-authentication" + type = "directory-service-authentication" active_directory_id = "${aws_directory_service_directory.test.id}" } @@ -433,7 +354,7 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } func testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rName string) string { - return fmt.Sprintf(` + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { @@ -441,48 +362,21 @@ resource "aws_vpc" "test" { } } resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - map_public_ip_on_launch = true + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" tags = { Name = "tf-acc-subnet-%s" } } -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" authentication_options { - type = "certificate-authentication" + type = "certificate-authentication" root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" } @@ -498,7 +392,7 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } func testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rName string) string { - return fmt.Sprintf(` + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { @@ -506,48 +400,21 @@ resource "aws_vpc" "test" { } } resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - map_public_ip_on_launch = true + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" tags = { Name = "tf-acc-subnet-%s" } } -resource "tls_private_key" "example" { - algorithm = "RSA" -} - -resource "tls_self_signed_cert" "example" { - key_algorithm = "RSA" - private_key_pem = "${tls_private_key.example.private_key_pem}" - - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } - - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] -} - -resource "aws_acm_certificate" "cert" { - private_key = "${tls_private_key.example.private_key_pem}" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" -} - resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" + description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.cert.arn}" - client_cidr_block = "10.0.0.0/16" + client_cidr_block = "10.0.0.0/16" authentication_options { - type = "certificate-authentication" + type = "certificate-authentication" root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" } From 7995ce7eb33ed0c0c714ff663c6fd713c7bf4fdb Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Sat, 16 Feb 2019 13:45:04 -0600 Subject: [PATCH 06/44] more acctest changes --- aws/resource_aws_ec2_client_vpn_endpoint.go | 13 ++-- ...source_aws_ec2_client_vpn_endpoint_test.go | 62 ++++++++++++------- .../r/ec2_client_vpn_endpoint.html.markdown | 1 - 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index ae9853152a9..e08b873e847 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -112,16 +112,16 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { Type: schema.TypeString, Required: true, }, - "association_id": { - Type: schema.TypeString, - Computed: true, - }, "security_groups": { Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "association_id": { + Type: schema.TypeString, + Computed: true, + }, }, }, }, @@ -155,10 +155,6 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "status": { - Type: schema.TypeString, - Computed: true, - }, }, } } @@ -324,7 +320,6 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ d.Set("server_certificate_arn", result.ClientVpnEndpoints[0].ServerCertificateArn) d.Set("transport_protocol", result.ClientVpnEndpoints[0].TransportProtocol) d.Set("dns_name", result.ClientVpnEndpoints[0].DnsName) - d.Set("status", result.ClientVpnEndpoints[0].Status) err = d.Set("authentication_options", flattenAuthOptsConfig(result.ClientVpnEndpoints[0].AuthenticationOptions)) if err != nil { diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index fb8814a4d26..b9b15368be3 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -13,6 +13,7 @@ import ( func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -22,14 +23,21 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), - resource.TestCheckResourceAttr("aws_ec2_client_vpn_endpoint.test", "authentication_options.#", "1"), - resource.TestCheckResourceAttr("aws_ec2_client_vpn_endpoint.test", "authentication_options.0.type", "certificate-authentication"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "description"), + resource.TestCheckResourceAttrSet(resourceName, "server_certificate_arn"), + resource.TestCheckResourceAttr(resourceName, "client_cidr_block", "10.0.0.0/16"), + resource.TestCheckResourceAttr(resourceName, "transport_protocol", "udp"), + resource.TestCheckResourceAttr(resourceName, "authentication_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "certificate-authentication"), + resource.TestCheckResourceAttr(resourceName, "connection_log_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "connection_log_options.0.enabled", "false"), + resource.TestCheckResourceAttrSet(resourceName, "dns_name"), ), }, { - ResourceName: "aws_ec2_client_vpn_endpoint.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -39,6 +47,7 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -48,14 +57,14 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfigWithMicrosoftAD(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), - resource.TestCheckResourceAttr("aws_ec2_client_vpn_endpoint.test", "authentication_options.#", "1"), - resource.TestCheckResourceAttr("aws_ec2_client_vpn_endpoint.test", "authentication_options.0.type", "directory-service-authentication"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "authentication_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "directory-service-authentication"), ), }, { - ResourceName: "aws_ec2_client_vpn_endpoint.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -65,6 +74,7 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -74,19 +84,23 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithLogGroup(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "connection_log_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "connection_log_options.0.enabled", "true"), + resource.TestCheckResourceAttrSet(resourceName, "connection_log_options.0.cloudwatch_log_group"), + resource.TestCheckResourceAttrSet(resourceName, "connection_log_options.0.cloudwatch_log_stream"), ), }, { - ResourceName: "aws_ec2_client_vpn_endpoint.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -96,6 +110,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -105,29 +120,23 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithDNSServers(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, - - { - ResourceName: "aws_ec2_client_vpn_endpoint.test", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"dns_servers"}, - }, }, }) } func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -137,13 +146,14 @@ func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "network_association.#", "1"), ), }, }, @@ -152,6 +162,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -161,13 +172,16 @@ func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists("aws_ec2_client_vpn_endpoint.test"), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "authorization_rule.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.description"), + resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.target_network_cidr"), ), }, }, diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index c88967cb2ed..08d66c3469e 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -84,7 +84,6 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Client VPN endpoint. * `dns_name` - The DNS name to be used by clients when establishing their VPN session. -* `status` - The current state of the Client VPN endpoint. ## Import From d0c9bbfdab6fe198c2a4298f33235ff39b4b9f2c Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Fri, 15 Mar 2019 17:12:12 -0500 Subject: [PATCH 07/44] adding route functionality --- aws/resource_aws_ec2_client_vpn_endpoint.go | 144 ++++++++++++++++++ ...source_aws_ec2_client_vpn_endpoint_test.go | 144 ++++++++++++++++++ .../r/ec2_client_vpn_endpoint.html.markdown | 9 ++ 3 files changed, 297 insertions(+) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 84fc5acc87e..ff970a81ca5 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -151,6 +151,27 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, + "route": { + Type: schema.TypeSet, + Optional: true, + Set: resourceAwsRouteHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "destination_network_cidr": { + Type: schema.TypeString, + Required: true, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, "dns_name": { Type: schema.TypeString, Computed: true, @@ -290,6 +311,17 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac } } + if _, ok := d.GetOk("route"); ok { + rules := addRoutes(d.Id(), d.Get("route").(*schema.Set).List()) + + for _, r := range rules { + _, err := conn.CreateClientVpnRoute(r) + if err != nil { + return fmt.Errorf("Failure adding new Client VPN routes: %s", err) + } + } + } + return resourceAwsEc2ClientVpnEndpointRead(d, meta) } @@ -356,6 +388,15 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ d.Set("authorization_rule", flattenAuthRules(authRuleResult.AuthorizationRules)) + routeResult, err := conn.DescribeClientVpnRoutes(&ec2.DescribeClientVpnRoutesInput{ + ClientVpnEndpointId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + d.Set("route", flattenRoutes(routeResult.Routes)) + return nil } @@ -563,6 +604,35 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac } } + if d.HasChange("route") { + o, n := d.GetChange("route") + os := o.(*schema.Set) + ns := n.(*schema.Set) + + remove := removeRoutes(d.Id(), os.Difference(ns).List()) + add := addRoutes(d.Id(), ns.Difference(os).List()) + + if len(remove) > 0 { + for _, r := range remove { + log.Printf("[DEBUG] Client VPN authorization route opts: %s", r) + _, err := conn.DeleteClientVpnRoute(r) + if err != nil { + return fmt.Errorf("Failure removing outdated Client VPN routes: %s", err) + } + } + } + + if len(add) > 0 { + for _, r := range add { + log.Printf("[DEBUG] Client VPN authorization route opts: %s", r) + _, err := conn.CreateClientVpnRoute(r) + if err != nil { + return fmt.Errorf("Failure adding new Client VPN authorization routes: %s", err) + } + } + } + } + if err := setTags(conn, d); err != nil { return err } @@ -769,3 +839,77 @@ func resourceAwsAuthRuleHash(v interface{}) int { return hashcode.String(buf.String()) } + +func flattenRoutes(list []*ec2.ClientVpnRoute) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + l := map[string]interface{}{ + "destination_network_cidr": *i.DestinationCidr, + "subnet_id": *i.TargetSubnet, + } + + if i.Description != nil { + l["description"] = aws.String(*i.Description) + } + + result = append(result, l) + } + return result +} + +func addRoutes(eid string, configured []interface{}) []*ec2.CreateClientVpnRouteInput { + routes := make([]*ec2.CreateClientVpnRouteInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + route := &ec2.CreateClientVpnRouteInput{} + + route.ClientVpnEndpointId = aws.String(eid) + route.DestinationCidrBlock = aws.String(item["destination_network_cidr"].(string)) + route.TargetVpcSubnetId = aws.String(item["subnet_id"].(string)) + + if item["description"].(string) != "" { + route.Description = aws.String(item["description"].(string)) + } + + routes = append(routes, route) + } + + return routes +} + +func removeRoutes(eid string, configured []interface{}) []*ec2.DeleteClientVpnRouteInput { + routes := make([]*ec2.DeleteClientVpnRouteInput, 0, len(configured)) + + for _, i := range configured { + item := i.(map[string]interface{}) + route := &ec2.DeleteClientVpnRouteInput{} + + route.ClientVpnEndpointId = aws.String(eid) + route.DestinationCidrBlock = aws.String(item["destination_network_cidr"].(string)) + route.TargetVpcSubnetId = aws.String(item["subnet_id"].(string)) + + routes = append(routes, route) + } + + return routes +} + +func resourceAwsRouteHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["description"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["destination_network_cidr"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["subnet_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + return hashcode.String(buf.String()) +} diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index d6ca2a79b6b..adc044d0a58 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -188,6 +188,46 @@ func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { }) } +func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { + rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersWithTLS, + CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnEndpointConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + ), + }, + { + Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + ), + }, + { + Config: testAccEc2ClientVpnEndpointConfigWithRoutes(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "route.#", "2"), + ), + }, + { + Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + ), + }, + }, + }) +} + func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" rStr := acctest.RandString(5) @@ -484,6 +524,110 @@ resource "aws_ec2_client_vpn_endpoint" "test" { `, rName, rName, rName) } +func testAccEc2ClientVpnEndpointConfigWithRoute(rName string) string { + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } +} +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + tags = { + Name = "tf-acc-subnet-%s" + } +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.cert.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" + } + + connection_log_options { + enabled = false + } + + network_association { + subnet_id = "${aws_subnet.test.id}" + } + + authorization_rule { + description = "example auth rule" + target_network_cidr = "10.1.1.0/24" + } + + route { + description = "example route 1" + subnet_id = "${aws_subnet.test.id}" + destination_network_cidr = "192.168.1.0/24" + } +} +`, rName, rName, rName) +} + +func testAccEc2ClientVpnEndpointConfigWithRoutes(rName string) string { + return testAccEc2ClientVpnEndpointBaseConfig + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } +} +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + tags = { + Name = "tf-acc-subnet-%s" + } +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.cert.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" + } + + connection_log_options { + enabled = false + } + + network_association { + subnet_id = "${aws_subnet.test.id}" + } + + authorization_rule { + description = "example auth rule" + target_network_cidr = "10.1.1.0/24" + } + + route { + description = "example route 1" + subnet_id = "${aws_subnet.test.id}" + destination_network_cidr = "192.168.1.0/24" + } + + route { + description = "example route 2" + subnet_id = "${aws_subnet.test.id}" + destination_network_cidr = "192.168.2.0/24" + } +} +`, rName, rName, rName) +} + func testAccEc2ClientVpnEndpointConfig_tags(rName string) string { return fmt.Sprintf(` resource "tls_private_key" "example" { diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 66b2f1c7339..a495a5615f1 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -45,6 +45,7 @@ The following arguments are supported: * `connection_log_options` - (Required) Information about the client connection logging options. * `network_association` - (Optional) Information about associating networks to this Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `authorization_rule` - (Optional) Information about granting access to networks via this Client VPN endpoint. Multiple definitions of this parameter can be supplied. +* `route` - (Optional) Information about routes to add to a Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `tags` - (Optional) A mapping of tags to assign to the resource. ### `authentication_options` Argument Reference @@ -79,6 +80,14 @@ One of the following arguments must be supplied: * `access_group_id` - (Optional) The ID of the Active Directory group to which the authorization rule grants access. * `authorize_all_groups` - (Optional) Indicates whether the authorization rule grants access to all clients. +### `route` Argument Reference + +One of the following arguments must be supplied: + +* `description` - (Optional) A brief description of the route. +* `destination_network_cidr` - (Required) The IPv4 address range, in CIDR notation, of the route destination. +* `subnet_id` - (Required) The ID of the subnet through which you want to route traffic. The specified subnet must be an existing target network of the Client VPN endpoint. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From f7fa8f4a68940bfc5ad44db6238429a291c46b1e Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Sat, 16 Mar 2019 19:07:16 -0500 Subject: [PATCH 08/44] readding network assoc resource w/ deprecation message --- aws/provider.go | 1 + ..._aws_ec2_client_vpn_network_association.go | 175 +++++++++++++++ ...ec2_client_vpn_network_association_test.go | 202 ++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 aws/resource_aws_ec2_client_vpn_network_association.go create mode 100644 aws/resource_aws_ec2_client_vpn_network_association_test.go diff --git a/aws/provider.go b/aws/provider.go index af371ae4873..1f01be702f0 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -421,6 +421,7 @@ func Provider() terraform.ResourceProvider { "aws_ebs_volume": resourceAwsEbsVolume(), "aws_ec2_capacity_reservation": resourceAwsEc2CapacityReservation(), "aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(), + "aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(), "aws_ec2_fleet": resourceAwsEc2Fleet(), "aws_ec2_transit_gateway": resourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_route": resourceAwsEc2TransitGatewayRoute(), diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go new file mode 100644 index 00000000000..6210a0c0257 --- /dev/null +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -0,0 +1,175 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, + Read: resourceAwsEc2ClientVpnNetworkAssociationRead, + Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, + + Schema: map[string]*schema.Schema{ + "client_vpn_endpoint_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "security_groups": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsEc2ClientVpnNetworkAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + req := &ec2.AssociateClientVpnTargetNetworkInput{ + ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + SubnetId: aws.String(d.Get("subnet_id").(string)), + } + + log.Printf("[DEBUG] Creating Client VPN network association: %#v", req) + resp, err := conn.AssociateClientVpnTargetNetwork(req) + if err != nil { + return fmt.Errorf("Error creating Client VPN network association: %s", err) + } + + d.SetId(*resp.AssociationId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeAssociating}, + Target: []string{ec2.AssociationStatusCodeAssociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) + } + + return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta) +} + +func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + var err error + + result, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + AssociationIds: []*string{aws.String(d.Id())}, + }) + + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("Error reading Client VPN network association: %s", err) + } + + if result == nil || len(result.ClientVpnTargetNetworks) == 0 || result.ClientVpnTargetNetworks[0] == nil { + log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if result.ClientVpnTargetNetworks[0].Status != nil && aws.StringValue(result.ClientVpnTargetNetworks[0].Status.Code) == ec2.AssociationStatusCodeDisassociated { + log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("client_vpn_endpoint_id", result.ClientVpnTargetNetworks[0].ClientVpnEndpointId) + d.Set("status", result.ClientVpnTargetNetworks[0].Status.Code) + d.Set("subnet_id", result.ClientVpnTargetNetworks[0].TargetNetworkId) + d.Set("vpc_id", result.ClientVpnTargetNetworks[0].VpcId) + + if err := d.Set("security_groups", aws.StringValueSlice(result.ClientVpnTargetNetworks[0].SecurityGroups)); err != nil { + return fmt.Errorf("error setting security_groups: %s", err) + } + + return nil +} + +func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + _, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{ + ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + AssociationId: aws.String(d.Id()), + }) + + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + return nil + } + + if err != nil { + return fmt.Errorf("Error deleting Client VPN network association: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeDisassociating}, + Target: []string{ec2.AssociationStatusCodeDisassociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), + Timeout: d.Timeout(schema.TimeoutDelete), + } + + log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) + } + + return nil +} + +func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(cvepID), + AssociationIds: []*string{aws.String(cvnaID)}, + }) + + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + return 42, ec2.AssociationStatusCodeDisassociated, nil + } + + if err != nil { + return nil, "", err + } + + if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { + return 42, ec2.AssociationStatusCodeDisassociated, nil + } + + return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil + } +} diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go new file mode 100644 index 00000000000..7d1f13c96dc --- /dev/null +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -0,0 +1,202 @@ +package aws + +import ( + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { + var assoc1 ec2.TargetNetwork + rStr := acctest.RandString(5) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersWithTLS, + CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), + ), + }, + }, + }) +} + +func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { + var assoc1 ec2.TargetNetwork + rStr := acctest.RandString(5) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersWithTLS, + CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), + testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(&assoc1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_client_vpn_network_association" { + continue + } + + resp, _ := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), + AssociationIds: []*string{aws.String(rs.Primary.ID)}, + }) + + for _, v := range resp.ClientVpnTargetNetworks { + if *v.AssociationId == rs.Primary.ID && !(*v.Status.Code == "Disassociated") { + return fmt.Errorf("[DESTROY ERROR] Client VPN network association (%s) not deleted", rs.Primary.ID) + } + } + } + + return nil +} + +func testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(targetNetwork *ec2.TargetNetwork) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{ + AssociationId: targetNetwork.AssociationId, + ClientVpnEndpointId: targetNetwork.ClientVpnEndpointId, + }) + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeDisassociating}, + Target: []string{ec2.AssociationStatusCodeDisassociated}, + Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(targetNetwork.AssociationId), aws.StringValue(targetNetwork.ClientVpnEndpointId)), + Timeout: 10 * time.Minute, + } + + _, err = stateConf.WaitForState() + + return err + } +} + +func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2.TargetNetwork) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ + ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), + AssociationIds: []*string{aws.String(rs.Primary.ID)}, + }) + + if err != nil { + return fmt.Errorf("Error reading Client VPN network association (%s): %s", rs.Primary.ID, err) + } + + for _, a := range resp.ClientVpnTargetNetworks { + if *a.AssociationId == rs.Primary.ID && !(*a.Status.Code == "Disassociated") { + *assoc = *a + return nil + } + } + + return fmt.Errorf("Client VPN network association (%s) not found", rs.Primary.ID) + } +} + +func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + map_public_ip_on_launch = true + tags = { + Name = "tf-acc-subnet-%s" + } +} + +resource "tls_private_key" "example" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "example" { + key_algorithm = "RSA" + private_key_pem = "${tls_private_key.example.private_key_pem}" + + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } + + validity_period_hours = 12 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} + +resource "aws_acm_certificate" "cert" { + private_key = "${tls_private_key.example.private_key_pem}" + certificate_body = "${tls_self_signed_cert.example.cert_pem}" +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.cert.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}" + } + + connection_log_options { + enabled = false + } +} + +resource "aws_ec2_client_vpn_network_association" "test" { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" + subnet_id = "${aws_subnet.test.id}" +} +`, rName, rName, rName) +} From 6cc4c3138e8ce98183884a1e915cbec41a14f970 Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Sat, 16 Mar 2019 19:12:14 -0500 Subject: [PATCH 09/44] re-adding network association docs too --- ..._aws_ec2_client_vpn_network_association.go | 7 ++-- website/aws.erb | 4 ++ ...ient_vpn_network_association.html.markdown | 39 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 website/docs/r/ec2_client_vpn_network_association.html.markdown diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go index 6210a0c0257..b06de526cb5 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -12,9 +12,10 @@ import ( func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, - Read: resourceAwsEc2ClientVpnNetworkAssociationRead, - Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, + Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, + Read: resourceAwsEc2ClientVpnNetworkAssociationRead, + Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, + DeprecationMessage: "WARN: aws_ec2_client_vpn_network_association functionality has been consolidated into aws_ec2_client_vpn_endpoint. This resource will be removed in an upcoming version of this provider.", Schema: map[string]*schema.Schema{ "client_vpn_endpoint_id": { diff --git a/website/aws.erb b/website/aws.erb index f612b70fb0d..e9c9687fcb1 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1136,6 +1136,10 @@ aws_ec2_client_vpn_endpoint + > + aws_ec2_client_vpn_network_association + + > aws_ec2_fleet diff --git a/website/docs/r/ec2_client_vpn_network_association.html.markdown b/website/docs/r/ec2_client_vpn_network_association.html.markdown new file mode 100644 index 00000000000..e3d4fb3770c --- /dev/null +++ b/website/docs/r/ec2_client_vpn_network_association.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "aws" +page_title: "AWS: aws_ec2_client_vpn_network_association" +sidebar_current: "docs-aws-resource-ec2-client-vpn-network-association" +description: |- + Provides network associations for AWS Client VPN endpoints. +--- + +# aws_ec2_client_vpn_network_association + +Provides network associations for AWS Client VPN endpoints. For more information on usage, please see the +[AWS Client VPN Administrator's Guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html). + +NOTE: This functionality has been consolidated into the `aws_ec2_client_vpn_endpoint` resource. This resource will be removed in an upcoming version of this provider. + +## Example Usage + +```hcl +resource "aws_ec2_client_vpn_network_association" "example" { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}" + subnet_id = "${aws_subnet.example.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `client_vpn_endpoint_id` - (Required) The ID of the Client VPN endpoint. +* `subnet_id` - (Required) The ID of the subnet to associate with the Client VPN endpoint. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique ID of the target network association. +* `security_groups` - The IDs of the security groups applied to the target network association. +* `status` - The current state of the target network association. +* `vpc_id` - The ID of the VPC in which the target network (subnet) is located. From 51324233bdffbbceac8e3deb8a0bead6a7622534 Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Sat, 16 Mar 2019 19:33:33 -0500 Subject: [PATCH 10/44] renaming net assoc refresh func --- aws/resource_aws_ec2_client_vpn_endpoint.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index ff970a81ca5..4d4cc0299e3 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -273,7 +273,7 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AssociationStatusCodeAssociating}, Target: []string{ec2.AssociationStatusCodeAssociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(netResp.AssociationId), d.Id()), + Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(netResp.AssociationId), d.Id()), Timeout: d.Timeout(schema.TimeoutCreate), } @@ -425,7 +425,7 @@ func resourceAwsEc2ClientVpnEndpointDelete(d *schema.ResourceData, meta interfac stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AssociationStatusCodeDisassociating}, Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(n.AssociationId), d.Id()), + Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(n.AssociationId), d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), } @@ -524,7 +524,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AssociationStatusCodeDisassociating}, Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(r.AssociationId), d.Id()), + Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(r.AssociationId), d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), } @@ -547,7 +547,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AssociationStatusCodeAssociating}, Target: []string{ec2.AssociationStatusCodeAssociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(addResp.AssociationId), d.Id()), + Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(addResp.AssociationId), d.Id()), Timeout: d.Timeout(schema.TimeoutCreate), } @@ -642,7 +642,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return resourceAwsEc2ClientVpnEndpointRead(d, meta) } -func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { +func clientVpnNetworkAssociationRefresh(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ ClientVpnEndpointId: aws.String(cvepID), From 69207fea5652fdda1e6f4e45e930f635327e37f1 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 11:02:54 -0700 Subject: [PATCH 11/44] Authorization Rules do not require a Network Association --- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 32453967f20..ad6e06967a3 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -691,10 +691,6 @@ resource "aws_ec2_client_vpn_endpoint" "test" { connection_log_options { enabled = false } - - network_association { - subnet_id = "${aws_subnet.test.id}" - } authorization_rule { description = "example auth rule" From baaedf8a90a673967c69289016c8382eee800d3d Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 11:03:54 -0700 Subject: [PATCH 12/44] Formatting cleanup --- ...source_aws_ec2_client_vpn_endpoint_test.go | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index ad6e06967a3..d1931dfa52e 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -618,19 +618,19 @@ data "aws_availability_zones" "available" { } resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-subnet-%s" - } + cidr_block = "10.1.0.0/16" + tags = { + Name = "terraform-testacc-subnet-%s" + } } resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + tags = { + Name = "tf-acc-subnet-%s" + } } resource "aws_ec2_client_vpn_endpoint" "test" { @@ -645,11 +645,11 @@ resource "aws_ec2_client_vpn_endpoint" "test" { connection_log_options { enabled = false - } + } network_association { - subnet_id = "${aws_subnet.test.id}" - } + subnet_id = "${aws_subnet.test.id}" + } } `, rName, rName, rName) } @@ -670,12 +670,12 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + tags = { + Name = "tf-acc-subnet-%s" + } } resource "aws_ec2_client_vpn_endpoint" "test" { @@ -690,12 +690,12 @@ resource "aws_ec2_client_vpn_endpoint" "test" { connection_log_options { enabled = false - } + } - authorization_rule { - description = "example auth rule" - target_network_cidr = "10.1.1.0/24" - } + authorization_rule { + description = "example auth rule" + target_network_cidr = "10.1.1.0/24" + } } `, rName, rName) } From d73e537f431e4d236f16eba0085e4b5cd31d6333 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 11:22:19 -0700 Subject: [PATCH 13/44] Authorization Rules are not required for Routes --- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index d1931dfa52e..5b036a6af24 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -92,7 +92,6 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "status", ec2.ClientVpnEndpointStatusCodePendingAssociate), ), }, - { ResourceName: resourceName, ImportState: true, @@ -140,7 +139,6 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "directory-service-authentication"), ), }, - { ResourceName: resourceName, ImportState: true, @@ -168,7 +166,6 @@ func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "authentication_options.1.type", "certificate-authentication"), ), }, - { ResourceName: resourceName, ImportState: true, @@ -193,7 +190,6 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, - { Config: testAccEc2ClientVpnEndpointConfigWithLogGroup(rStr), Check: resource.ComposeTestCheckFunc( @@ -228,7 +224,6 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), ), }, - { Config: testAccEc2ClientVpnEndpointConfigWithDNSServers(rStr), Check: resource.ComposeTestCheckFunc( @@ -746,11 +741,6 @@ resource "aws_ec2_client_vpn_endpoint" "test" { subnet_id = "${aws_subnet.test.id}" } - authorization_rule { - description = "example auth rule" - target_network_cidr = "10.1.1.0/24" - } - route { description = "example route 1" subnet_id = "${aws_subnet.test.id}" @@ -803,11 +793,6 @@ resource "aws_ec2_client_vpn_endpoint" "test" { subnet_id = "${aws_subnet.test.id}" } - authorization_rule { - description = "example auth rule" - target_network_cidr = "10.1.1.0/24" - } - route { description = "example route 1" subnet_id = "${aws_subnet.test.id}" From cac47cab87e9f09509520c623a3486eb1be383c8 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 11:41:15 -0700 Subject: [PATCH 14/44] Adds API resource parameter to `Exists` function --- ...source_aws_ec2_client_vpn_endpoint_test.go | 102 ++++++++++++++---- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 5b036a6af24..5565e10173d 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -74,6 +74,7 @@ func testSweepEc2ClientVpnEndpoints(region string) error { } func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { + var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -85,7 +86,7 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`client-vpn-endpoint/cvpn-endpoint-.+`)), resource.TestCheckResourceAttr(resourceName, "authentication_options.#", "1"), resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "certificate-authentication"), @@ -102,6 +103,7 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { + var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -113,7 +115,7 @@ func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ClientVpnEndpoint(), resourceName), ), ExpectNonEmptyPlan: true, @@ -123,6 +125,7 @@ func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { + var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -134,7 +137,7 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfigWithMicrosoftAD(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "authentication_options.#", "1"), resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "directory-service-authentication"), ), @@ -149,6 +152,7 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { + var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -160,7 +164,7 @@ func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfigWithMutualAuthAndMicrosoftAD(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "authentication_options.#", "2"), resource.TestCheckResourceAttr(resourceName, "authentication_options.0.type", "directory-service-authentication"), resource.TestCheckResourceAttr(resourceName, "authentication_options.1.type", "certificate-authentication"), @@ -176,6 +180,7 @@ func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { + var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -187,13 +192,13 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithLogGroup(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "connection_log_options.#", "1"), resource.TestCheckResourceAttr(resourceName, "connection_log_options.0.enabled", "true"), resource.TestCheckResourceAttrSet(resourceName, "connection_log_options.0.cloudwatch_log_group"), @@ -210,6 +215,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { + var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -221,13 +227,13 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithDNSServers(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), ), }, }, @@ -235,6 +241,28 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { + var v ec2.ClientVpnEndpoint + rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_endpoint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "network_association.#", "1"), + ), + }, + }, + }) +} + +func TestAccAwsEc2ClientVpnEndpoint_addNetworkAssociation(t *testing.T) { + var v1, v2, v3 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -246,21 +274,30 @@ func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "network_association.#", "0"), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "network_association.#", "1"), ), }, + { + Config: testAccEc2ClientVpnEndpointConfig(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v3), + resource.TestCheckResourceAttr(resourceName, "network_association.#", "0"), + ), + }, }, }) } func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { + var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -272,14 +309,14 @@ func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "authorization_rule.#", "0"), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "authorization_rule.#", "1"), resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.description"), resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.target_network_cidr"), @@ -290,6 +327,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { + var v1, v2, v3, v4 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -301,27 +339,27 @@ func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithRoutes(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v3), resource.TestCheckResourceAttr(resourceName, "route.#", "2"), ), }, { Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v4), resource.TestCheckResourceAttr(resourceName, "route.#", "1"), ), }, @@ -330,6 +368,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { + var v1, v2, v3 ec2.ClientVpnEndpoint resourceName := "aws_ec2_client_vpn_endpoint.test" rStr := acctest.RandString(5) @@ -341,7 +380,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig_tags(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.Usage", "original"), ), @@ -354,7 +393,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig_tagsChanged(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Usage", "changed"), ), @@ -362,7 +401,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v3), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -371,6 +410,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { } func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { + var v1, v2 ec2.ClientVpnEndpoint rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -382,7 +422,7 @@ func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfigSplitTunnel(rName, true), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "split_tunnel", "true"), ), }, @@ -394,7 +434,7 @@ func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { { Config: testAccEc2ClientVpnEndpointConfigSplitTunnel(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName), + testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "split_tunnel", "false"), ), }, @@ -424,13 +464,29 @@ func testAccCheckAwsEc2ClientVpnEndpointDestroy(s *terraform.State) error { return nil } -func testAccCheckAwsEc2ClientVpnEndpointExists(name string) resource.TestCheckFunc { +func testAccCheckAwsEc2ClientVpnEndpointExists(name string, endpoint *ec2.ClientVpnEndpoint) resource.TestCheckFunc { return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Not found: %s", name) } + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DescribeClientVpnEndpointsInput{ + ClientVpnEndpointIds: []*string{aws.String(rs.Primary.ID)}, + } + result, err := conn.DescribeClientVpnEndpoints(input) + if err != nil { + return err + } + + if result == nil || len(result.ClientVpnEndpoints) == 0 || result.ClientVpnEndpoints[0] == nil { + return fmt.Errorf("EC2 Client VPN Endpoint (%s) not found", rs.Primary.ID) + } + + *endpoint = *result.ClientVpnEndpoints[0] + return nil } } From 63f6b94adbb121a47daefb59751ecedda11cce6c Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 13:51:00 -0700 Subject: [PATCH 15/44] Removes deprecation of `aws_ec2_client_vpn_network_association` and removes `network_association` field from `aws_ec2_client_vpn_endpoint` resource --- aws/resource_aws_ec2_client_vpn_endpoint.go | 205 ------------------ ...source_aws_ec2_client_vpn_endpoint_test.go | 104 --------- ..._aws_ec2_client_vpn_network_association.go | 7 +- ...ec2_client_vpn_network_association_test.go | 6 +- .../r/ec2_client_vpn_endpoint.html.markdown | 9 +- ...ient_vpn_network_association.html.markdown | 2 - 6 files changed, 8 insertions(+), 325 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 3df4aea2e56..c9690e29218 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -111,29 +111,6 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, - "network_association": { - Type: schema.TypeSet, - Optional: true, - Set: resourceAwsNetAssocHash, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "subnet_id": { - Type: schema.TypeString, - Required: true, - }, - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "association_id": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, "authorization_rule": { Type: schema.TypeSet, Optional: true, @@ -257,45 +234,6 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac d.SetId(*resp.ClientVpnEndpointId) - if _, ok := d.GetOk("network_association"); ok { - networks, securityGroups := addNetworkAssociation(d.Id(), d.Get("network_association").(*schema.Set).List()) - - for i, n := range networks { - netResp, err := conn.AssociateClientVpnTargetNetwork(n) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeAssociating}, - Target: []string{ec2.AssociationStatusCodeAssociated}, - Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(netResp.AssociationId), d.Id()), - Timeout: d.Timeout(schema.TimeoutCreate), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", aws.StringValue(netResp.AssociationId)) - targetNetworkRaw, err := stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) - } - - targetNetwork := targetNetworkRaw.(*ec2.TargetNetwork) - - if len(securityGroups[i]) > 0 { - sgReq := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{ - ClientVpnEndpointId: aws.String(d.Id()), - VpcId: targetNetwork.VpcId, - SecurityGroupIds: securityGroups[i], - } - - _, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(sgReq) - if err != nil { - return fmt.Errorf("Error applying security groups to Client VPN network association: %s", err) - } - } - } - } - if _, ok := d.GetOk("authorization_rule"); ok { rules := addAuthorizationRules(d.Id(), d.Get("authorization_rule").(*schema.Set).List()) @@ -380,14 +318,6 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error setting tags: %s", err) } - netAssocResult, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(d.Id()), - }) - if err != nil { - return err - } - d.Set("network_association", flattenNetAssoc(netAssocResult.ClientVpnTargetNetworks)) - authRuleResult, err := conn.DescribeClientVpnAuthorizationRules(&ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(d.Id()), }) @@ -523,76 +453,6 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) } - if d.HasChange("network_association") { - o, n := d.GetChange("network_association") - os := o.(*schema.Set) - ns := n.(*schema.Set) - - removeNets := removeNetworkAssociation(d.Id(), os.Difference(ns).List()) - addNets, addSGs := addNetworkAssociation(d.Id(), ns.Difference(os).List()) - - if len(removeNets) > 0 { - for _, r := range removeNets { - log.Printf("[DEBUG] Client VPN network authorization opts: %s", r) - _, err := conn.DisassociateClientVpnTargetNetwork(r) - if err != nil { - return fmt.Errorf("Failure removing outdated Client VPN network authorizations: %s", err) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeDisassociating}, - Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(r.AssociationId), d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", aws.StringValue(r.AssociationId)) - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) - } - } - } - - if len(addNets) > 0 { - for i, a := range addNets { - log.Printf("[DEBUG] Client VPN network authorization opts: %s", a) - addResp, err := conn.AssociateClientVpnTargetNetwork(a) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN network authorizations: %s", err) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeAssociating}, - Target: []string{ec2.AssociationStatusCodeAssociated}, - Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(addResp.AssociationId), d.Id()), - Timeout: d.Timeout(schema.TimeoutCreate), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", aws.StringValue(addResp.AssociationId)) - targetNetworkRaw, err := stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) - } - - targetNetwork := targetNetworkRaw.(*ec2.TargetNetwork) - - if len(addSGs[i]) > 0 { - sgReq := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{ - ClientVpnEndpointId: aws.String(d.Id()), - VpcId: targetNetwork.VpcId, - SecurityGroupIds: addSGs[i], - } - - _, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(sgReq) - if err != nil { - return fmt.Errorf("Error applying security groups to Client VPN network association: %s", err) - } - } - } - } - } - if d.HasChange("authorization_rule") { o, n := d.GetChange("authorization_rule") os := o.(*schema.Set) @@ -733,71 +593,6 @@ func expandEc2ClientVpnAuthenticationRequest(data map[string]interface{}) *ec2.C return req } -func flattenNetAssoc(list []*ec2.TargetNetwork) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(list)) - for _, i := range list { - l := map[string]interface{}{ - "association_id": *i.AssociationId, - } - - if i.SecurityGroups != nil { - l["security_groups"] = i.SecurityGroups - } - - result = append(result, l) - } - return result -} - -func addNetworkAssociation(eid string, configured []interface{}) ([]*ec2.AssociateClientVpnTargetNetworkInput, [][]*string) { - networks := make([]*ec2.AssociateClientVpnTargetNetworkInput, 0, len(configured)) - securityGroups := make([][]*string, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - network := &ec2.AssociateClientVpnTargetNetworkInput{} - - network.ClientVpnEndpointId = aws.String(eid) - network.SubnetId = aws.String(item["subnet_id"].(string)) - - networks = append(networks, network) - securityGroups = append(securityGroups, expandStringSet(item["security_groups"].(*schema.Set))) - } - - return networks, securityGroups -} - -func removeNetworkAssociation(eid string, configured []interface{}) []*ec2.DisassociateClientVpnTargetNetworkInput { - networks := make([]*ec2.DisassociateClientVpnTargetNetworkInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - network := &ec2.DisassociateClientVpnTargetNetworkInput{} - - network.ClientVpnEndpointId = aws.String(eid) - network.AssociationId = aws.String(item["association_id"].(string)) - - networks = append(networks, network) - } - - return networks -} - -func resourceAwsNetAssocHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["subnet_id"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["security_groups"]; ok { - buf.WriteString(fmt.Sprintf("%v-", v.(*schema.Set).List())) - } - - return hashcode.String(buf.String()) -} - func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) for _, i := range list { diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 5565e10173d..660c153272a 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -240,62 +240,6 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withNetworkAssociation(t *testing.T) { - var v ec2.ClientVpnEndpoint - rStr := acctest.RandString(5) - resourceName := "aws_ec2_client_vpn_endpoint.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "network_association.#", "1"), - ), - }, - }, - }) -} - -func TestAccAwsEc2ClientVpnEndpoint_addNetworkAssociation(t *testing.T) { - var v1, v2, v3 ec2.ClientVpnEndpoint - rStr := acctest.RandString(5) - resourceName := "aws_ec2_client_vpn_endpoint.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnEndpointConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), - resource.TestCheckResourceAttr(resourceName, "network_association.#", "0"), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), - resource.TestCheckResourceAttr(resourceName, "network_association.#", "1"), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v3), - resource.TestCheckResourceAttr(resourceName, "network_association.#", "0"), - ), - }, - }, - }) -} - func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) @@ -657,54 +601,6 @@ resource "aws_ec2_client_vpn_endpoint" "test" { `, rName) } -func testAccEc2ClientVpnEndpointConfigWithNetworkAssociation(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-subnet-%s" - } -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } - - network_association { - subnet_id = "${aws_subnet.test.id}" - } -} -`, rName, rName, rName) -} - func testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rName string) string { return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` data "aws_availability_zones" "available" { diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go index 2e7f4591b1d..1caf9acfb64 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -12,10 +12,9 @@ import ( func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, - Read: resourceAwsEc2ClientVpnNetworkAssociationRead, - Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, - DeprecationMessage: "WARN: aws_ec2_client_vpn_network_association functionality has been consolidated into aws_ec2_client_vpn_endpoint. This resource will be removed in an upcoming version of this provider.", + Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, + Read: resourceAwsEc2ClientVpnNetworkAssociationRead, + Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete, Schema: map[string]*schema.Schema{ "client_vpn_endpoint_id": { diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 7d69dc7619d..bfe1372fbdb 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -15,6 +15,7 @@ import ( func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { var assoc1 ec2.TargetNetwork rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -24,7 +25,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { { Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), + testAccCheckAwsEc2ClientVpnNetworkAssociationExists(resourceName, &assoc1), ), }, }, @@ -34,6 +35,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { var assoc1 ec2.TargetNetwork rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -43,7 +45,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { { Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1), + testAccCheckAwsEc2ClientVpnNetworkAssociationExists(resourceName, &assoc1), testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(&assoc1), ), ExpectNonEmptyPlan: true, diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index d5106b21195..6b3f4ce7dc7 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -45,7 +45,6 @@ The following arguments are supported: * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. -* `network_association` - (Optional) Information about associating networks to this Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `authorization_rule` - (Optional) Information about granting access to networks via this Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `route` - (Optional) Information about routes to add to a Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -67,13 +66,6 @@ One of the following arguments must be supplied: * `cloudwatch_log_group` - (Optional) The name of the CloudWatch Logs log group. * `cloudwatch_log_stream` - (Optional) The name of the CloudWatch Logs log stream to which the connection data is published. -### `network_association` Argument Reference - -One of the following arguments must be supplied: - -* `subnet_id` - (Required) The ID of the subnet to associate with the Client VPN endpoint. -* `security_groups` - (Optional) The IDs of the security groups to apply to the associated target network. Up to 5 security groups can be applied to an associated target network. - ### `authorization_rule` Argument Reference One of the following arguments must be supplied: @@ -98,6 +90,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Client VPN endpoint. * `arn` - The ARN of the Client VPN endpoint. * `dns_name` - The DNS name to be used by clients when establishing their VPN session. +* `status` - The current state of the Client VPN endpoint. ## Import diff --git a/website/docs/r/ec2_client_vpn_network_association.html.markdown b/website/docs/r/ec2_client_vpn_network_association.html.markdown index bfd0da860d7..a258eade643 100644 --- a/website/docs/r/ec2_client_vpn_network_association.html.markdown +++ b/website/docs/r/ec2_client_vpn_network_association.html.markdown @@ -11,8 +11,6 @@ description: |- Provides network associations for AWS Client VPN endpoints. For more information on usage, please see the [AWS Client VPN Administrator's Guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html). -NOTE: This functionality has been consolidated into the `aws_ec2_client_vpn_endpoint` resource. This resource will be removed in an upcoming version of this provider. - ## Example Usage ```hcl From 85d5e0303ac7e9a3ca60c13a137438a6ddc1d919 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 23 Jun 2020 17:35:43 -0700 Subject: [PATCH 16/44] Moves Client VPN authorization rule to separate resource and adds basic test --- aws/provider.go | 1 + ...e_aws_ec2_client_vpn_authorization_rule.go | 226 ++++++++++++++++++ ..._ec2_client_vpn_authorization_rule_test.go | 155 ++++++++++++ aws/resource_aws_ec2_client_vpn_endpoint.go | 166 +------------ ...source_aws_ec2_client_vpn_endpoint_test.go | 30 --- ..._aws_ec2_client_vpn_network_association.go | 18 +- ...ec2_client_vpn_network_association_test.go | 45 +--- ...lient_vpn_authorization_rule.html.markdown | 32 +++ .../r/ec2_client_vpn_endpoint.html.markdown | 10 - 9 files changed, 430 insertions(+), 253 deletions(-) create mode 100644 aws/resource_aws_ec2_client_vpn_authorization_rule.go create mode 100644 aws/resource_aws_ec2_client_vpn_authorization_rule_test.go create mode 100644 website/docs/r/ec2_client_vpn_authorization_rule.html.markdown diff --git a/aws/provider.go b/aws/provider.go index d6ea3587f19..01cde03f40d 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -545,6 +545,7 @@ func Provider() terraform.ResourceProvider { "aws_ebs_volume": resourceAwsEbsVolume(), "aws_ec2_availability_zone_group": resourceAwsEc2AvailabilityZoneGroup(), "aws_ec2_capacity_reservation": resourceAwsEc2CapacityReservation(), + "aws_ec2_client_vpn_authorization_rule": resourceAwsEc2ClientVpnAuthorizationRule(), "aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(), "aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(), "aws_ec2_fleet": resourceAwsEc2Fleet(), diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go new file mode 100644 index 00000000000..b094e0e4c76 --- /dev/null +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -0,0 +1,226 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceAwsEc2ClientVpnAuthorizationRule() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2ClientVpnAuthorizationRuleCreate, + Read: resourceAwsEc2ClientVpnAuthorizationRuleRead, + Delete: resourceAwsEc2ClientVpnAuthorizationRuleDelete, + + Schema: map[string]*schema.Schema{ + "client_vpn_endpoint_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "target_network_cidr": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCIDRNetworkAddress, + }, + "access_group_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"access_group_id", "authorize_all_groups"}, + }, + "authorize_all_groups": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"access_group_id", "authorize_all_groups"}, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + endpointID := d.Get("client_vpn_endpoint_id").(string) + targetNetworkCidr := d.Get("target_network_cidr").(string) + + input := &ec2.AuthorizeClientVpnIngressInput{ + ClientVpnEndpointId: aws.String(endpointID), + TargetNetworkCidr: aws.String(targetNetworkCidr), + } + + var accessGroupID string + if v, ok := d.GetOk("access_group_id"); ok { + accessGroupID = v.(string) + input.AccessGroupId = aws.String(accessGroupID) + } + + if v, ok := d.GetOk("authorize_all_groups"); ok { + input.AuthorizeAllGroups = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Client VPN authorization rule: %#v", input) + _, err := conn.AuthorizeClientVpnIngress(input) + if err != nil { + return fmt.Errorf("Error creating Client VPN authorization rule: %s", err) + } + + // TODO wait + + d.SetId(ec2ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID)) + + // stateConf := &resource.StateChangeConf{ + // Pending: []string{ec2.AssociationStatusCodeAssociating}, + // Target: []string{ec2.AssociationStatusCodeAssociated}, + // Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), + // Timeout: d.Timeout(schema.TimeoutCreate), + // } + + // log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id()) + // _, err = stateConf.WaitForState() + // if err != nil { + // return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) + // } + + return resourceAwsEc2ClientVpnAuthorizationRuleRead(d, meta) +} + +const errCodeClientVpnEndpointAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" + +func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + } + + result, err := conn.DescribeClientVpnAuthorizationRules(input) + + if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + log.Printf("[WARN] EC2 Client VPN authorization rule (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("Error reading Client VPN authorization rule: %w", err) + } + + if result == nil || len(result.AuthorizationRules) == 0 || result.AuthorizationRules[0] == nil { + log.Printf("[WARN] EC2 Client VPN authorization rule (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("client_vpn_endpoint_id", result.AuthorizationRules[0].ClientVpnEndpointId) + d.Set("target_network_cidr", result.AuthorizationRules[0].DestinationCidr) + // TODO: Fill in Groups + d.Set("description", result.AuthorizationRules[0].Description) + + return nil +} + +func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.RevokeClientVpnIngressInput{ + ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + TargetNetworkCidr: aws.String(d.Get("target_network_cidr").(string)), + } + if v, ok := d.GetOk("access_group_id"); ok { + input.AccessGroupId = aws.String(v.(string)) + } + if v, ok := d.GetOk("authorize_all_groups"); ok { + input.RevokeAllGroups = aws.Bool(v.(bool)) + } + + _, err := conn.RevokeClientVpnIngress(input) + + if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Client VPN authorization rule: %w", err) + } + + // WAIT + // stateConf := &resource.StateChangeConf{ + // Pending: []string{ec2.AssociationStatusCodeDisassociating}, + // Target: []string{ec2.AssociationStatusCodeDisassociated}, + // Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), + // Timeout: d.Timeout(schema.TimeoutDelete), + // } + + // log.Printf("[DEBUG] Waiting to revoke Client VPN authorization rule (%s)", d.Id()) + // _, err = stateConf.WaitForState() + // if err != nil { + // return fmt.Errorf("error waiting to revoke Client VPN authorization rule (%s): %w", d.Id(), err) + // } + + return nil +} + +// func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { +// return func() (interface{}, string, error) { +// resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ +// ClientVpnEndpointId: aws.String(cvepID), +// AssociationIds: []*string{aws.String(cvnaID)}, +// }) + +// if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, clientVpnEndpointIdNotFoundError, "") { +// return 42, ec2.AssociationStatusCodeDisassociated, nil +// } + +// if err != nil { +// return nil, "", err +// } + +// if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { +// return 42, ec2.AssociationStatusCodeDisassociated, nil +// } + +// return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil +// } +// } + +const ec2ClientVpnAuthorizationRuleIDSeparator = "," + +func ec2ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID string) string { + parts := []string{endpointID, targetNetworkCidr} + if accessGroupID != "" { + parts = append(parts, accessGroupID) + } + id := strings.Join(parts, ec2ClientVpnAuthorizationRuleIDSeparator) + return id +} + +func ec2ClientVpnAuthorizationRuleParseID(id string) (string, string, string, error) { + parts := strings.Split(id, ec2ClientVpnAuthorizationRuleIDSeparator) + if len(parts) == 2 { + return parts[0], parts[1], "", nil + } + if len(parts) == 3 { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", + fmt.Errorf("unexpected format for ID (%q), expected endpoint-id"+ec2ClientVpnAuthorizationRuleIDSeparator+ + "target-network-cidr or endpoint-id"+ec2ClientVpnAuthorizationRuleIDSeparator+"target-network-cidr"+ + ec2ClientVpnAuthorizationRuleIDSeparator+"group-id", id) +} diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go new file mode 100644 index 00000000000..cc5989e8bc6 --- /dev/null +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -0,0 +1,155 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { + var v ec2.AuthorizationRule + rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_authorization_rule.test" + subnetResourceName := "aws_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigBasic(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "target_network_cidr", subnetResourceName, "cidr_block"), + resource.TestCheckResourceAttr(resourceName, "authorize_all_groups", "true"), + ), + }, + }, + }) +} + +func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_client_vpn_authorization_rule" { + continue + } + + endpointID, _, _, err := ec2ClientVpnAuthorizationRuleParseID(rs.Primary.ID) + // targetNetworkCidr := parts[1] + // accessGroupID := parts[2] + if err != nil { + return err + } + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(endpointID), + // TODO: filters + } + + _, err = conn.DescribeClientVpnAuthorizationRules(input) + + if err == nil { + return fmt.Errorf("Client VPN authorization rule (%s) still exists", rs.Primary.ID) + } + if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + continue + } + return err + } + + return nil +} + +func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2.AuthorizationRule) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), + } + result, err := conn.DescribeClientVpnAuthorizationRules(input) + + if err != nil { + return fmt.Errorf("error reading Client VPN authorization rule (%s): %w", rs.Primary.ID, err) + } + + if result != nil || len(result.AuthorizationRules) == 1 || result.AuthorizationRules[0] != nil { + *assoc = *result.AuthorizationRules[0] + return nil + } + + return fmt.Errorf("Client VPN network association (%s) not found", rs.Primary.ID) + } +} + +func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { + return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` +data "aws_availability_zones" "available" { + # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region + blacklisted_zone_ids = ["usw2-az4"] + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = "terraform-testacc-subnet-%[1]s" + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.test.id}" + map_public_ip_on_launch = true + + tags = { + Name = "tf-acc-subnet-%[1]s" + } +} + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%[1]s" + server_certificate_arn = "${aws_acm_certificate.test.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" + } + + connection_log_options { + enabled = false + } +} + +resource "aws_ec2_client_vpn_authorization_rule" "test" { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" + target_network_cidr = "${aws_subnet.test.cidr_block}" + authorize_all_groups = true +} +`, rName) +} diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index c9690e29218..67f0bffdae9 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -15,6 +15,8 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) +const errCodeClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" + func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { return &schema.Resource{ Create: resourceAwsEc2ClientVpnEndpointCreate, @@ -111,32 +113,6 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, - "authorization_rule": { - Type: schema.TypeSet, - Optional: true, - Set: resourceAwsAuthRuleHash, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "description": { - Type: schema.TypeString, - Optional: true, - }, - "target_network_cidr": { - Type: schema.TypeString, - Required: true, - }, - "access_group_id": { - Type: schema.TypeString, - Optional: true, - }, - "authorize_all_groups": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - }, - }, - }, "route": { Type: schema.TypeSet, Optional: true, @@ -234,17 +210,6 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac d.SetId(*resp.ClientVpnEndpointId) - if _, ok := d.GetOk("authorization_rule"); ok { - rules := addAuthorizationRules(d.Id(), d.Get("authorization_rule").(*schema.Set).List()) - - for _, r := range rules { - _, err := conn.AuthorizeClientVpnIngress(r) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) - } - } - } - if _, ok := d.GetOk("route"); ok { rules := addRoutes(d.Id(), d.Get("route").(*schema.Set).List()) @@ -269,7 +234,7 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ ClientVpnEndpointIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Endpoint (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -318,14 +283,6 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error setting tags: %s", err) } - authRuleResult, err := conn.DescribeClientVpnAuthorizationRules(&ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(d.Id()), - }) - if err != nil { - return err - } - d.Set("authorization_rule", flattenAuthRules(authRuleResult.AuthorizationRules)) - routeResult, err := conn.DescribeClientVpnRoutes(&ec2.DescribeClientVpnRoutesInput{ ClientVpnEndpointId: aws.String(d.Id()), }) @@ -453,35 +410,6 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) } - if d.HasChange("authorization_rule") { - o, n := d.GetChange("authorization_rule") - os := o.(*schema.Set) - ns := n.(*schema.Set) - - remove := removeAuthorizationRules(d.Id(), os.Difference(ns).List()) - add := addAuthorizationRules(d.Id(), ns.Difference(os).List()) - - if len(remove) > 0 { - for _, r := range remove { - log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) - _, err := conn.RevokeClientVpnIngress(r) - if err != nil { - return fmt.Errorf("Failure removing outdated Client VPN authorization rules: %s", err) - } - } - } - - if len(add) > 0 { - for _, r := range add { - log.Printf("[DEBUG] Client VPN authorization rule opts: %s", r) - _, err := conn.AuthorizeClientVpnIngress(r) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN authorization rules: %s", err) - } - } - } - } - if d.HasChange("route") { o, n := d.GetChange("route") os := o.(*schema.Set) @@ -528,7 +456,7 @@ func clientVpnNetworkAssociationRefresh(conn *ec2.EC2, cvnaID string, cvepID str AssociationIds: []*string{aws.String(cvnaID)}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return 42, ec2.AssociationStatusCodeDisassociated, nil } @@ -593,92 +521,6 @@ func expandEc2ClientVpnAuthenticationRequest(data map[string]interface{}) *ec2.C return req } -func flattenAuthRules(list []*ec2.AuthorizationRule) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(list)) - for _, i := range list { - l := map[string]interface{}{ - "target_network_cidr": *i.DestinationCidr, - } - - if i.Description != nil { - l["description"] = aws.String(*i.Description) - } - if i.GroupId != nil { - l["access_group_id"] = aws.String(*i.GroupId) - } - if i.AccessAll != nil { - l["authorize_all_groups"] = aws.Bool(*i.AccessAll) - } - - result = append(result, l) - } - return result -} - -func addAuthorizationRules(eid string, configured []interface{}) []*ec2.AuthorizeClientVpnIngressInput { - rules := make([]*ec2.AuthorizeClientVpnIngressInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - rule := &ec2.AuthorizeClientVpnIngressInput{} - - rule.ClientVpnEndpointId = aws.String(eid) - rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) - rule.AuthorizeAllGroups = aws.Bool(item["authorize_all_groups"].(bool)) - - if item["description"].(string) != "" { - rule.Description = aws.String(item["description"].(string)) - } - - if item["access_group_id"].(string) != "" { - rule.AccessGroupId = aws.String(item["access_group_id"].(string)) - } - - rules = append(rules, rule) - } - - return rules -} - -func removeAuthorizationRules(eid string, configured []interface{}) []*ec2.RevokeClientVpnIngressInput { - rules := make([]*ec2.RevokeClientVpnIngressInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - rule := &ec2.RevokeClientVpnIngressInput{} - - rule.ClientVpnEndpointId = aws.String(eid) - rule.TargetNetworkCidr = aws.String(item["target_network_cidr"].(string)) - - rules = append(rules, rule) - } - - return rules -} - -func resourceAwsAuthRuleHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["description"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["target_network_cidr"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["access_group_id"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["authorize_all_groups"]; ok { - buf.WriteString(fmt.Sprintf("%v-", v.(bool))) - } - - return hashcode.String(buf.String()) -} - func flattenRoutes(list []*ec2.ClientVpnRoute) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) for _, i := range list { diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 660c153272a..5539f8f0463 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -240,36 +240,6 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withAuthorizationRules(t *testing.T) { - var v1, v2 ec2.ClientVpnEndpoint - rStr := acctest.RandString(5) - resourceName := "aws_ec2_client_vpn_endpoint.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnEndpointConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), - resource.TestCheckResourceAttr(resourceName, "authorization_rule.#", "0"), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), - resource.TestCheckResourceAttr(resourceName, "authorization_rule.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.description"), - resource.TestCheckResourceAttrSet(resourceName, "authorization_rule.2707333359.target_network_cidr"), - ), - }, - }, - }) -} - func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { var v1, v2, v3, v4 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go index 1caf9acfb64..0bb6de29956 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -55,7 +55,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationCreate(d *schema.ResourceData, met log.Printf("[DEBUG] Creating Client VPN network association: %#v", req) resp, err := conn.AssociateClientVpnTargetNetwork(req) if err != nil { - return fmt.Errorf("Error creating Client VPN network association: %s", err) + return fmt.Errorf("Error creating Client VPN network association: %w", err) } d.SetId(*resp.AssociationId) @@ -70,7 +70,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationCreate(d *schema.ResourceData, met log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id()) _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) + return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %w", err) } return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta) @@ -85,14 +85,14 @@ func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta AssociationIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("Error reading Client VPN network association: %s", err) + return fmt.Errorf("Error reading Client VPN network association: %w", err) } if result == nil || len(result.ClientVpnTargetNetworks) == 0 || result.ClientVpnTargetNetworks[0] == nil { @@ -113,7 +113,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta d.Set("vpc_id", result.ClientVpnTargetNetworks[0].VpcId) if err := d.Set("security_groups", aws.StringValueSlice(result.ClientVpnTargetNetworks[0].SecurityGroups)); err != nil { - return fmt.Errorf("error setting security_groups: %s", err) + return fmt.Errorf("error setting security_groups: %w", err) } return nil @@ -127,12 +127,12 @@ func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, met AssociationId: aws.String(d.Id()), }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return nil } if err != nil { - return fmt.Errorf("Error deleting Client VPN network association: %s", err) + return fmt.Errorf("Error deleting Client VPN network association: %w", err) } stateConf := &resource.StateChangeConf{ @@ -145,7 +145,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, met log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", d.Id()) _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) + return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %w", err) } return nil @@ -158,7 +158,7 @@ func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID AssociationIds: []*string{aws.String(cvnaID)}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") { + if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return 42, ec2.AssociationStatusCodeDisassociated, nil } diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index bfe1372fbdb..239959dab41 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -3,7 +3,6 @@ package aws import ( "fmt" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -46,7 +45,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), Check: resource.ComposeTestCheckFunc( testAccCheckAwsEc2ClientVpnNetworkAssociationExists(resourceName, &assoc1), - testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(&assoc1), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ClientVpnNetworkAssociation(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -77,32 +76,6 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) er return nil } -func testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(targetNetwork *ec2.TargetNetwork) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - _, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{ - AssociationId: targetNetwork.AssociationId, - ClientVpnEndpointId: targetNetwork.ClientVpnEndpointId, - }) - - if err != nil { - return err - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeDisassociating}, - Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(targetNetwork.AssociationId), aws.StringValue(targetNetwork.ClientVpnEndpointId)), - Timeout: 10 * time.Minute, - } - - _, err = stateConf.WaitForState() - - return err - } -} - func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2.TargetNetwork) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -122,7 +95,7 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 }) if err != nil { - return fmt.Errorf("Error reading Client VPN network association (%s): %s", rs.Primary.ID, err) + return fmt.Errorf("Error reading Client VPN network association (%s): %w", rs.Primary.ID, err) } for _, a := range resp.ClientVpnTargetNetworks { @@ -136,20 +109,8 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } } -func testAccEc2ClientVpnNetworkAssociationConfigAcmCertificateBase() string { - key := tlsRsaPrivateKeyPem(2048) - certificate := tlsRsaX509SelfSignedCertificatePem(key, "example.com") - - return fmt.Sprintf(` -resource "aws_acm_certificate" "test" { - certificate_body = "%[1]s" - private_key = "%[2]s" -} -`, tlsPemEscapeNewlines(certificate), tlsPemEscapeNewlines(key)) -} - func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return testAccEc2ClientVpnNetworkAssociationConfigAcmCertificateBase() + fmt.Sprintf(` + return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` data "aws_availability_zones" "available" { # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region blacklisted_zone_ids = ["usw2-az4"] diff --git a/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown new file mode 100644 index 00000000000..9e5f4d37d2d --- /dev/null +++ b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown @@ -0,0 +1,32 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_client_vpn_authorization_rule" +description: |- + Provides authorization rules for AWS Client VPN endpoints. +--- + +# Resource: aws_ec2_client_vpn_authorization_rule + +Provides authorization rules for AWS Client VPN endpoints. For more information on usage, please see the +[AWS Client VPN Administrator's Guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html). + +## Example Usage + +```hcl +resource "aws_ec2_client_vpn_network_association" "example" { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}" + target_network_cidr = "${aws_subnet.example.cidr_block}" + authorize_all_groups = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `client_vpn_endpoint_id` - (Required) The ID of the Client VPN endpoint. +* `target_network_cidr` - (Required) The IPv4 address range, in CIDR notation, of the network to which the authorization rule applies. +* `access_group_id` - (Optional) The ID of the group to which the authorization rule grants access. One of `access_group_id` or `authorize_all_groups` must be set. +* `authorize_all_groups` - (Optional) Indicates whether the authorization rule grants access to all clients. One of `access_group_id` or `authorize_all_groups` must be set. +* `description` - (Optional) A brief description of the authorization rule. diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 6b3f4ce7dc7..61ce71adb16 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -45,7 +45,6 @@ The following arguments are supported: * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. -* `authorization_rule` - (Optional) Information about granting access to networks via this Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `route` - (Optional) Information about routes to add to a Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -66,15 +65,6 @@ One of the following arguments must be supplied: * `cloudwatch_log_group` - (Optional) The name of the CloudWatch Logs log group. * `cloudwatch_log_stream` - (Optional) The name of the CloudWatch Logs log stream to which the connection data is published. -### `authorization_rule` Argument Reference - -One of the following arguments must be supplied: - -* `description` - (Optional) A brief description of the authorization rule. -* `target_network_cidr` - (Required) The IPv4 address range, in CIDR notation, of the network to which the authorization rule applies. -* `access_group_id` - (Optional) The ID of the Active Directory group to which the authorization rule grants access. -* `authorize_all_groups` - (Optional) Indicates whether the authorization rule grants access to all clients. - ### `route` Argument Reference One of the following arguments must be supplied: From 0fede62ffbf71d6a7bc59f02cc7a82c7f16bf859 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 24 Jun 2020 18:14:13 -0700 Subject: [PATCH 17/44] Adds waiters and `disappears` test --- aws/internal/service/ec2/errors.go | 3 + aws/internal/service/ec2/id.go | 32 ++++ aws/internal/service/ec2/waiter/status.go | 6 + aws/internal/service/ec2/waiter/waiter.go | 6 + ...e_aws_ec2_client_vpn_authorization_rule.go | 162 +++++++++--------- ..._ec2_client_vpn_authorization_rule_test.go | 29 +++- 6 files changed, 156 insertions(+), 82 deletions(-) create mode 100644 aws/internal/service/ec2/errors.go create mode 100644 aws/internal/service/ec2/id.go diff --git a/aws/internal/service/ec2/errors.go b/aws/internal/service/ec2/errors.go new file mode 100644 index 00000000000..6f2a90e9546 --- /dev/null +++ b/aws/internal/service/ec2/errors.go @@ -0,0 +1,3 @@ +package ec2 + +const ErrCodeClientVpnEndpointAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" diff --git a/aws/internal/service/ec2/id.go b/aws/internal/service/ec2/id.go new file mode 100644 index 00000000000..2c6b3827215 --- /dev/null +++ b/aws/internal/service/ec2/id.go @@ -0,0 +1,32 @@ +package ec2 + +import ( + "fmt" + "strings" +) + +const clientVpnAuthorizationRuleIDSeparator = "," + +func ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID string) string { + parts := []string{endpointID, targetNetworkCidr} + if accessGroupID != "" { + parts = append(parts, accessGroupID) + } + id := strings.Join(parts, clientVpnAuthorizationRuleIDSeparator) + return id +} + +func ClientVpnAuthorizationRuleParseID(id string) (string, string, string, error) { + parts := strings.Split(id, clientVpnAuthorizationRuleIDSeparator) + if len(parts) == 2 { + return parts[0], parts[1], "", nil + } + if len(parts) == 3 { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", + fmt.Errorf("unexpected format for ID (%q), expected endpoint-id"+clientVpnAuthorizationRuleIDSeparator+ + "target-network-cidr or endpoint-id"+clientVpnAuthorizationRuleIDSeparator+"target-network-cidr"+ + clientVpnAuthorizationRuleIDSeparator+"group-id", id) +} diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 7cc74a43b35..b126b94d67a 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -39,3 +39,9 @@ func LocalGatewayRouteTableVpcAssociationState(conn *ec2.EC2, localGatewayRouteT return association, aws.StringValue(association.State), nil } } + +const ( + ClientVpnAuthorizationRuleStatusNotFound = "NotFound" + + ClientVpnAuthorizationRuleStatusUnknown = "Unknown" +) diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index c2943d78577..d2af69d26d7 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -50,3 +50,9 @@ func LocalGatewayRouteTableVpcAssociationDisassociated(conn *ec2.EC2, localGatew return nil, err } + +const ( + ClientVpnEndpointAuthorizationRuleActiveTimeout = 1 * time.Minute + + ClientVpnEndpointAuthorizationRuleRevokedTimeout = 1 * time.Minute +) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index b094e0e4c76..674fc1edbac 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -3,11 +3,13 @@ package aws import ( "fmt" "log" - "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) func resourceAwsEc2ClientVpnAuthorizationRule() *schema.Resource { @@ -74,34 +76,24 @@ func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta input.Description = aws.String(v.(string)) } + id := tfec2.ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID) + log.Printf("[DEBUG] Creating Client VPN authorization rule: %#v", input) _, err := conn.AuthorizeClientVpnIngress(input) if err != nil { - return fmt.Errorf("Error creating Client VPN authorization rule: %s", err) + return fmt.Errorf("error creating Client VPN authorization rule %q: %w", id, err) } - // TODO wait - - d.SetId(ec2ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID)) - - // stateConf := &resource.StateChangeConf{ - // Pending: []string{ec2.AssociationStatusCodeAssociating}, - // Target: []string{ec2.AssociationStatusCodeAssociated}, - // Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), - // Timeout: d.Timeout(schema.TimeoutCreate), - // } + _, err = ClientVpnAuthorizationRuleAuthorized(conn, id) + if err != nil { + return fmt.Errorf("error waiting for Client VPN authorization rule %q to be active: %w", id, err) + } - // log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id()) - // _, err = stateConf.WaitForState() - // if err != nil { - // return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err) - // } + d.SetId(id) return resourceAwsEc2ClientVpnAuthorizationRuleRead(d, meta) } -const errCodeClientVpnEndpointAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" - func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn @@ -111,14 +103,13 @@ func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta i result, err := conn.DescribeClientVpnAuthorizationRules(input) - if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { log.Printf("[WARN] EC2 Client VPN authorization rule (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - if err != nil { - return fmt.Errorf("Error reading Client VPN authorization rule: %w", err) + return fmt.Errorf("error reading Client VPN authorization rule: %w", err) } if result == nil || len(result.AuthorizationRules) == 0 || result.AuthorizationRules[0] == nil { @@ -149,78 +140,93 @@ func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta input.RevokeAllGroups = aws.Bool(v.(bool)) } - _, err := conn.RevokeClientVpnIngress(input) - - if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") { - return nil - } - + log.Printf("[DEBUG] Revoking Client VPN authorization rule %q", d.Id()) + err := deleteClientVpnAuthorizationRule(conn, input) if err != nil { - return fmt.Errorf("error deleting Client VPN authorization rule: %w", err) + return fmt.Errorf("error revoking Client VPN authorization rule %q: %w", d.Id(), err) } - // WAIT - // stateConf := &resource.StateChangeConf{ - // Pending: []string{ec2.AssociationStatusCodeDisassociating}, - // Target: []string{ec2.AssociationStatusCodeDisassociated}, - // Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)), - // Timeout: d.Timeout(schema.TimeoutDelete), - // } - - // log.Printf("[DEBUG] Waiting to revoke Client VPN authorization rule (%s)", d.Id()) - // _, err = stateConf.WaitForState() - // if err != nil { - // return fmt.Errorf("error waiting to revoke Client VPN authorization rule (%s): %w", d.Id(), err) - // } - return nil } -// func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { -// return func() (interface{}, string, error) { -// resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ -// ClientVpnEndpointId: aws.String(cvepID), -// AssociationIds: []*string{aws.String(cvnaID)}, -// }) +func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnIngressInput) error { + id := tfec2.ClientVpnAuthorizationRuleCreateID(aws.StringValue(input.ClientVpnEndpointId), aws.StringValue(input.TargetNetworkCidr), aws.StringValue(input.AccessGroupId)) -// if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, clientVpnEndpointIdNotFoundError, "") { -// return 42, ec2.AssociationStatusCodeDisassociated, nil -// } + _, err := conn.RevokeClientVpnIngress(input) + if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + return nil + } -// if err != nil { -// return nil, "", err -// } + _, err = ClientVpnAuthorizationRuleRevoked(conn, id) -// if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { -// return 42, ec2.AssociationStatusCodeDisassociated, nil -// } + return err +} -// return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil -// } -// } +func ClientVpnAuthorizationRuleAuthorized(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeAuthorizing}, + Target: []string{ec2.ClientVpnAuthorizationRuleStatusCodeActive}, + Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), + Timeout: waiter.ClientVpnEndpointAuthorizationRuleActiveTimeout, + } -const ec2ClientVpnAuthorizationRuleIDSeparator = "," + outputRaw, err := stateConf.WaitForState() -func ec2ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGroupID string) string { - parts := []string{endpointID, targetNetworkCidr} - if accessGroupID != "" { - parts = append(parts, accessGroupID) + if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { + return output, err } - id := strings.Join(parts, ec2ClientVpnAuthorizationRuleIDSeparator) - return id + + return nil, err } -func ec2ClientVpnAuthorizationRuleParseID(id string) (string, string, string, error) { - parts := strings.Split(id, ec2ClientVpnAuthorizationRuleIDSeparator) - if len(parts) == 2 { - return parts[0], parts[1], "", nil +func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeRevoking}, + Target: []string{}, + Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), + Timeout: waiter.ClientVpnEndpointAuthorizationRuleRevokedTimeout, } - if len(parts) == 3 { - return parts[0], parts[1], parts[2], nil + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { + return output, err } - return "", "", "", - fmt.Errorf("unexpected format for ID (%q), expected endpoint-id"+ec2ClientVpnAuthorizationRuleIDSeparator+ - "target-network-cidr or endpoint-id"+ec2ClientVpnAuthorizationRuleIDSeparator+"target-network-cidr"+ - ec2ClientVpnAuthorizationRuleIDSeparator+"group-id", id) + return nil, err +} + +// ClientVpnAuthorizationRuleStatus fetches the Client VPN authorization rule and its Status +// This should be in the waiters package, but has a dependency on isAWSErr() +func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + endpointID, _ /*targetNetworkCidr*/, _ /*accessGroupID*/, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) + if err != nil { + return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err + } + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(endpointID), + // TODO: filters + } + + result, err := conn.DescribeClientVpnAuthorizationRules(input) + if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil + } + if err != nil { + return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err + } + + if result == nil || len(result.AuthorizationRules) == 0 || result.AuthorizationRules[0] == nil { + return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil + } + + rule := result.AuthorizationRules[0] + if rule.Status == nil || rule.Status.Code == nil { + return rule, waiter.ClientVpnAuthorizationRuleStatusUnknown, nil + } + + return rule, aws.StringValue(rule.Status.Code), nil + } } diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index cc5989e8bc6..cf65b04e0e8 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { @@ -34,6 +35,28 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { }) } +func TestAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { + var v ec2.AuthorizationRule + rStr := acctest.RandString(5) + resourceName := "aws_ec2_client_vpn_authorization_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigBasic(rStr), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ClientVpnAuthorizationRule(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -42,9 +65,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err continue } - endpointID, _, _, err := ec2ClientVpnAuthorizationRuleParseID(rs.Primary.ID) - // targetNetworkCidr := parts[1] - // accessGroupID := parts[2] + endpointID, _ /*targetNetworkCidr*/, _ /*accessGroupID*/, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) if err != nil { return err } @@ -59,7 +80,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err if err == nil { return fmt.Errorf("Client VPN authorization rule (%s) still exists", rs.Primary.ID) } - if isAWSErr(err, errCodeClientVpnEndpointAuthorizationRuleNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { continue } return err From 830085785944be68334ca92afa998af68a63dda1 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 13:12:05 -0700 Subject: [PATCH 18/44] Handles multiple authorization groups --- ...e_aws_ec2_client_vpn_authorization_rule.go | 41 ++++-- ..._ec2_client_vpn_authorization_rule_test.go | 129 ++++++++++++++---- 2 files changed, 138 insertions(+), 32 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index 674fc1edbac..1eb6909967e 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -97,8 +97,16 @@ func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + filters := map[string]string{ + "destination-cidr": d.Get("target_network_cidr").(string), + } + if v := d.Get("access_group_id").(string); v != "" { + filters["group-id"] = v + } + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), + Filters: buildEC2AttributeFilterList(filters), } result, err := conn.DescribeClientVpnAuthorizationRules(input) @@ -118,10 +126,12 @@ func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta i return nil } - d.Set("client_vpn_endpoint_id", result.AuthorizationRules[0].ClientVpnEndpointId) - d.Set("target_network_cidr", result.AuthorizationRules[0].DestinationCidr) - // TODO: Fill in Groups - d.Set("description", result.AuthorizationRules[0].Description) + rule := result.AuthorizationRules[0] + d.Set("client_vpn_endpoint_id", rule.ClientVpnEndpointId) + d.Set("target_network_cidr", rule.DestinationCidr) + d.Set("access_group_id", rule.GroupId) + d.Set("authorize_all_groups", rule.AccessAll) + d.Set("description", rule.Description) return nil } @@ -134,10 +144,14 @@ func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta TargetNetworkCidr: aws.String(d.Get("target_network_cidr").(string)), } if v, ok := d.GetOk("access_group_id"); ok { - input.AccessGroupId = aws.String(v.(string)) + if s, ok := v.(string); ok && s != "" { + input.AccessGroupId = aws.String(s) + } } if v, ok := d.GetOk("authorize_all_groups"); ok { - input.RevokeAllGroups = aws.Bool(v.(bool)) + if b, ok := v.(bool); ok { + input.RevokeAllGroups = aws.Bool(b) + } } log.Printf("[DEBUG] Revoking Client VPN authorization rule %q", d.Id()) @@ -200,14 +214,21 @@ func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string // This should be in the waiters package, but has a dependency on isAWSErr() func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - endpointID, _ /*targetNetworkCidr*/, _ /*accessGroupID*/, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) if err != nil { return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err } + filters := map[string]string{ + "destination-cidr": targetNetworkCidr, + } + if accessGroupID != "" { + filters["group-id"] = accessGroupID + } + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(endpointID), - // TODO: filters + Filters: buildEC2AttributeFilterList(filters), } result, err := conn.DescribeClientVpnAuthorizationRules(input) @@ -222,6 +243,10 @@ func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil } + if len(result.AuthorizationRules) > 1 { + return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, fmt.Errorf("internal error: found %d results for status, need 1", len(result.AuthorizationRules)) + } + rule := result.AuthorizationRules[0] if rule.Status == nil || rule.Status.Code == nil { return rule, waiter.ClientVpnAuthorizationRuleStatusUnknown, nil diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index cf65b04e0e8..9648f288fd5 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -29,6 +30,69 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, "target_network_cidr", subnetResourceName, "cidr_block"), resource.TestCheckResourceAttr(resourceName, "authorize_all_groups", "true"), + resource.TestCheckResourceAttr(resourceName, "access_group_id", ""), + ), + }, + }, + }) +} + +func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { + var v1, v2, v3, v4 ec2.AuthorizationRule + rStr := acctest.RandString(5) + resource1Name := "aws_ec2_client_vpn_authorization_rule.test1" + resource2Name := "aws_ec2_client_vpn_authorization_rule.test2" + subnetResourceName := "aws_subnet.test" + + group1Name := "group_one" + group2Name := "group_two" + + groups1 := map[string]string{ + "test1": group1Name, + } + groups2 := map[string]string{ + "test1": group1Name, + "test2": group2Name, + } + groups3 := map[string]string{ + "test2": group2Name, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigGroups(rStr, groups1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource1Name, &v1), + resource.TestCheckResourceAttrPair(resource1Name, "target_network_cidr", subnetResourceName, "cidr_block"), + resource.TestCheckResourceAttr(resource1Name, "authorize_all_groups", "false"), + resource.TestCheckResourceAttr(resource1Name, "access_group_id", group1Name), + ), + }, + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigGroups(rStr, groups2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource1Name, &v2), + resource.TestCheckResourceAttrPair(resource1Name, "target_network_cidr", subnetResourceName, "cidr_block"), + resource.TestCheckResourceAttr(resource1Name, "authorize_all_groups", "false"), + resource.TestCheckResourceAttr(resource1Name, "access_group_id", group1Name), + + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource2Name, &v3), + resource.TestCheckResourceAttrPair(resource2Name, "target_network_cidr", subnetResourceName, "cidr_block"), + resource.TestCheckResourceAttr(resource2Name, "authorize_all_groups", "false"), + resource.TestCheckResourceAttr(resource2Name, "access_group_id", group2Name), + ), + }, + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigGroups(rStr, groups3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource2Name, &v4), + resource.TestCheckResourceAttrPair(resource2Name, "target_network_cidr", subnetResourceName, "cidr_block"), + resource.TestCheckResourceAttr(resource2Name, "authorize_all_groups", "false"), + resource.TestCheckResourceAttr(resource2Name, "access_group_id", group2Name), ), }, }, @@ -65,14 +129,13 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err continue } - endpointID, _ /*targetNetworkCidr*/, _ /*accessGroupID*/, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) + endpointID, _, _, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) if err != nil { return err } input := &ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(endpointID), - // TODO: filters } _, err = conn.DescribeClientVpnAuthorizationRules(input) @@ -121,7 +184,36 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. } func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` + return testAccEc2ClientVpnEndpointComposeConfig(rName, + testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), ` +resource "aws_ec2_client_vpn_authorization_rule" "test" { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" + target_network_cidr = "${aws_subnet.test[0].cidr_block}" + authorize_all_groups = true +} +`) +} + +func testAccEc2ClientVpnAuthorizationRuleConfigGroups(rName string, groupNames map[string]string) string { + var b strings.Builder + for k, v := range groupNames { + fmt.Fprintf(&b, ` +resource "aws_ec2_client_vpn_authorization_rule" %[1]q { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" + target_network_cidr = "${aws_subnet.test[0].cidr_block}" + access_group_id = %[2]q +} +`, k, v) + + } + + return testAccEc2ClientVpnEndpointComposeConfig(rName, + testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), + b.String()) +} + +func testAccEc2ClientVpnEndpointConfigVpcBase(rName string, subnetCount int) string { + return fmt.Sprintf(` data "aws_availability_zones" "available" { # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region blacklisted_zone_ids = ["usw2-az4"] @@ -142,8 +234,9 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "test" { + count = %[2]d availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.1.1.0/24" + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) vpc_id = "${aws_vpc.test.id}" map_public_ip_on_launch = true @@ -151,26 +244,14 @@ resource "aws_subnet" "test" { Name = "tf-acc-subnet-%[1]s" } } - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%[1]s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } +`, rName, subnetCount) } -resource "aws_ec2_client_vpn_authorization_rule" "test" { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - target_network_cidr = "${aws_subnet.test.cidr_block}" - authorize_all_groups = true -} -`, rName) +func testAccEc2ClientVpnEndpointComposeConfig(rName string, config ...string) string { + return composeConfig( + append( + config, + testAccEc2ClientVpnEndpointConfig(rName), + )..., + ) } From 540f123598422e41dee40699c1f52074c61494fa Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 14:46:53 -0700 Subject: [PATCH 19/44] Removes `route` from `aws_ec2_client_vpn_endpoint.test` --- aws/resource_aws_ec2_client_vpn_endpoint.go | 145 ------------- ...source_aws_ec2_client_vpn_endpoint_test.go | 200 ------------------ .../r/ec2_client_vpn_endpoint.html.markdown | 9 - 3 files changed, 354 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 67f0bffdae9..627e3f665b7 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -1,14 +1,12 @@ package aws import ( - "bytes" "fmt" "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -113,27 +111,6 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { }, }, }, - "route": { - Type: schema.TypeSet, - Optional: true, - Set: resourceAwsRouteHash, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "description": { - Type: schema.TypeString, - Optional: true, - }, - "destination_network_cidr": { - Type: schema.TypeString, - Required: true, - }, - "subnet_id": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, "dns_name": { Type: schema.TypeString, Computed: true, @@ -210,17 +187,6 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac d.SetId(*resp.ClientVpnEndpointId) - if _, ok := d.GetOk("route"); ok { - rules := addRoutes(d.Id(), d.Get("route").(*schema.Set).List()) - - for _, r := range rules { - _, err := conn.CreateClientVpnRoute(r) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN routes: %s", err) - } - } - } - return resourceAwsEc2ClientVpnEndpointRead(d, meta) } @@ -283,14 +249,6 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error setting tags: %s", err) } - routeResult, err := conn.DescribeClientVpnRoutes(&ec2.DescribeClientVpnRoutesInput{ - ClientVpnEndpointId: aws.String(d.Id()), - }) - if err != nil { - return err - } - d.Set("route", flattenRoutes(routeResult.Routes)) - arn := arn.ARN{ Partition: meta.(*AWSClient).partition, Service: "ec2", @@ -410,35 +368,6 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) } - if d.HasChange("route") { - o, n := d.GetChange("route") - os := o.(*schema.Set) - ns := n.(*schema.Set) - - remove := removeRoutes(d.Id(), os.Difference(ns).List()) - add := addRoutes(d.Id(), ns.Difference(os).List()) - - if len(remove) > 0 { - for _, r := range remove { - log.Printf("[DEBUG] Client VPN authorization route opts: %s", r) - _, err := conn.DeleteClientVpnRoute(r) - if err != nil { - return fmt.Errorf("Failure removing outdated Client VPN routes: %s", err) - } - } - } - - if len(add) > 0 { - for _, r := range add { - log.Printf("[DEBUG] Client VPN authorization route opts: %s", r) - _, err := conn.CreateClientVpnRoute(r) - if err != nil { - return fmt.Errorf("Failure adding new Client VPN authorization routes: %s", err) - } - } - } - } - if d.HasChange("tags") { o, n := d.GetChange("tags") if err := keyvaluetags.Ec2UpdateTags(conn, d.Id(), o, n); err != nil { @@ -520,77 +449,3 @@ func expandEc2ClientVpnAuthenticationRequest(data map[string]interface{}) *ec2.C return req } - -func flattenRoutes(list []*ec2.ClientVpnRoute) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(list)) - for _, i := range list { - l := map[string]interface{}{ - "destination_network_cidr": *i.DestinationCidr, - "subnet_id": *i.TargetSubnet, - } - - if i.Description != nil { - l["description"] = aws.String(*i.Description) - } - - result = append(result, l) - } - return result -} - -func addRoutes(eid string, configured []interface{}) []*ec2.CreateClientVpnRouteInput { - routes := make([]*ec2.CreateClientVpnRouteInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - route := &ec2.CreateClientVpnRouteInput{} - - route.ClientVpnEndpointId = aws.String(eid) - route.DestinationCidrBlock = aws.String(item["destination_network_cidr"].(string)) - route.TargetVpcSubnetId = aws.String(item["subnet_id"].(string)) - - if item["description"].(string) != "" { - route.Description = aws.String(item["description"].(string)) - } - - routes = append(routes, route) - } - - return routes -} - -func removeRoutes(eid string, configured []interface{}) []*ec2.DeleteClientVpnRouteInput { - routes := make([]*ec2.DeleteClientVpnRouteInput, 0, len(configured)) - - for _, i := range configured { - item := i.(map[string]interface{}) - route := &ec2.DeleteClientVpnRouteInput{} - - route.ClientVpnEndpointId = aws.String(eid) - route.DestinationCidrBlock = aws.String(item["destination_network_cidr"].(string)) - route.TargetVpcSubnetId = aws.String(item["subnet_id"].(string)) - - routes = append(routes, route) - } - - return routes -} - -func resourceAwsRouteHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["description"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["destination_network_cidr"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - if v, ok := m["subnet_id"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - - return hashcode.String(buf.String()) -} diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 5539f8f0463..c5f66f6224a 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -240,47 +240,6 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withRoutes(t *testing.T) { - var v1, v2, v3, v4 ec2.ClientVpnEndpoint - rStr := acctest.RandString(5) - resourceName := "aws_ec2_client_vpn_endpoint.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, - Steps: []resource.TestStep{ - { - Config: testAccEc2ClientVpnEndpointConfig(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v1), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v2), - resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfigWithRoutes(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v3), - resource.TestCheckResourceAttr(resourceName, "route.#", "2"), - ), - }, - { - Config: testAccEc2ClientVpnEndpointConfigWithRoute(rStr), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsEc2ClientVpnEndpointExists(resourceName, &v4), - resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - ), - }, - }, - }) -} - func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { var v1, v2, v3 ec2.ClientVpnEndpoint resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -571,165 +530,6 @@ resource "aws_ec2_client_vpn_endpoint" "test" { `, rName) } -func testAccEc2ClientVpnEndpointConfigWithAuthorizationRules(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } - - authorization_rule { - description = "example auth rule" - target_network_cidr = "10.1.1.0/24" - } -} -`, rName, rName) -} - -func testAccEc2ClientVpnEndpointConfigWithRoute(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + - fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-subnet-%s" - } -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } - - network_association { - subnet_id = "${aws_subnet.test.id}" - } - - route { - description = "example route 1" - subnet_id = "${aws_subnet.test.id}" - destination_network_cidr = "192.168.1.0/24" - } -}`, rName, rName, rName) -} - -func testAccEc2ClientVpnEndpointConfigWithRoutes(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - tags = { - Name = "terraform-testacc-subnet-%s" - } -} -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - availability_zone = "${data.aws_availability_zones.available.names[0]}" - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } - - network_association { - subnet_id = "${aws_subnet.test.id}" - } - - route { - description = "example route 1" - subnet_id = "${aws_subnet.test.id}" - destination_network_cidr = "192.168.1.0/24" - } - - route { - description = "example route 2" - subnet_id = "${aws_subnet.test.id}" - destination_network_cidr = "192.168.2.0/24" - } -} -`, rName, rName, rName) -} - func testAccEc2ClientVpnEndpointConfig_tags(rName string) string { return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` resource "aws_ec2_client_vpn_endpoint" "test" { diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 61ce71adb16..4d1e9fb5ddb 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -45,7 +45,6 @@ The following arguments are supported: * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. -* `route` - (Optional) Information about routes to add to a Client VPN endpoint. Multiple definitions of this parameter can be supplied. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -65,14 +64,6 @@ One of the following arguments must be supplied: * `cloudwatch_log_group` - (Optional) The name of the CloudWatch Logs log group. * `cloudwatch_log_stream` - (Optional) The name of the CloudWatch Logs log stream to which the connection data is published. -### `route` Argument Reference - -One of the following arguments must be supplied: - -* `description` - (Optional) A brief description of the route. -* `destination_network_cidr` - (Required) The IPv4 address range, in CIDR notation, of the route destination. -* `subnet_id` - (Required) The ID of the subnet through which you want to route traffic. The specified subnet must be an existing target network of the Client VPN endpoint. - ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 9f7c42938fd89bf7cd71233cd5a75cdf680714fd Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 15:50:00 -0700 Subject: [PATCH 20/44] Cleanup --- aws/resource_aws_ec2_client_vpn_endpoint.go | 4 ++-- ...source_aws_ec2_client_vpn_endpoint_test.go | 24 +++++++++---------- ..._aws_ec2_client_vpn_network_association.go | 8 ++++--- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 627e3f665b7..09e9eb62217 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -200,7 +200,7 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ ClientVpnEndpointIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Endpoint (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -385,7 +385,7 @@ func clientVpnNetworkAssociationRefresh(conn *ec2.EC2, cvnaID string, cvepID str AssociationIds: []*string{aws.String(cvnaID)}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return 42, ec2.AssociationStatusCodeDisassociated, nil } diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index c5f66f6224a..67a557dbf03 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -490,18 +490,18 @@ func testAccEc2ClientVpnEndpointConfigWithMicrosoftAD(rName string) string { return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + testAccEc2ClientVpnEndpointMsADBase() + fmt.Sprintf(` resource "aws_ec2_client_vpn_endpoint" "test" { -description = "terraform-testacc-clientvpn-%s" -server_certificate_arn = "${aws_acm_certificate.test.arn}" -client_cidr_block = "10.0.0.0/16" - -authentication_options { -type = "directory-service-authentication" -active_directory_id = "${aws_directory_service_directory.test.id}" -} - -connection_log_options { -enabled = false -} + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.test.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "directory-service-authentication" + active_directory_id = "${aws_directory_service_directory.test.id}" + } + + connection_log_options { + enabled = false + } } `, rName) } diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go index 0bb6de29956..d516098e9af 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) +const errCodeClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationId.NotFound" + func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { return &schema.Resource{ Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, @@ -85,7 +87,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta AssociationIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -127,7 +129,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, met AssociationId: aws.String(d.Id()), }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return nil } @@ -158,7 +160,7 @@ func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID AssociationIds: []*string{aws.String(cvnaID)}, }) - if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { return 42, ec2.AssociationStatusCodeDisassociated, nil } From 3c411481f789b5fa7aa49126e2cd15730cef0ed1 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 15:51:12 -0700 Subject: [PATCH 21/44] Adds import --- ...e_aws_ec2_client_vpn_authorization_rule.go | 21 ++++++++++++++----- ..._ec2_client_vpn_authorization_rule_test.go | 15 +++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index 1eb6909967e..e394cc390e1 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -17,6 +17,9 @@ func resourceAwsEc2ClientVpnAuthorizationRule() *schema.Resource { Create: resourceAwsEc2ClientVpnAuthorizationRuleCreate, Read: resourceAwsEc2ClientVpnAuthorizationRuleRead, Delete: resourceAwsEc2ClientVpnAuthorizationRuleDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsEc2ClientVpnAuthorizationRuleImport, + }, Schema: map[string]*schema.Schema{ "client_vpn_endpoint_id": { @@ -142,17 +145,13 @@ func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta input := &ec2.RevokeClientVpnIngressInput{ ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), TargetNetworkCidr: aws.String(d.Get("target_network_cidr").(string)), + RevokeAllGroups: aws.Bool(d.Get("authorize_all_groups").(bool)), } if v, ok := d.GetOk("access_group_id"); ok { if s, ok := v.(string); ok && s != "" { input.AccessGroupId = aws.String(s) } } - if v, ok := d.GetOk("authorize_all_groups"); ok { - if b, ok := v.(bool); ok { - input.RevokeAllGroups = aws.Bool(b) - } - } log.Printf("[DEBUG] Revoking Client VPN authorization rule %q", d.Id()) err := deleteClientVpnAuthorizationRule(conn, input) @@ -163,6 +162,18 @@ func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta return nil } +func resourceAwsEc2ClientVpnAuthorizationRuleImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(d.Id()) + if err != nil { + return nil, err + } + + d.Set("client_vpn_endpoint_id", endpointID) + d.Set("target_network_cidr", targetNetworkCidr) + d.Set("access_group_id", accessGroupID) + return []*schema.ResourceData{d}, nil +} + func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnIngressInput) error { id := tfec2.ClientVpnAuthorizationRuleCreateID(aws.StringValue(input.ClientVpnEndpointId), aws.StringValue(input.TargetNetworkCidr), aws.StringValue(input.AccessGroupId)) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index 9648f288fd5..ef3638e718d 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -33,6 +33,11 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_group_id", ""), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -72,6 +77,11 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { resource.TestCheckResourceAttr(resource1Name, "access_group_id", group1Name), ), }, + { + ResourceName: resource1Name, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEc2ClientVpnAuthorizationRuleConfigGroups(rStr, groups2), Check: resource.ComposeTestCheckFunc( @@ -86,6 +96,11 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { resource.TestCheckResourceAttr(resource2Name, "access_group_id", group2Name), ), }, + { + ResourceName: resource2Name, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEc2ClientVpnAuthorizationRuleConfigGroups(rStr, groups3), Check: resource.ComposeTestCheckFunc( From 47b626d9f30374ecbd7819a11e8cf9c29cfda608 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 16:52:59 -0700 Subject: [PATCH 22/44] Adds correct filters to Destroy check --- ...ource_aws_ec2_client_vpn_authorization_rule_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index ef3638e718d..90b34a59dfa 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -144,13 +144,21 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err continue } - endpointID, _, _, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) if err != nil { return err } + filters := map[string]string{ + "destination-cidr": targetNetworkCidr, + } + if accessGroupID != "" { + filters["group-id"] = accessGroupID + } + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ ClientVpnEndpointId: aws.String(endpointID), + Filters: buildEC2AttributeFilterList(filters), } _, err = conn.DescribeClientVpnAuthorizationRules(input) From a6177319320452231458facfbcc9f5ad22691137 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 16:53:56 -0700 Subject: [PATCH 23/44] Adds tests for multiple subnets --- ..._ec2_client_vpn_authorization_rule_test.go | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index 90b34a59dfa..cb3caa71698 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -114,6 +114,62 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { }) } +func TestAccAwsEc2ClientVpnAuthorizationRule_Subnets(t *testing.T) { + var v1, v2, v3 ec2.AuthorizationRule + rStr := acctest.RandString(5) + resource1Name := "aws_ec2_client_vpn_authorization_rule.test1" + resource2Name := "aws_ec2_client_vpn_authorization_rule.test2" + + subnetCount := 2 + + subnetIndex1 := 0 + subnetIndex2 := 1 + + case1 := map[string]int{ + "test1": subnetIndex1, + "test2": subnetIndex2, + } + case2 := map[string]int{ + "test2": subnetIndex2, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rStr, subnetCount, case1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource1Name, &v1), + resource.TestCheckResourceAttrPair(resource1Name, "target_network_cidr", fmt.Sprintf("aws_subnet.test.%d", subnetIndex1), "cidr_block"), + resource.TestCheckResourceAttr(resource1Name, "authorize_all_groups", "true"), + resource.TestCheckResourceAttr(resource1Name, "access_group_id", ""), + + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource2Name, &v2), + resource.TestCheckResourceAttrPair(resource2Name, "target_network_cidr", fmt.Sprintf("aws_subnet.test.%d", subnetIndex2), "cidr_block"), + resource.TestCheckResourceAttr(resource2Name, "authorize_all_groups", "true"), + resource.TestCheckResourceAttr(resource2Name, "access_group_id", ""), + ), + }, + { + ResourceName: resource2Name, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rStr, subnetCount, case2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(resource2Name, &v3), + resource.TestCheckResourceAttrPair(resource2Name, "target_network_cidr", fmt.Sprintf("aws_subnet.test.%d", subnetIndex2), "cidr_block"), + resource.TestCheckResourceAttr(resource2Name, "authorize_all_groups", "true"), + resource.TestCheckResourceAttr(resource2Name, "access_group_id", ""), + ), + }, + }, + }) +} + func TestAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { var v ec2.AuthorizationRule rStr := acctest.RandString(5) @@ -227,7 +283,6 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { access_group_id = %[2]q } `, k, v) - } return testAccEc2ClientVpnEndpointComposeConfig(rName, @@ -235,6 +290,23 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { b.String()) } +func testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rName string, subnetCount int, groupNames map[string]int) string { + var b strings.Builder + for k, v := range groupNames { + fmt.Fprintf(&b, ` +resource "aws_ec2_client_vpn_authorization_rule" %[1]q { + client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" + target_network_cidr = "${aws_subnet.test[%[2]d].cidr_block}" + authorize_all_groups = true +} +`, k, v) + } + + return testAccEc2ClientVpnEndpointComposeConfig(rName, + testAccEc2ClientVpnEndpointConfigVpcBase(rName, subnetCount), + b.String()) +} + func testAccEc2ClientVpnEndpointConfigVpcBase(rName string, subnetCount int) string { return fmt.Sprintf(` data "aws_availability_zones" "available" { @@ -258,7 +330,7 @@ resource "aws_vpc" "test" { resource "aws_subnet" "test" { count = %[2]d - availability_zone = data.aws_availability_zones.available.names[0] + availability_zone = data.aws_availability_zones.available.names[count.index] cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) vpc_id = "${aws_vpc.test.id}" map_public_ip_on_launch = true From b06cac4b83fe4bdd64fe16c4b2b2859f4500a438 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 17:09:26 -0700 Subject: [PATCH 24/44] Adds import documentation --- .../ec2_client_vpn_authorization_rule.html.markdown | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown index 9e5f4d37d2d..0b9a68f3c1f 100644 --- a/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown +++ b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown @@ -30,3 +30,15 @@ The following arguments are supported: * `access_group_id` - (Optional) The ID of the group to which the authorization rule grants access. One of `access_group_id` or `authorize_all_groups` must be set. * `authorize_all_groups` - (Optional) Indicates whether the authorization rule grants access to all clients. One of `access_group_id` or `authorize_all_groups` must be set. * `description` - (Optional) A brief description of the authorization rule. + +## Import + +AWS Client VPN authorization rules can be imported using the endpoint ID and target network CIDR. If there is a specific group name that is included as well. All values are separated by a `,`. + +``` +$ terraform import aws_ec2_client_vpn_authorization_rule.example cvpn-endpoint-0ac3a1abbccddd666,10.1.0.0/24 +``` + +``` +$ terraform import aws_ec2_client_vpn_authorization_rule.example cvpn-endpoint-0ac3a1abbccddd666,10.1.0.0/24,team-a +``` From c3e9794be4165b98a053eb9974500c4f1675939d Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 17:26:34 -0700 Subject: [PATCH 25/44] Fixes documentation --- website/aws.erb | 3 +++ website/docs/r/ec2_client_vpn_authorization_rule.html.markdown | 2 +- website/docs/r/ec2_client_vpn_endpoint.html.markdown | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/website/aws.erb b/website/aws.erb index 8f070b7bc4d..46e7272eea2 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1216,6 +1216,9 @@
  • aws_ec2_capacity_reservation
  • +
  • + aws_ec2_client_vpn_authorization_rule +
  • aws_ec2_client_vpn_endpoint
  • diff --git a/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown index 0b9a68f3c1f..e825bee9a43 100644 --- a/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown +++ b/website/docs/r/ec2_client_vpn_authorization_rule.html.markdown @@ -14,7 +14,7 @@ Provides authorization rules for AWS Client VPN endpoints. For more information ## Example Usage ```hcl -resource "aws_ec2_client_vpn_network_association" "example" { +resource "aws_ec2_client_vpn_authorization_rule" "example" { client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}" target_network_cidr = "${aws_subnet.example.cidr_block}" authorize_all_groups = true diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 4d1e9fb5ddb..22f908e57e2 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -41,7 +41,6 @@ The following arguments are supported: * `dns_servers` - (Optional) Information about the DNS servers to be used for DNS resolution. A Client VPN endpoint can have up to two DNS servers. If no DNS server is specified, the DNS address of the VPC that is to be associated with Client VPN endpoint is used as the DNS server. * `server_certificate_arn` - (Required) The ARN of the ACM server certificate. * `split_tunnel` - (Optional) Indicates whether split-tunnel is enabled on VPN endpoint. Default value is `false`. -* `tags` - (Optional) A map of tags to assign to the resource. * `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. * `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `connection_log_options` - (Required) Information about the client connection logging options. From fb0571d0a28aa9eecad39db2ab680b8154cb721c Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 17:30:00 -0700 Subject: [PATCH 26/44] Test cleanup and update to use Terraform 0.12 syntax --- ..._ec2_client_vpn_authorization_rule_test.go | 14 ++--- ...ec2_client_vpn_network_association_test.go | 54 ++----------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index cb3caa71698..4a47c4fac52 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -266,8 +266,8 @@ func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { return testAccEc2ClientVpnEndpointComposeConfig(rName, testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), ` resource "aws_ec2_client_vpn_authorization_rule" "test" { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - target_network_cidr = "${aws_subnet.test[0].cidr_block}" + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id + target_network_cidr = aws_subnet.test[0].cidr_block authorize_all_groups = true } `) @@ -278,8 +278,8 @@ func testAccEc2ClientVpnAuthorizationRuleConfigGroups(rName string, groupNames m for k, v := range groupNames { fmt.Fprintf(&b, ` resource "aws_ec2_client_vpn_authorization_rule" %[1]q { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - target_network_cidr = "${aws_subnet.test[0].cidr_block}" + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id + target_network_cidr = aws_subnet.test[0].cidr_block access_group_id = %[2]q } `, k, v) @@ -295,8 +295,8 @@ func testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rName string, subnetCount for k, v := range groupNames { fmt.Fprintf(&b, ` resource "aws_ec2_client_vpn_authorization_rule" %[1]q { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - target_network_cidr = "${aws_subnet.test[%[2]d].cidr_block}" + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id + target_network_cidr = aws_subnet.test[%[2]d].cidr_block authorize_all_groups = true } `, k, v) @@ -332,7 +332,7 @@ resource "aws_subnet" "test" { count = %[2]d availability_zone = data.aws_availability_zones.available.names[count.index] cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) - vpc_id = "${aws_vpc.test.id}" + vpc_id = aws_vpc.test.id map_public_ip_on_launch = true tags = { diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 239959dab41..7e0845c3ee5 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -110,55 +110,11 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return testAccEc2ClientVpnEndpointConfigAcmCertificateBase() + fmt.Sprintf(` -data "aws_availability_zones" "available" { - # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region - blacklisted_zone_ids = ["usw2-az4"] - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-subnet-%s" - } -} - -resource "aws_subnet" "test" { - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.1.1.0/24" - vpc_id = "${aws_vpc.test.id}" - map_public_ip_on_launch = true - - tags = { - Name = "tf-acc-subnet-%s" - } -} - -resource "aws_ec2_client_vpn_endpoint" "test" { - description = "terraform-testacc-clientvpn-%s" - server_certificate_arn = "${aws_acm_certificate.test.arn}" - client_cidr_block = "10.0.0.0/16" - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" - } - - connection_log_options { - enabled = false - } -} - + return testAccEc2ClientVpnEndpointComposeConfig(rName, + testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), ` resource "aws_ec2_client_vpn_network_association" "test" { - client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}" - subnet_id = "${aws_subnet.test.id}" + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id + subnet_id = aws_subnet.test[0].id } -`, rName, rName, rName) +`) } From 2256f433d223dbe022667042411140ed12a3bd7d Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 25 Jun 2020 17:33:11 -0700 Subject: [PATCH 27/44] Renames shared configuration functions --- ...aws_ec2_client_vpn_authorization_rule_test.go | 16 ++++++++-------- ...ws_ec2_client_vpn_network_association_test.go | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index 4a47c4fac52..d2d5f44809a 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -263,8 +263,8 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. } func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { - return testAccEc2ClientVpnEndpointComposeConfig(rName, - testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), ` + return testAccEc2ClientVpnComposeConfig(rName, + testAccEc2ClientVpnVpcBase(rName, 1), ` resource "aws_ec2_client_vpn_authorization_rule" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id target_network_cidr = aws_subnet.test[0].cidr_block @@ -285,8 +285,8 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnEndpointComposeConfig(rName, - testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), + return testAccEc2ClientVpnComposeConfig(rName, + testAccEc2ClientVpnVpcBase(rName, 1), b.String()) } @@ -302,12 +302,12 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnEndpointComposeConfig(rName, - testAccEc2ClientVpnEndpointConfigVpcBase(rName, subnetCount), + return testAccEc2ClientVpnComposeConfig(rName, + testAccEc2ClientVpnVpcBase(rName, subnetCount), b.String()) } -func testAccEc2ClientVpnEndpointConfigVpcBase(rName string, subnetCount int) string { +func testAccEc2ClientVpnVpcBase(rName string, subnetCount int) string { return fmt.Sprintf(` data "aws_availability_zones" "available" { # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region @@ -342,7 +342,7 @@ resource "aws_subnet" "test" { `, rName, subnetCount) } -func testAccEc2ClientVpnEndpointComposeConfig(rName string, config ...string) string { +func testAccEc2ClientVpnComposeConfig(rName string, config ...string) string { return composeConfig( append( config, diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 7e0845c3ee5..139332c06ca 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -110,8 +110,8 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return testAccEc2ClientVpnEndpointComposeConfig(rName, - testAccEc2ClientVpnEndpointConfigVpcBase(rName, 1), ` + return testAccEc2ClientVpnComposeConfig(rName, + testAccEc2ClientVpnVpcBase(rName, 1), ` resource "aws_ec2_client_vpn_network_association" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id subnet_id = aws_subnet.test[0].id From 36185b371ce2c82c58c945affbb7884536e963f5 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 26 Jun 2020 11:21:00 -0700 Subject: [PATCH 28/44] Reorganizes shared test configurations --- ...ws_ec2_client_vpn_authorization_rule_test.go | 17 ++++++----------- ...resource_aws_ec2_client_vpn_endpoint_test.go | 9 +++++++++ ...s_ec2_client_vpn_network_association_test.go | 3 +-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index d2d5f44809a..dc4eaf47a23 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -263,8 +263,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. } func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { - return testAccEc2ClientVpnComposeConfig(rName, - testAccEc2ClientVpnVpcBase(rName, 1), ` + return testAccEc2ClientVpnVpcComposeConfig(rName, 1, ` resource "aws_ec2_client_vpn_authorization_rule" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id target_network_cidr = aws_subnet.test[0].cidr_block @@ -285,9 +284,7 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnComposeConfig(rName, - testAccEc2ClientVpnVpcBase(rName, 1), - b.String()) + return testAccEc2ClientVpnVpcComposeConfig(rName, 1, b.String()) } func testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rName string, subnetCount int, groupNames map[string]int) string { @@ -302,9 +299,7 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnComposeConfig(rName, - testAccEc2ClientVpnVpcBase(rName, subnetCount), - b.String()) + return testAccEc2ClientVpnVpcComposeConfig(rName, subnetCount, b.String()) } func testAccEc2ClientVpnVpcBase(rName string, subnetCount int) string { @@ -342,11 +337,11 @@ resource "aws_subnet" "test" { `, rName, subnetCount) } -func testAccEc2ClientVpnComposeConfig(rName string, config ...string) string { - return composeConfig( +func testAccEc2ClientVpnVpcComposeConfig(rName string, subnetCount int, config ...string) string { + return testAccEc2ClientVpnComposeConfig(rName, append( config, - testAccEc2ClientVpnEndpointConfig(rName), + testAccEc2ClientVpnVpcBase(rName, subnetCount), )..., ) } diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 67a557dbf03..e16ea08360b 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -596,3 +596,12 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } `, rName, splitTunnel) } + +func testAccEc2ClientVpnComposeConfig(rName string, config ...string) string { + return composeConfig( + append( + config, + testAccEc2ClientVpnEndpointConfig(rName), + )..., + ) +} diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 139332c06ca..42164b432f2 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -110,8 +110,7 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return testAccEc2ClientVpnComposeConfig(rName, - testAccEc2ClientVpnVpcBase(rName, 1), ` + return testAccEc2ClientVpnVpcComposeConfig(rName, 1, ` resource "aws_ec2_client_vpn_network_association" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id subnet_id = aws_subnet.test[0].id From af651525a8b4085c30a2459bffec01fe13cbe226 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 26 Jun 2020 11:22:06 -0700 Subject: [PATCH 29/44] Consolidates retrieving individual Client VPN authorization rules --- ...e_aws_ec2_client_vpn_authorization_rule.go | 60 ++++++++++--------- ..._ec2_client_vpn_authorization_rule_test.go | 27 +-------- 2 files changed, 35 insertions(+), 52 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index e394cc390e1..60ea7ef5eef 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -100,19 +100,11 @@ func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - filters := map[string]string{ - "destination-cidr": d.Get("target_network_cidr").(string), - } - if v := d.Get("access_group_id").(string); v != "" { - filters["group-id"] = v - } - - input := &ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)), - Filters: buildEC2AttributeFilterList(filters), - } - - result, err := conn.DescribeClientVpnAuthorizationRules(input) + result, err := findClientVpnAuthorizationRule(conn, + d.Get("client_vpn_endpoint_id").(string), + d.Get("target_network_cidr").(string), + d.Get("access_group_id").(string), + ) if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { log.Printf("[WARN] EC2 Client VPN authorization rule (%s) not found, removing from state", d.Id()) @@ -230,19 +222,7 @@ func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err } - filters := map[string]string{ - "destination-cidr": targetNetworkCidr, - } - if accessGroupID != "" { - filters["group-id"] = accessGroupID - } - - input := &ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(endpointID), - Filters: buildEC2AttributeFilterList(filters), - } - - result, err := conn.DescribeClientVpnAuthorizationRules(input) + result, err := findClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil } @@ -255,7 +235,7 @@ func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) } if len(result.AuthorizationRules) > 1 { - return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, fmt.Errorf("internal error: found %d results for status, need 1", len(result.AuthorizationRules)) + return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, fmt.Errorf("internal error: found %d results for Client VPN authorization rule (%s) status, need 1", len(result.AuthorizationRules), authorizationRuleID) } rule := result.AuthorizationRules[0] @@ -266,3 +246,29 @@ func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) return rule, aws.StringValue(rule.Status.Code), nil } } + +func findClientVpnAuthorizationRule(conn *ec2.EC2, endpointID, targetNetworkCidr, accessGroupID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { + filters := map[string]string{ + "destination-cidr": targetNetworkCidr, + } + if accessGroupID != "" { + filters["group-id"] = accessGroupID + } + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(endpointID), + Filters: buildEC2AttributeFilterList(filters), + } + + return conn.DescribeClientVpnAuthorizationRules(input) + +} + +func findClientVpnAuthorizationRuleByID(conn *ec2.EC2, authorizationRuleID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) + if err != nil { + return nil, err + } + + return findClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) +} diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index dc4eaf47a23..e1bc563e03c 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -5,7 +5,6 @@ import ( "strings" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -200,25 +199,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err continue } - endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(rs.Primary.ID) - if err != nil { - return err - } - - filters := map[string]string{ - "destination-cidr": targetNetworkCidr, - } - if accessGroupID != "" { - filters["group-id"] = accessGroupID - } - - input := &ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(endpointID), - Filters: buildEC2AttributeFilterList(filters), - } - - _, err = conn.DescribeClientVpnAuthorizationRules(input) - + _, err := findClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) if err == nil { return fmt.Errorf("Client VPN authorization rule (%s) still exists", rs.Primary.ID) } @@ -244,11 +225,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. conn := testAccProvider.Meta().(*AWSClient).ec2conn - input := &ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]), - } - result, err := conn.DescribeClientVpnAuthorizationRules(input) - + result, err := findClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) if err != nil { return fmt.Errorf("error reading Client VPN authorization rule (%s): %w", rs.Primary.ID, err) } From 17426dbfce5f016e86103716aeef37017cbcd0a9 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 26 Jun 2020 13:22:25 -0700 Subject: [PATCH 30/44] Adds semaphore to limit concurrency of Client VPN-related acceptance tests --- aws/internal/tfawsresource/sync.go | 44 +++++++++++++++++++ ..._ec2_client_vpn_authorization_rule_test.go | 10 +++-- ...source_aws_ec2_client_vpn_endpoint_test.go | 31 +++++++++---- ...ec2_client_vpn_network_association_test.go | 6 ++- docs/MAINTAINING.md | 1 + 5 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 aws/internal/tfawsresource/sync.go diff --git a/aws/internal/tfawsresource/sync.go b/aws/internal/tfawsresource/sync.go new file mode 100644 index 00000000000..a7163aa487b --- /dev/null +++ b/aws/internal/tfawsresource/sync.go @@ -0,0 +1,44 @@ +package tfawsresource + +import ( + "fmt" + "os" + "strconv" + "testing" +) + +// Semaphore can be used to limit concurrent executions. This can be used to work with resources with low quotas +type Semaphore chan struct{} + +// InitializeSemaphore initializes a semaphore with a default capacity or overrides it using an environment variable +func InitializeSemaphore(envvar string, defaultLimit int) Semaphore { + limit := defaultLimit + x := os.Getenv(envvar) + if x != "" { + var err error + limit, err = strconv.Atoi(x) + if err != nil { + panic(fmt.Errorf("could not parse %q: expected integer, got %q", envvar, x)) + } + } + return make(Semaphore, limit) +} + +// Wait waits for a semaphore before continuing +func (s Semaphore) Wait() { + s <- struct{}{} +} + +// Notify releases a semaphore +func (s Semaphore) Notify() { + <-s +} + +// TestAccPreCheckSyncronized waits for a semaphore and skips the test if there is no capacity +func TestAccPreCheckSyncronize(t *testing.T, semaphore Semaphore, resource string) { + if cap(semaphore) == 0 { + t.Skipf("concurrency for %s testing set to 0", resource) + } + + semaphore.Wait() +} diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index e1bc563e03c..2ed7291b7b2 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -19,7 +19,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { subnetResourceName := "aws_subnet.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, Steps: []resource.TestStep{ @@ -63,7 +63,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, Steps: []resource.TestStep{ @@ -133,7 +133,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_Subnets(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, Steps: []resource.TestStep{ @@ -175,7 +175,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { resourceName := "aws_ec2_client_vpn_authorization_rule.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy, Steps: []resource.TestStep{ @@ -194,6 +194,8 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn + defer testAccEc2ClientVpnEndpointSemaphore.Notify() + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ec2_client_vpn_authorization_rule" { continue diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index e16ea08360b..f3ec8e95a67 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -11,8 +11,17 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource" ) +const clientVpnEndpointDefaultLimit = 5 + +var testAccEc2ClientVpnEndpointSemaphore tfawsresource.Semaphore + +func init() { + testAccEc2ClientVpnEndpointSemaphore = tfawsresource.InitializeSemaphore("AWS_EC2_CLIENT_VPN_LIMIT", clientVpnEndpointDefaultLimit) +} + func init() { resource.AddTestSweepers("aws_ec2_client_vpn_endpoint", &resource.Sweeper{ Name: "aws_ec2_client_vpn_endpoint", @@ -79,7 +88,7 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -108,7 +117,7 @@ func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -130,7 +139,7 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -157,7 +166,7 @@ func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -185,7 +194,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -220,7 +229,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -246,7 +255,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { rStr := acctest.RandString(5) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -288,7 +297,7 @@ func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { resourceName := "aws_ec2_client_vpn_endpoint.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy, Steps: []resource.TestStep{ @@ -315,9 +324,15 @@ func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { }) } +func testAccPreCheckClientVPNSyncronize(t *testing.T) { + tfawsresource.TestAccPreCheckSyncronize(t, testAccEc2ClientVpnEndpointSemaphore, "Client VPN") +} + func testAccCheckAwsEc2ClientVpnEndpointDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn + defer testAccEc2ClientVpnEndpointSemaphore.Notify() + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_client_vpn_endpoint" { continue diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 42164b432f2..8274c13db41 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -17,7 +17,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -37,7 +37,7 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -56,6 +56,8 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn + defer testAccEc2ClientVpnEndpointSemaphore.Notify() + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ec2_client_vpn_network_association" { continue diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md index 863ef273f31..8ce496ac9e1 100644 --- a/docs/MAINTAINING.md +++ b/docs/MAINTAINING.md @@ -393,6 +393,7 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi | `AWS_COGNITO_USER_POOL_DOMAIN_CERTIFICATE_ARN` | Amazon Resource Name of ACM Certificate in `us-east-1` for Cognito User Pool Domain Name testing. | | `AWS_COGNITO_USER_POOL_DOMAIN_ROOT_DOMAIN` | Root domain name to use with Cognito User Pool Domain testing. | | `AWS_DEFAULT_REGION` | Primary AWS region for tests. Defaults to `us-west-2`. | +| `AWS_EC2_CLIENT_VPN_LIMIT` | Concurrency limit for Client VPN acceptance tests. [Default is 5](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/limits.html) if not specified. | | `AWS_EC2_EIP_PUBLIC_IPV4_POOL` | Identifier for EC2 Public IPv4 Pool for EC2 EIP testing. | | `AWS_GUARDDUTY_MEMBER_ACCOUNT_ID` | Identifier of AWS Account for GuardDuty Member testing. **DEPRECATED:** Should be replaced with standard alternate account handling for tests. | | `AWS_GUARDDUTY_MEMBER_EMAIL` | Email address for GuardDuty Member testing. **DEPRECATED:** It may be possible to use a placeholder email address instead. | From 706c2d26d199aa0807df6cda4eb7a1f092f3226e Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 29 Jun 2020 14:46:50 -0700 Subject: [PATCH 31/44] Removes unneeded Client VPN network association deletion from endpoint deletion --- aws/resource_aws_ec2_client_vpn_endpoint.go | 64 +-------------------- 1 file changed, 2 insertions(+), 62 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 09e9eb62217..23c8c961a65 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -194,8 +194,6 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ conn := meta.(*AWSClient).ec2conn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - var err error - result, err := conn.DescribeClientVpnEndpoints(&ec2.DescribeClientVpnEndpointsInput{ ClientVpnEndpointIds: []*string{aws.String(d.Id())}, }) @@ -264,44 +262,9 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ func resourceAwsEc2ClientVpnEndpointDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - netAssocResult, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(d.Id()), - }) + err := deleteClientVpnEndpoint(conn, d.Id()) if err != nil { - return err - } - - for _, n := range netAssocResult.ClientVpnTargetNetworks { - network := &ec2.DisassociateClientVpnTargetNetworkInput{} - - network.ClientVpnEndpointId = aws.String(d.Id()) - network.AssociationId = aws.String(*n.AssociationId) - - log.Printf("[DEBUG] Client VPN network association opts: %s", n) - _, err := conn.DisassociateClientVpnTargetNetwork(network) - if err != nil { - return fmt.Errorf("D Failure removing Client VPN network associations: %s \n %s", err, n) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AssociationStatusCodeDisassociating}, - Target: []string{ec2.AssociationStatusCodeDisassociated}, - Refresh: clientVpnNetworkAssociationRefresh(conn, aws.StringValue(n.AssociationId), d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - } - - log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", aws.StringValue(n.AssociationId)) - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err) - } - } - - _, err = conn.DeleteClientVpnEndpoint(&ec2.DeleteClientVpnEndpointInput{ - ClientVpnEndpointId: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("Error deleting Client VPN endpoint: %s", err) + return fmt.Errorf("error deleting Client VPN endpoint: %w", err) } return nil @@ -378,29 +341,6 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac return resourceAwsEc2ClientVpnEndpointRead(d, meta) } -func clientVpnNetworkAssociationRefresh(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{ - ClientVpnEndpointId: aws.String(cvepID), - AssociationIds: []*string{aws.String(cvnaID)}, - }) - - if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { - return 42, ec2.AssociationStatusCodeDisassociated, nil - } - - if err != nil { - return nil, "", err - } - - if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil { - return 42, ec2.AssociationStatusCodeDisassociated, nil - } - - return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil - } -} - func flattenConnLoggingConfig(lopts *ec2.ConnectionLogResponseOptions) []map[string]interface{} { m := make(map[string]interface{}) if lopts.CloudwatchLogGroup != nil { From ebc3e37a2f9757421307c0ce5c880dc7a19debc3 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 29 Jun 2020 15:50:59 -0700 Subject: [PATCH 32/44] Adds waiter for Client VPN to be deleted and removed --- aws/internal/service/ec2/waiter/status.go | 6 ++ aws/internal/service/ec2/waiter/waiter.go | 8 +- ...e_aws_ec2_client_vpn_authorization_rule.go | 9 ++- aws/resource_aws_ec2_client_vpn_endpoint.go | 75 +++++++++++++++++-- ...source_aws_ec2_client_vpn_endpoint_test.go | 25 ++----- 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index b126b94d67a..9c68a38c9c6 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -40,6 +40,12 @@ func LocalGatewayRouteTableVpcAssociationState(conn *ec2.EC2, localGatewayRouteT } } +const ( + ClientVpnEndpointStatusNotFound = "NotFound" + + ClientVpnEndpointStatusUnknown = "Unknown" +) + const ( ClientVpnAuthorizationRuleStatusNotFound = "NotFound" diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index d2af69d26d7..bf01b8358ea 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -52,7 +52,11 @@ func LocalGatewayRouteTableVpcAssociationDisassociated(conn *ec2.EC2, localGatew } const ( - ClientVpnEndpointAuthorizationRuleActiveTimeout = 1 * time.Minute + ClientVpnEndpointDeletedTimout = 5 * time.Minute +) + +const ( + ClientVpnAuthorizationRuleActiveTimeout = 1 * time.Minute - ClientVpnEndpointAuthorizationRuleRevokedTimeout = 1 * time.Minute + ClientVpnAuthorizationRuleRevokedTimeout = 1 * time.Minute ) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index 60ea7ef5eef..e3274ec470d 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -173,6 +173,9 @@ func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnI if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { return nil } + if err != nil { + return err + } _, err = ClientVpnAuthorizationRuleRevoked(conn, id) @@ -184,7 +187,7 @@ func ClientVpnAuthorizationRuleAuthorized(conn *ec2.EC2, authorizationRuleID str Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeAuthorizing}, Target: []string{ec2.ClientVpnAuthorizationRuleStatusCodeActive}, Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), - Timeout: waiter.ClientVpnEndpointAuthorizationRuleActiveTimeout, + Timeout: waiter.ClientVpnAuthorizationRuleActiveTimeout, } outputRaw, err := stateConf.WaitForState() @@ -201,7 +204,7 @@ func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeRevoking}, Target: []string{}, Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), - Timeout: waiter.ClientVpnEndpointAuthorizationRuleRevokedTimeout, + Timeout: waiter.ClientVpnAuthorizationRuleRevokedTimeout, } outputRaw, err := stateConf.WaitForState() @@ -214,7 +217,7 @@ func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string } // ClientVpnAuthorizationRuleStatus fetches the Client VPN authorization rule and its Status -// This should be in the waiters package, but has a dependency on isAWSErr() +// TODO: This should be in the waiters package, but has a dependency on isAWSErr() func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index 23c8c961a65..a6767213abe 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) const errCodeClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" @@ -182,7 +183,7 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac resp, err := conn.CreateClientVpnEndpoint(req) if err != nil { - return fmt.Errorf("Error creating Client VPN endpoint: %s", err) + return fmt.Errorf("Error creating Client VPN endpoint: %w", err) } d.SetId(*resp.ClientVpnEndpointId) @@ -205,7 +206,7 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ } if err != nil { - return fmt.Errorf("Error reading Client VPN endpoint: %s", err) + return fmt.Errorf("Error reading Client VPN endpoint: %w", err) } if result == nil || len(result.ClientVpnEndpoints) == 0 || result.ClientVpnEndpoints[0] == nil { @@ -234,17 +235,17 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ err = d.Set("authentication_options", flattenAuthOptsConfig(result.ClientVpnEndpoints[0].AuthenticationOptions)) if err != nil { - return fmt.Errorf("error setting authentication_options: %s", err) + return fmt.Errorf("error setting authentication_options: %w", err) } err = d.Set("connection_log_options", flattenConnLoggingConfig(result.ClientVpnEndpoints[0].ConnectionLogOptions)) if err != nil { - return fmt.Errorf("error setting connection_log_options: %s", err) + return fmt.Errorf("error setting connection_log_options: %w", err) } err = d.Set("tags", keyvaluetags.Ec2KeyValueTags(result.ClientVpnEndpoints[0].Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()) if err != nil { - return fmt.Errorf("error setting tags: %s", err) + return fmt.Errorf("error setting tags: %w", err) } arn := arn.ARN{ @@ -328,13 +329,13 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac } if _, err := conn.ModifyClientVpnEndpoint(req); err != nil { - return fmt.Errorf("Error modifying Client VPN endpoint: %s", err) + return fmt.Errorf("Error modifying Client VPN endpoint: %w", err) } if d.HasChange("tags") { o, n := d.GetChange("tags") if err := keyvaluetags.Ec2UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating EC2 Client VPN Endpoint (%s) tags: %s", d.Id(), err) + return fmt.Errorf("error updating EC2 Client VPN Endpoint (%s) tags: %w", d.Id(), err) } } @@ -389,3 +390,63 @@ func expandEc2ClientVpnAuthenticationRequest(data map[string]interface{}) *ec2.C return req } + +func deleteClientVpnEndpoint(conn *ec2.EC2, endpointID string) error { + _, err := conn.DeleteClientVpnEndpoint(&ec2.DeleteClientVpnEndpointInput{ + ClientVpnEndpointId: aws.String(endpointID), + }) + if isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + return nil + } + if err != nil { + return err + } + + _, err = ClientVpnEndpointDeleted(conn, endpointID) + + return err +} + +func ClientVpnEndpointDeleted(conn *ec2.EC2, id string) (*ec2.ClientVpnEndpoint, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnEndpointStatusCodeDeleting}, + Target: []string{}, + Refresh: ClientVpnEndpointStatus(conn, id), + Timeout: waiter.ClientVpnEndpointDeletedTimout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ClientVpnEndpoint); ok { + return output, err + } + + return nil, err +} + +// ClientVpnEndpointStatus fetches the Client VPN endpoint and its Status +// TODO: This should be in the waiters package, but has a dependency on isAWSErr() +func ClientVpnEndpointStatus(conn *ec2.EC2, endpointID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + result, err := conn.DescribeClientVpnEndpoints(&ec2.DescribeClientVpnEndpointsInput{ + ClientVpnEndpointIds: aws.StringSlice([]string{endpointID}), + }) + if isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + return nil, waiter.ClientVpnEndpointStatusNotFound, nil + } + if err != nil { + return nil, waiter.ClientVpnEndpointStatusUnknown, err + } + + if result == nil || len(result.ClientVpnEndpoints) == 0 || result.ClientVpnEndpoints[0] == nil { + return nil, waiter.ClientVpnEndpointStatusNotFound, nil + } + + endpoint := result.ClientVpnEndpoints[0] + if endpoint.Status == nil || endpoint.Status.Code == nil { + return endpoint, waiter.ClientVpnEndpointStatusUnknown, nil + } + + return endpoint, aws.StringValue(endpoint.Status.Code), nil + } +} diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index f3ec8e95a67..e2ae2ce67aa 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -36,7 +36,7 @@ func testSweepEc2ClientVpnEndpoints(region string) error { client, err := sharedClientForRegion(region) if err != nil { - return fmt.Errorf("error getting client: %s", err) + return fmt.Errorf("error getting client: %w", err) } conn := client.(*AWSClient).ec2conn @@ -51,24 +51,15 @@ func testSweepEc2ClientVpnEndpoints(region string) error { } if err != nil { - return fmt.Errorf("error retrieving Client VPN Endpoints: %s", err) + return fmt.Errorf("error retrieving Client VPN Endpoints: %w", err) } for _, clientVpnEndpoint := range output.ClientVpnEndpoints { - if clientVpnEndpoint.Status != nil && aws.StringValue(clientVpnEndpoint.Status.Code) == ec2.ClientVpnEndpointStatusCodeDeleted { - continue - } - id := aws.StringValue(clientVpnEndpoint.ClientVpnEndpointId) - input := &ec2.DeleteClientVpnEndpointInput{ - ClientVpnEndpointId: clientVpnEndpoint.ClientVpnEndpointId, - } - log.Printf("[INFO] Deleting Client VPN Endpoint: %s", id) - _, err := conn.DeleteClientVpnEndpoint(input) - + err := deleteClientVpnEndpoint(conn, id) if err != nil { - return fmt.Errorf("error deleting Client VPN Endpoint (%s): %s", id, err) + return fmt.Errorf("error deleting Client VPN Endpoint (%s): %w", id, err) } } @@ -339,13 +330,13 @@ func testAccCheckAwsEc2ClientVpnEndpointDestroy(s *terraform.State) error { } input := &ec2.DescribeClientVpnEndpointsInput{ - ClientVpnEndpointIds: []*string{aws.String(rs.Primary.ID)}, + ClientVpnEndpointIds: aws.StringSlice([]string{rs.Primary.ID}), } resp, _ := conn.DescribeClientVpnEndpoints(input) for _, v := range resp.ClientVpnEndpoints { - if *v.ClientVpnEndpointId == rs.Primary.ID && !(*v.Status.Code == ec2.ClientVpnEndpointStatusCodeDeleted) { - return fmt.Errorf("[DESTROY ERROR] Client VPN endpoint (%s) not deleted", rs.Primary.ID) + if aws.StringValue(v.ClientVpnEndpointId) == rs.Primary.ID { + return fmt.Errorf("Client VPN endpoint (%s) not deleted", rs.Primary.ID) } } } @@ -362,7 +353,7 @@ func testAccCheckAwsEc2ClientVpnEndpointExists(name string, endpoint *ec2.Client conn := testAccProvider.Meta().(*AWSClient).ec2conn input := &ec2.DescribeClientVpnEndpointsInput{ - ClientVpnEndpointIds: []*string{aws.String(rs.Primary.ID)}, + ClientVpnEndpointIds: aws.StringSlice([]string{rs.Primary.ID}), } result, err := conn.DescribeClientVpnEndpoints(input) if err != nil { From 923ed2ff184ad96df8ad08059fabf94d89b71732 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 30 Jun 2020 13:27:36 -0700 Subject: [PATCH 33/44] Moves Endpoint delete waiter to waiter package. Renames some error constants --- aws/internal/service/ec2/errors.go | 23 +++++++- aws/internal/service/ec2/waiter/status.go | 27 ++++++++++ aws/internal/service/ec2/waiter/waiter.go | 17 ++++++ ...e_aws_ec2_client_vpn_authorization_rule.go | 6 +-- ..._ec2_client_vpn_authorization_rule_test.go | 2 +- aws/resource_aws_ec2_client_vpn_endpoint.go | 54 ++----------------- ..._aws_ec2_client_vpn_network_association.go | 9 ++-- 7 files changed, 78 insertions(+), 60 deletions(-) diff --git a/aws/internal/service/ec2/errors.go b/aws/internal/service/ec2/errors.go index 6f2a90e9546..48bfbe7a44d 100644 --- a/aws/internal/service/ec2/errors.go +++ b/aws/internal/service/ec2/errors.go @@ -1,3 +1,24 @@ package ec2 -const ErrCodeClientVpnEndpointAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" +import ( + "errors" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// Copied from aws-sdk-go-base +// Can be removed when aws-sdk-go-base v0.6+ is merged +// TODO: +func ErrCodeEquals(err error, code string) bool { + var awsErr awserr.Error + if errors.As(err, &awsErr) { + return awsErr.Code() == code + } + return false +} + +const ErrCodeClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" + +const ErrCodeClientVpnAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" + +const ErrCodeClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationId.NotFound" diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 9c68a38c9c6..2720446e880 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -4,6 +4,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) // LocalGatewayRouteTableVpcAssociationState fetches the LocalGatewayRouteTableVpcAssociation and its State @@ -46,6 +47,32 @@ const ( ClientVpnEndpointStatusUnknown = "Unknown" ) +// ClientVpnEndpointStatus fetches the Client VPN endpoint and its Status +func ClientVpnEndpointStatus(conn *ec2.EC2, endpointID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + result, err := conn.DescribeClientVpnEndpoints(&ec2.DescribeClientVpnEndpointsInput{ + ClientVpnEndpointIds: aws.StringSlice([]string{endpointID}), + }) + if tfec2.ErrCodeEquals(err, tfec2.ErrCodeClientVpnEndpointIdNotFound) { + return nil, ClientVpnEndpointStatusNotFound, nil + } + if err != nil { + return nil, ClientVpnEndpointStatusUnknown, err + } + + if result == nil || len(result.ClientVpnEndpoints) == 0 || result.ClientVpnEndpoints[0] == nil { + return nil, ClientVpnEndpointStatusNotFound, nil + } + + endpoint := result.ClientVpnEndpoints[0] + if endpoint.Status == nil || endpoint.Status.Code == nil { + return endpoint, ClientVpnEndpointStatusUnknown, nil + } + + return endpoint, aws.StringValue(endpoint.Status.Code), nil + } +} + const ( ClientVpnAuthorizationRuleStatusNotFound = "NotFound" diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index bf01b8358ea..9be4ac387ad 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -55,6 +55,23 @@ const ( ClientVpnEndpointDeletedTimout = 5 * time.Minute ) +func ClientVpnEndpointDeleted(conn *ec2.EC2, id string) (*ec2.ClientVpnEndpoint, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnEndpointStatusCodeDeleting}, + Target: []string{}, + Refresh: ClientVpnEndpointStatus(conn, id), + Timeout: ClientVpnEndpointDeletedTimout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ClientVpnEndpoint); ok { + return output, err + } + + return nil, err +} + const ( ClientVpnAuthorizationRuleActiveTimeout = 1 * time.Minute diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index e3274ec470d..7abf9f27891 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -106,7 +106,7 @@ func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta i d.Get("access_group_id").(string), ) - if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") { log.Printf("[WARN] EC2 Client VPN authorization rule (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -170,7 +170,7 @@ func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnI id := tfec2.ClientVpnAuthorizationRuleCreateID(aws.StringValue(input.ClientVpnEndpointId), aws.StringValue(input.TargetNetworkCidr), aws.StringValue(input.AccessGroupId)) _, err := conn.RevokeClientVpnIngress(input) - if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") { return nil } if err != nil { @@ -226,7 +226,7 @@ func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) } result, err := findClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) - if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") { return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil } if err != nil { diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index 2ed7291b7b2..c200cb0b7f0 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -205,7 +205,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err if err == nil { return fmt.Errorf("Client VPN authorization rule (%s) still exists", rs.Primary.ID) } - if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointAuthorizationRuleNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") || isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { continue } return err diff --git a/aws/resource_aws_ec2_client_vpn_endpoint.go b/aws/resource_aws_ec2_client_vpn_endpoint.go index a6767213abe..d7cf7db4eb4 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint.go @@ -7,15 +7,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) -const errCodeClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" - func resourceAwsEc2ClientVpnEndpoint() *schema.Resource { return &schema.Resource{ Create: resourceAwsEc2ClientVpnEndpointCreate, @@ -199,7 +197,7 @@ func resourceAwsEc2ClientVpnEndpointRead(d *schema.ResourceData, meta interface{ ClientVpnEndpointIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Endpoint (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -395,58 +393,14 @@ func deleteClientVpnEndpoint(conn *ec2.EC2, endpointID string) error { _, err := conn.DeleteClientVpnEndpoint(&ec2.DeleteClientVpnEndpointInput{ ClientVpnEndpointId: aws.String(endpointID), }) - if isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { return nil } if err != nil { return err } - _, err = ClientVpnEndpointDeleted(conn, endpointID) + _, err = waiter.ClientVpnEndpointDeleted(conn, endpointID) return err } - -func ClientVpnEndpointDeleted(conn *ec2.EC2, id string) (*ec2.ClientVpnEndpoint, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.ClientVpnEndpointStatusCodeDeleting}, - Target: []string{}, - Refresh: ClientVpnEndpointStatus(conn, id), - Timeout: waiter.ClientVpnEndpointDeletedTimout, - } - - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*ec2.ClientVpnEndpoint); ok { - return output, err - } - - return nil, err -} - -// ClientVpnEndpointStatus fetches the Client VPN endpoint and its Status -// TODO: This should be in the waiters package, but has a dependency on isAWSErr() -func ClientVpnEndpointStatus(conn *ec2.EC2, endpointID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - result, err := conn.DescribeClientVpnEndpoints(&ec2.DescribeClientVpnEndpointsInput{ - ClientVpnEndpointIds: aws.StringSlice([]string{endpointID}), - }) - if isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { - return nil, waiter.ClientVpnEndpointStatusNotFound, nil - } - if err != nil { - return nil, waiter.ClientVpnEndpointStatusUnknown, err - } - - if result == nil || len(result.ClientVpnEndpoints) == 0 || result.ClientVpnEndpoints[0] == nil { - return nil, waiter.ClientVpnEndpointStatusNotFound, nil - } - - endpoint := result.ClientVpnEndpoints[0] - if endpoint.Status == nil || endpoint.Status.Code == nil { - return endpoint, waiter.ClientVpnEndpointStatusUnknown, nil - } - - return endpoint, aws.StringValue(endpoint.Status.Code), nil - } -} diff --git a/aws/resource_aws_ec2_client_vpn_network_association.go b/aws/resource_aws_ec2_client_vpn_network_association.go index d516098e9af..d7234e5b073 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association.go +++ b/aws/resource_aws_ec2_client_vpn_network_association.go @@ -8,10 +8,9 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) -const errCodeClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationId.NotFound" - func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource { return &schema.Resource{ Create: resourceAwsEc2ClientVpnNetworkAssociationCreate, @@ -87,7 +86,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta AssociationIds: []*string{aws.String(d.Id())}, }) - if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -129,7 +128,7 @@ func resourceAwsEc2ClientVpnNetworkAssociationDelete(d *schema.ResourceData, met AssociationId: aws.String(d.Id()), }) - if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { return nil } @@ -160,7 +159,7 @@ func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID AssociationIds: []*string{aws.String(cvnaID)}, }) - if isAWSErr(err, errCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, errCodeClientVpnEndpointIdNotFound, "") { + if isAWSErr(err, tfec2.ErrCodeClientVpnAssociationIdNotFound, "") || isAWSErr(err, tfec2.ErrCodeClientVpnEndpointIdNotFound, "") { return 42, ec2.AssociationStatusCodeDisassociated, nil } From aaf82b70c54543311bbb779a1afe63765e62def6 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 30 Jun 2020 13:28:42 -0700 Subject: [PATCH 34/44] Combines Client VPN tests for synchronization --- ..._ec2_client_vpn_authorization_rule_test.go | 8 +-- ...source_aws_ec2_client_vpn_endpoint_test.go | 65 ++++++++++++++++--- ...ec2_client_vpn_network_association_test.go | 10 ++- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index c200cb0b7f0..6f9c71fcbf1 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -12,7 +12,7 @@ import ( tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) -func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { +func testAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { var v ec2.AuthorizationRule rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_authorization_rule.test" @@ -41,7 +41,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { +func testAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { var v1, v2, v3, v4 ec2.AuthorizationRule rStr := acctest.RandString(5) resource1Name := "aws_ec2_client_vpn_authorization_rule.test1" @@ -113,7 +113,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_groups(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnAuthorizationRule_Subnets(t *testing.T) { +func testAccAwsEc2ClientVpnAuthorizationRule_Subnets(t *testing.T) { var v1, v2, v3 ec2.AuthorizationRule rStr := acctest.RandString(5) resource1Name := "aws_ec2_client_vpn_authorization_rule.test1" @@ -169,7 +169,7 @@ func TestAccAwsEc2ClientVpnAuthorizationRule_Subnets(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { +func testAccAwsEc2ClientVpnAuthorizationRule_disappears(t *testing.T) { var v ec2.AuthorizationRule rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_authorization_rule.test" diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index e2ae2ce67aa..ebe9cb6e4f5 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -73,7 +73,54 @@ func testSweepEc2ClientVpnEndpoints(region string) error { return nil } -func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { +func TestAccAwsEc2ClientVpnX(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "Endpoint": { + "basic": testAccAwsEc2ClientVpnEndpoint_basic, + "disappears": testAccAwsEc2ClientVpnEndpoint_disappears, + "msAD": testAccAwsEc2ClientVpnEndpoint_msAD, + "mutualAuthAndMsAD": testAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD, + "withLogGroup": testAccAwsEc2ClientVpnEndpoint_withLogGroup, + "withDNSServers": testAccAwsEc2ClientVpnEndpoint_withDNSServers, + "tags": testAccAwsEc2ClientVpnEndpoint_tags, + "splitTunnel": testAccAwsEc2ClientVpnEndpoint_splitTunnel, + }, + "AuthorizationRule": { + "basic": testAccAwsEc2ClientVpnAuthorizationRule_basic, + "groups": testAccAwsEc2ClientVpnAuthorizationRule_groups, + "Subnets": testAccAwsEc2ClientVpnAuthorizationRule_Subnets, + "disappears": testAccAwsEc2ClientVpnAuthorizationRule_disappears, + }, + "NetworkAssociation": { + "basic": testAccAwsEc2ClientVpnNetworkAssociation_basic, + "disappears": testAccAwsEc2ClientVpnNetworkAssociation_disappears, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} + +func synchronizedTest(t *testing.T, semaphore tfawsresource.Semaphore, test func(t *testing.T)) func(t *testing.T) { + return func(t *testing.T) { + semaphore.Wait() + + test(t) + + semaphore.Notify() + } +} + +func testAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -102,7 +149,7 @@ func TestAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -124,7 +171,7 @@ func TestAccAwsEc2ClientVpnEndpoint_disappears(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -151,7 +198,7 @@ func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -179,7 +226,7 @@ func TestAccAwsEc2ClientVpnEndpoint_mutualAuthAndMsAD(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -214,7 +261,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withLogGroup(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { var v1, v2 ec2.ClientVpnEndpoint rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -240,7 +287,7 @@ func TestAccAwsEc2ClientVpnEndpoint_withDNSServers(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { var v1, v2, v3 ec2.ClientVpnEndpoint resourceName := "aws_ec2_client_vpn_endpoint.test" rStr := acctest.RandString(5) @@ -282,7 +329,7 @@ func TestAccAwsEc2ClientVpnEndpoint_tags(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { +func testAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { var v1, v2 ec2.ClientVpnEndpoint rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_ec2_client_vpn_endpoint.test" @@ -410,7 +457,7 @@ resource "aws_subnet" "test2" { } resource "aws_directory_service_directory" "test" { - name = "corp.notexample.com" + name = "vpn.notexample.com" password = "SuperSecretPassw0rd" type = "MicrosoftAD" diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 8274c13db41..484a8112862 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -11,13 +11,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { +func testAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { var assoc1 ec2.TargetNetwork rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -31,13 +31,13 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { }) } -func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { +func testAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { var assoc1 ec2.TargetNetwork rStr := acctest.RandString(5) resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -56,8 +56,6 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn - defer testAccEc2ClientVpnEndpointSemaphore.Notify() - for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ec2_client_vpn_network_association" { continue From 9c04ab52b2124b54f0d9e2fd1ea4eafcb63f3977 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 30 Jun 2020 13:38:40 -0700 Subject: [PATCH 35/44] Fixes test name --- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index ebe9cb6e4f5..bf63872744e 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -73,7 +73,7 @@ func testSweepEc2ClientVpnEndpoints(region string) error { return nil } -func TestAccAwsEc2ClientVpnX(t *testing.T) { +func TestAccAwsEc2ClientVpn(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "Endpoint": { "basic": testAccAwsEc2ClientVpnEndpoint_basic, From 269186a0a88cb7fa820e8a02e9fd673e7319172a Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 30 Jun 2020 14:42:42 -0700 Subject: [PATCH 36/44] Removes unused function --- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index bf63872744e..331605eac97 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -110,16 +110,6 @@ func TestAccAwsEc2ClientVpn(t *testing.T) { } } -func synchronizedTest(t *testing.T, semaphore tfawsresource.Semaphore, test func(t *testing.T)) func(t *testing.T) { - return func(t *testing.T) { - semaphore.Wait() - - test(t) - - semaphore.Notify() - } -} - func testAccAwsEc2ClientVpnEndpoint_basic(t *testing.T) { var v ec2.ClientVpnEndpoint rStr := acctest.RandString(5) From d85342a7fce991b24e07016659487db040813ac8 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 30 Jun 2020 17:15:01 -0700 Subject: [PATCH 37/44] Fixes synchronization for network association and consolidates test cases --- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 14 ++++++-------- ..._aws_ec2_client_vpn_network_association_test.go | 10 ++++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 331605eac97..7ddb26844ca 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -99,14 +99,12 @@ func TestAccAwsEc2ClientVpn(t *testing.T) { for group, m := range testCases { m := m - t.Run(group, func(t *testing.T) { - for name, tc := range m { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } - }) + for name, tc := range m { + tc := tc + t.Run(fmt.Sprintf("%s_%s", group, name), func(t *testing.T) { + tc(t) + }) + } } } diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 484a8112862..6136ba539a5 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -17,7 +17,7 @@ func testAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -37,7 +37,7 @@ func testAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { resourceName := "aws_ec2_client_vpn_network_association.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckClientVPNSyncronize(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ @@ -56,6 +56,8 @@ func testAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn + defer testAccEc2ClientVpnEndpointSemaphore.Notify() + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ec2_client_vpn_network_association" { continue @@ -67,7 +69,7 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) er }) for _, v := range resp.ClientVpnTargetNetworks { - if *v.AssociationId == rs.Primary.ID && !(*v.Status.Code == "Disassociated") { + if *v.AssociationId == rs.Primary.ID && !(*v.Status.Code == ec2.AssociationStatusCodeDisassociated) { return fmt.Errorf("[DESTROY ERROR] Client VPN network association (%s) not deleted", rs.Primary.ID) } } @@ -99,7 +101,7 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } for _, a := range resp.ClientVpnTargetNetworks { - if *a.AssociationId == rs.Primary.ID && !(*a.Status.Code == "Disassociated") { + if *a.AssociationId == rs.Primary.ID && !(*a.Status.Code == ec2.AssociationStatusCodeDisassociated) { *assoc = *a return nil } From a76229c52e3e8e473db53dad225294226b08fe43 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 6 Jul 2020 17:51:03 -0700 Subject: [PATCH 38/44] Moves waiter and status functions to `waiter` package --- aws/ec2_filters.go | 26 +---- aws/internal/service/ec2/filter.go | 55 +++++++++ aws/internal/service/ec2/finder/finder.go | 33 ++++++ aws/internal/service/ec2/waiter/status.go | 37 ++++++ aws/internal/service/ec2/waiter/waiter.go | 34 ++++++ ...e_aws_ec2_client_vpn_authorization_rule.go | 107 ++---------------- ..._ec2_client_vpn_authorization_rule_test.go | 5 +- 7 files changed, 172 insertions(+), 125 deletions(-) create mode 100644 aws/internal/service/ec2/filter.go create mode 100644 aws/internal/service/ec2/finder/finder.go diff --git a/aws/ec2_filters.go b/aws/ec2_filters.go index 4d231dc003d..121033ecbc6 100644 --- a/aws/ec2_filters.go +++ b/aws/ec2_filters.go @@ -2,13 +2,13 @@ package aws import ( "fmt" - "sort" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" ) // buildEC2AttributeFilterList takes a flat map of scalar attributes (most @@ -33,29 +33,7 @@ import ( // the EC2 API, to aid in the implementation of Terraform data sources that // retrieve data about EC2 objects. func buildEC2AttributeFilterList(attrs map[string]string) []*ec2.Filter { - var filters []*ec2.Filter - - // sort the filters by name to make the output deterministic - var names []string - for filterName := range attrs { - names = append(names, filterName) - } - - sort.Strings(names) - - for _, filterName := range names { - value := attrs[filterName] - if value == "" { - continue - } - - filters = append(filters, &ec2.Filter{ - Name: aws.String(filterName), - Values: []*string{aws.String(value)}, - }) - } - - return filters + return tfec2.BuildAttributeFilterList(attrs) } // buildEC2TagFilterList takes a []*ec2.Tag and produces a []*ec2.Filter that diff --git a/aws/internal/service/ec2/filter.go b/aws/internal/service/ec2/filter.go new file mode 100644 index 00000000000..60ca4a2b682 --- /dev/null +++ b/aws/internal/service/ec2/filter.go @@ -0,0 +1,55 @@ +package ec2 + +import ( + "sort" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" +) + +// BuildAttributeFilterList takes a flat map of scalar attributes (most +// likely values extracted from a *schema.ResourceData on an EC2-querying +// data source) and produces a []*ec2.Filter representing an exact match +// for each of the given non-empty attributes. +// +// The keys of the given attributes map are the attribute names expected +// by the EC2 API, which are usually either in camelcase or with dash-separated +// words. We conventionally map these to underscore-separated identifiers +// with the same words when presenting these as data source query attributes +// in Terraform. +// +// It's the callers responsibility to transform any non-string values into +// the appropriate string serialization required by the AWS API when +// encoding the given filter. Any attributes given with empty string values +// are ignored, assuming that the user wishes to leave that attribute +// unconstrained while filtering. +// +// The purpose of this function is to create values to pass in +// for the "Filters" attribute on most of the "Describe..." API functions in +// the EC2 API, to aid in the implementation of Terraform data sources that +// retrieve data about EC2 objects. +func BuildAttributeFilterList(attrs map[string]string) []*ec2.Filter { + var filters []*ec2.Filter + + // sort the filters by name to make the output deterministic + var names []string + for filterName := range attrs { + names = append(names, filterName) + } + + sort.Strings(names) + + for _, filterName := range names { + value := attrs[filterName] + if value == "" { + continue + } + + filters = append(filters, &ec2.Filter{ + Name: aws.String(filterName), + Values: []*string{aws.String(value)}, + }) + } + + return filters +} diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go new file mode 100644 index 00000000000..eb24c411d16 --- /dev/null +++ b/aws/internal/service/ec2/finder/finder.go @@ -0,0 +1,33 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" +) + +func ClientVpnAuthorizationRule(conn *ec2.EC2, endpointID, targetNetworkCidr, accessGroupID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { + filters := map[string]string{ + "destination-cidr": targetNetworkCidr, + } + if accessGroupID != "" { + filters["group-id"] = accessGroupID + } + + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(endpointID), + Filters: tfec2.BuildAttributeFilterList(filters), + } + + return conn.DescribeClientVpnAuthorizationRules(input) + +} + +func ClientVpnAuthorizationRuleByID(conn *ec2.EC2, authorizationRuleID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) + if err != nil { + return nil, err + } + + return ClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) +} diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 2720446e880..8df3050a1e8 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -1,10 +1,13 @@ package waiter import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" ) // LocalGatewayRouteTableVpcAssociationState fetches the LocalGatewayRouteTableVpcAssociation and its State @@ -78,3 +81,37 @@ const ( ClientVpnAuthorizationRuleStatusUnknown = "Unknown" ) + +// ClientVpnAuthorizationRuleStatus fetches the Client VPN authorization rule and its Status +// TODO: This should be in the waiters package, but has a dependency on isAWSErr() +func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) + if err != nil { + return nil, ClientVpnAuthorizationRuleStatusUnknown, err + } + + result, err := finder.ClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) + if tfec2.ErrCodeEquals(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound) { + return nil, ClientVpnAuthorizationRuleStatusNotFound, nil + } + if err != nil { + return nil, ClientVpnAuthorizationRuleStatusUnknown, err + } + + if result == nil || len(result.AuthorizationRules) == 0 || result.AuthorizationRules[0] == nil { + return nil, ClientVpnAuthorizationRuleStatusNotFound, nil + } + + if len(result.AuthorizationRules) > 1 { + return nil, ClientVpnAuthorizationRuleStatusUnknown, fmt.Errorf("internal error: found %d results for Client VPN authorization rule (%s) status, need 1", len(result.AuthorizationRules), authorizationRuleID) + } + + rule := result.AuthorizationRules[0] + if rule.Status == nil || rule.Status.Code == nil { + return rule, ClientVpnAuthorizationRuleStatusUnknown, nil + } + + return rule, aws.StringValue(rule.Status.Code), nil + } +} diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index 9be4ac387ad..531e33b8da1 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -77,3 +77,37 @@ const ( ClientVpnAuthorizationRuleRevokedTimeout = 1 * time.Minute ) + +func ClientVpnAuthorizationRuleAuthorized(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeAuthorizing}, + Target: []string{ec2.ClientVpnAuthorizationRuleStatusCodeActive}, + Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), + Timeout: ClientVpnAuthorizationRuleActiveTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { + return output, err + } + + return nil, err +} + +func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeRevoking}, + Target: []string{}, + Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), + Timeout: ClientVpnAuthorizationRuleRevokedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index 7abf9f27891..51fc23802dc 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -6,9 +6,9 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) @@ -87,7 +87,7 @@ func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta return fmt.Errorf("error creating Client VPN authorization rule %q: %w", id, err) } - _, err = ClientVpnAuthorizationRuleAuthorized(conn, id) + _, err = waiter.ClientVpnAuthorizationRuleAuthorized(conn, id) if err != nil { return fmt.Errorf("error waiting for Client VPN authorization rule %q to be active: %w", id, err) } @@ -100,7 +100,7 @@ func resourceAwsEc2ClientVpnAuthorizationRuleCreate(d *schema.ResourceData, meta func resourceAwsEc2ClientVpnAuthorizationRuleRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - result, err := findClientVpnAuthorizationRule(conn, + result, err := finder.ClientVpnAuthorizationRule(conn, d.Get("client_vpn_endpoint_id").(string), d.Get("target_network_cidr").(string), d.Get("access_group_id").(string), @@ -167,7 +167,10 @@ func resourceAwsEc2ClientVpnAuthorizationRuleImport(d *schema.ResourceData, meta } func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnIngressInput) error { - id := tfec2.ClientVpnAuthorizationRuleCreateID(aws.StringValue(input.ClientVpnEndpointId), aws.StringValue(input.TargetNetworkCidr), aws.StringValue(input.AccessGroupId)) + id := tfec2.ClientVpnAuthorizationRuleCreateID( + aws.StringValue(input.ClientVpnEndpointId), + aws.StringValue(input.TargetNetworkCidr), + aws.StringValue(input.AccessGroupId)) _, err := conn.RevokeClientVpnIngress(input) if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") { @@ -177,101 +180,7 @@ func deleteClientVpnAuthorizationRule(conn *ec2.EC2, input *ec2.RevokeClientVpnI return err } - _, err = ClientVpnAuthorizationRuleRevoked(conn, id) + _, err = waiter.ClientVpnAuthorizationRuleRevoked(conn, id) return err } - -func ClientVpnAuthorizationRuleAuthorized(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeAuthorizing}, - Target: []string{ec2.ClientVpnAuthorizationRuleStatusCodeActive}, - Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), - Timeout: waiter.ClientVpnAuthorizationRuleActiveTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { - return output, err - } - - return nil, err -} - -func ClientVpnAuthorizationRuleRevoked(conn *ec2.EC2, authorizationRuleID string) (*ec2.AuthorizationRule, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.ClientVpnAuthorizationRuleStatusCodeRevoking}, - Target: []string{}, - Refresh: ClientVpnAuthorizationRuleStatus(conn, authorizationRuleID), - Timeout: waiter.ClientVpnAuthorizationRuleRevokedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*ec2.AuthorizationRule); ok { - return output, err - } - - return nil, err -} - -// ClientVpnAuthorizationRuleStatus fetches the Client VPN authorization rule and its Status -// TODO: This should be in the waiters package, but has a dependency on isAWSErr() -func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) - if err != nil { - return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err - } - - result, err := findClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) - if isAWSErr(err, tfec2.ErrCodeClientVpnAuthorizationRuleNotFound, "") { - return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil - } - if err != nil { - return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, err - } - - if result == nil || len(result.AuthorizationRules) == 0 || result.AuthorizationRules[0] == nil { - return nil, waiter.ClientVpnAuthorizationRuleStatusNotFound, nil - } - - if len(result.AuthorizationRules) > 1 { - return nil, waiter.ClientVpnAuthorizationRuleStatusUnknown, fmt.Errorf("internal error: found %d results for Client VPN authorization rule (%s) status, need 1", len(result.AuthorizationRules), authorizationRuleID) - } - - rule := result.AuthorizationRules[0] - if rule.Status == nil || rule.Status.Code == nil { - return rule, waiter.ClientVpnAuthorizationRuleStatusUnknown, nil - } - - return rule, aws.StringValue(rule.Status.Code), nil - } -} - -func findClientVpnAuthorizationRule(conn *ec2.EC2, endpointID, targetNetworkCidr, accessGroupID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { - filters := map[string]string{ - "destination-cidr": targetNetworkCidr, - } - if accessGroupID != "" { - filters["group-id"] = accessGroupID - } - - input := &ec2.DescribeClientVpnAuthorizationRulesInput{ - ClientVpnEndpointId: aws.String(endpointID), - Filters: buildEC2AttributeFilterList(filters), - } - - return conn.DescribeClientVpnAuthorizationRules(input) - -} - -func findClientVpnAuthorizationRuleByID(conn *ec2.EC2, authorizationRuleID string) (*ec2.DescribeClientVpnAuthorizationRulesOutput, error) { - endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) - if err != nil { - return nil, err - } - - return findClientVpnAuthorizationRule(conn, endpointID, targetNetworkCidr, accessGroupID) -} diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index d41f933d2a3..2f71991ed15 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" ) func testAccAwsEc2ClientVpnAuthorizationRule_basic(t *testing.T) { @@ -201,7 +202,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleDestroy(s *terraform.State) err continue } - _, err := findClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) + _, err := finder.ClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) if err == nil { return fmt.Errorf("Client VPN authorization rule (%s) still exists", rs.Primary.ID) } @@ -227,7 +228,7 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. conn := testAccProvider.Meta().(*AWSClient).ec2conn - result, err := findClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) + result, err := finder.ClientVpnAuthorizationRuleByID(conn, rs.Primary.ID) if err != nil { return fmt.Errorf("error reading Client VPN authorization rule (%s): %w", rs.Primary.ID, err) } From 20569d6575a6687e401f506cd8f67cdb6a38eb63 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 6 Jul 2020 17:51:32 -0700 Subject: [PATCH 39/44] Adds validity check for ID parsing --- aws/internal/service/ec2/id.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/internal/service/ec2/id.go b/aws/internal/service/ec2/id.go index 2c6b3827215..ada06090f63 100644 --- a/aws/internal/service/ec2/id.go +++ b/aws/internal/service/ec2/id.go @@ -18,10 +18,10 @@ func ClientVpnAuthorizationRuleCreateID(endpointID, targetNetworkCidr, accessGro func ClientVpnAuthorizationRuleParseID(id string) (string, string, string, error) { parts := strings.Split(id, clientVpnAuthorizationRuleIDSeparator) - if len(parts) == 2 { + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { return parts[0], parts[1], "", nil } - if len(parts) == 3 { + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { return parts[0], parts[1], parts[2], nil } From bf963eebbfc13195301fa10e143528a6476d7f41 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 6 Jul 2020 17:52:27 -0700 Subject: [PATCH 40/44] Moves synchronization functions to experimental package --- .../{tfawsresource => experimental/sync}/sync.go | 6 +++++- aws/resource_aws_ec2_client_vpn_endpoint_test.go | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) rename aws/internal/{tfawsresource => experimental/sync}/sync.go (74%) diff --git a/aws/internal/tfawsresource/sync.go b/aws/internal/experimental/sync/sync.go similarity index 74% rename from aws/internal/tfawsresource/sync.go rename to aws/internal/experimental/sync/sync.go index a7163aa487b..bf363e89b08 100644 --- a/aws/internal/tfawsresource/sync.go +++ b/aws/internal/experimental/sync/sync.go @@ -1,4 +1,4 @@ -package tfawsresource +package sync import ( "fmt" @@ -11,6 +11,7 @@ import ( type Semaphore chan struct{} // InitializeSemaphore initializes a semaphore with a default capacity or overrides it using an environment variable +// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE. func InitializeSemaphore(envvar string, defaultLimit int) Semaphore { limit := defaultLimit x := os.Getenv(envvar) @@ -25,16 +26,19 @@ func InitializeSemaphore(envvar string, defaultLimit int) Semaphore { } // Wait waits for a semaphore before continuing +// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE. func (s Semaphore) Wait() { s <- struct{}{} } // Notify releases a semaphore +// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE. func (s Semaphore) Notify() { <-s } // TestAccPreCheckSyncronized waits for a semaphore and skips the test if there is no capacity +// NOTE: this is currently an experimental feature and is likely to change. DO NOT USE. func TestAccPreCheckSyncronize(t *testing.T, semaphore Semaphore, resource string) { if cap(semaphore) == 0 { t.Skipf("concurrency for %s testing set to 0", resource) diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 7ddb26844ca..35dd682da9d 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -11,15 +11,15 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/experimental/sync" ) const clientVpnEndpointDefaultLimit = 5 -var testAccEc2ClientVpnEndpointSemaphore tfawsresource.Semaphore +var testAccEc2ClientVpnEndpointSemaphore sync.Semaphore func init() { - testAccEc2ClientVpnEndpointSemaphore = tfawsresource.InitializeSemaphore("AWS_EC2_CLIENT_VPN_LIMIT", clientVpnEndpointDefaultLimit) + testAccEc2ClientVpnEndpointSemaphore = sync.InitializeSemaphore("AWS_EC2_CLIENT_VPN_LIMIT", clientVpnEndpointDefaultLimit) } func init() { @@ -73,6 +73,10 @@ func testSweepEc2ClientVpnEndpoints(region string) error { return nil } +// This is part of an experimental feature, do not use this as a starting point for tests +// "This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. +// What is here was dangerous and repulsive to us. This message is a warning about danger." +// -- https://hyperallergic.com/312318/a-nuclear-warning-designed-to-last-10000-years/ func TestAccAwsEc2ClientVpn(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "Endpoint": { @@ -351,7 +355,7 @@ func testAccAwsEc2ClientVpnEndpoint_splitTunnel(t *testing.T) { } func testAccPreCheckClientVPNSyncronize(t *testing.T) { - tfawsresource.TestAccPreCheckSyncronize(t, testAccEc2ClientVpnEndpointSemaphore, "Client VPN") + sync.TestAccPreCheckSyncronize(t, testAccEc2ClientVpnEndpointSemaphore, "Client VPN") } func testAccCheckAwsEc2ClientVpnEndpointDestroy(s *terraform.State) error { From bb1f70a2481fa869ae7b1de55aedd0ea7d9e4687 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 6 Jul 2020 17:52:40 -0700 Subject: [PATCH 41/44] Fixes documentation re-ordering --- website/docs/r/ec2_client_vpn_endpoint.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/r/ec2_client_vpn_endpoint.html.markdown b/website/docs/r/ec2_client_vpn_endpoint.html.markdown index 22f908e57e2..994503189ee 100644 --- a/website/docs/r/ec2_client_vpn_endpoint.html.markdown +++ b/website/docs/r/ec2_client_vpn_endpoint.html.markdown @@ -36,15 +36,15 @@ resource "aws_ec2_client_vpn_endpoint" "example" { The following arguments are supported: -* `description` - (Optional) Name of the repository. +* `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. * `client_cidr_block` - (Required) The IPv4 address range, in CIDR notation, from which to assign client IP addresses. The address range cannot overlap with the local CIDR of the VPC in which the associated subnet is located, or the routes that you add manually. The address range cannot be changed after the Client VPN endpoint has been created. The CIDR block should be /22 or greater. +* `connection_log_options` - (Required) Information about the client connection logging options. +* `description` - (Optional) Name of the repository. * `dns_servers` - (Optional) Information about the DNS servers to be used for DNS resolution. A Client VPN endpoint can have up to two DNS servers. If no DNS server is specified, the DNS address of the VPC that is to be associated with Client VPN endpoint is used as the DNS server. * `server_certificate_arn` - (Required) The ARN of the ACM server certificate. * `split_tunnel` - (Optional) Indicates whether split-tunnel is enabled on VPN endpoint. Default value is `false`. -* `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. -* `authentication_options` - (Required) Information about the authentication method to be used to authenticate clients. -* `connection_log_options` - (Required) Information about the client connection logging options. * `tags` - (Optional) A mapping of tags to assign to the resource. +* `transport_protocol` - (Optional) The transport protocol to be used by the VPN session. Default value is `udp`. ### `authentication_options` Argument Reference From 385826294c63278b1d9032327209fa90a4f58800 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 6 Jul 2020 17:53:30 -0700 Subject: [PATCH 42/44] Makes acceptance tests more stand-alone --- ..._ec2_client_vpn_authorization_rule_test.go | 81 ++++++++++++++++--- ...source_aws_ec2_client_vpn_endpoint_test.go | 13 +-- ...ec2_client_vpn_network_association_test.go | 74 +++++++++++++++-- 3 files changed, 140 insertions(+), 28 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go index 2f71991ed15..ee13148f977 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule_test.go @@ -243,13 +243,31 @@ func testAccCheckAwsEc2ClientVpnAuthorizationRuleExists(name string, assoc *ec2. } func testAccEc2ClientVpnAuthorizationRuleConfigBasic(rName string) string { - return testAccEc2ClientVpnVpcComposeConfig(rName, 1, ` + return composeConfig( + testAccEc2ClientVpnAuthorizationRuleVpcBase(rName, 1), + testAccEc2ClientVpnAuthorizationRuleAcmCertificateBase(), + fmt.Sprintf(` resource "aws_ec2_client_vpn_authorization_rule" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id target_network_cidr = aws_subnet.test[0].cidr_block authorize_all_groups = true } -`) + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.test.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" + } + + connection_log_options { + enabled = false + } +} +`, rName)) } func testAccEc2ClientVpnAuthorizationRuleConfigGroups(rName string, groupNames map[string]string) string { @@ -264,7 +282,25 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnVpcComposeConfig(rName, 1, b.String()) + return composeConfig( + testAccEc2ClientVpnAuthorizationRuleVpcBase(rName, 1), + testAccEc2ClientVpnAuthorizationRuleAcmCertificateBase(), + b.String(), + fmt.Sprintf(` +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.test.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" + } + + connection_log_options { + enabled = false + } +}`, rName)) } func testAccEc2ClientVpnAuthorizationRuleConfigSubnets(rName string, subnetCount int, groupNames map[string]int) string { @@ -279,10 +315,28 @@ resource "aws_ec2_client_vpn_authorization_rule" %[1]q { `, k, v) } - return testAccEc2ClientVpnVpcComposeConfig(rName, subnetCount, b.String()) + return composeConfig( + testAccEc2ClientVpnAuthorizationRuleVpcBase(rName, subnetCount), + testAccEc2ClientVpnAuthorizationRuleAcmCertificateBase(), + b.String(), + fmt.Sprintf(` +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = "${aws_acm_certificate.test.arn}" + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = "${aws_acm_certificate.test.arn}" + } + + connection_log_options { + enabled = false + } +}`, rName)) } -func testAccEc2ClientVpnVpcBase(rName string, subnetCount int) string { +func testAccEc2ClientVpnAuthorizationRuleVpcBase(rName string, subnetCount int) string { return fmt.Sprintf(` data "aws_availability_zones" "available" { # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region @@ -317,11 +371,14 @@ resource "aws_subnet" "test" { `, rName, subnetCount) } -func testAccEc2ClientVpnVpcComposeConfig(rName string, subnetCount int, config ...string) string { - return testAccEc2ClientVpnComposeConfig(rName, - append( - config, - testAccEc2ClientVpnVpcBase(rName, subnetCount), - )..., - ) +func testAccEc2ClientVpnAuthorizationRuleAcmCertificateBase() string { + key := tlsRsaPrivateKeyPem(2048) + certificate := tlsRsaX509SelfSignedCertificatePem(key, "example.com") + + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + certificate_body = "%[1]s" + private_key = "%[2]s" +} +`, tlsPemEscapeNewlines(certificate), tlsPemEscapeNewlines(key)) } diff --git a/aws/resource_aws_ec2_client_vpn_endpoint_test.go b/aws/resource_aws_ec2_client_vpn_endpoint_test.go index 35dd682da9d..4d5b657e8d3 100644 --- a/aws/resource_aws_ec2_client_vpn_endpoint_test.go +++ b/aws/resource_aws_ec2_client_vpn_endpoint_test.go @@ -538,12 +538,12 @@ resource "aws_ec2_client_vpn_endpoint" "test" { description = "terraform-testacc-clientvpn-%s" server_certificate_arn = "${aws_acm_certificate.test.arn}" client_cidr_block = "10.0.0.0/16" - + authentication_options { type = "directory-service-authentication" active_directory_id = "${aws_directory_service_directory.test.id}" } - + connection_log_options { enabled = false } @@ -641,12 +641,3 @@ resource "aws_ec2_client_vpn_endpoint" "test" { } `, rName, splitTunnel) } - -func testAccEc2ClientVpnComposeConfig(rName string, config ...string) string { - return composeConfig( - append( - config, - testAccEc2ClientVpnEndpointConfig(rName), - )..., - ) -} diff --git a/aws/resource_aws_ec2_client_vpn_network_association_test.go b/aws/resource_aws_ec2_client_vpn_network_association_test.go index 6136ba539a5..a27c35eae83 100644 --- a/aws/resource_aws_ec2_client_vpn_network_association_test.go +++ b/aws/resource_aws_ec2_client_vpn_network_association_test.go @@ -22,7 +22,7 @@ func testAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), + Config: testAccEc2ClientVpnNetworkAssociationConfigBasic(rStr), Check: resource.ComposeTestCheckFunc( testAccCheckAwsEc2ClientVpnNetworkAssociationExists(resourceName, &assoc1), ), @@ -42,7 +42,7 @@ func testAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr), + Config: testAccEc2ClientVpnNetworkAssociationConfigBasic(rStr), Check: resource.ComposeTestCheckFunc( testAccCheckAwsEc2ClientVpnNetworkAssociationExists(resourceName, &assoc1), testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ClientVpnNetworkAssociation(), resourceName), @@ -111,11 +111,75 @@ func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2 } } -func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string { - return testAccEc2ClientVpnVpcComposeConfig(rName, 1, ` +func testAccEc2ClientVpnNetworkAssociationConfigBasic(rName string) string { + return composeConfig( + testAccEc2ClientVpnNetworkAssociationVpcBase(rName, 1), + testAccEc2ClientVpnNetworkAssociationAcmCertificateBase(), + fmt.Sprintf(` resource "aws_ec2_client_vpn_network_association" "test" { client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.test.id subnet_id = aws_subnet.test[0].id } -`) + +resource "aws_ec2_client_vpn_endpoint" "test" { + description = "terraform-testacc-clientvpn-%s" + server_certificate_arn = aws_acm_certificate.test.arn + client_cidr_block = "10.0.0.0/16" + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = aws_acm_certificate.test.arn + } + + connection_log_options { + enabled = false + } +}`, rName)) +} + +func testAccEc2ClientVpnNetworkAssociationVpcBase(rName string, subnetCount int) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + # InvalidParameterValue: AZ us-west-2d is not currently supported. Please choose another az in this region + exclude_zone_ids = ["usw2-az4"] + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = "terraform-testacc-subnet-%[1]s" + } +} + +resource "aws_subnet" "test" { + count = %[2]d + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + vpc_id = aws_vpc.test.id + map_public_ip_on_launch = true + + tags = { + Name = "tf-acc-subnet-%[1]s" + } +} +`, rName, subnetCount) +} + +func testAccEc2ClientVpnNetworkAssociationAcmCertificateBase() string { + key := tlsRsaPrivateKeyPem(2048) + certificate := tlsRsaX509SelfSignedCertificatePem(key, "example.com") + + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + certificate_body = "%[1]s" + private_key = "%[2]s" +} +`, tlsPemEscapeNewlines(certificate), tlsPemEscapeNewlines(key)) } From 0a56fc8cde02cdf091503d5c0fceba749c35a9d5 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 8 Jul 2020 09:29:54 -0700 Subject: [PATCH 43/44] Update aws/internal/service/ec2/waiter/status.go Removes outdated comment Co-authored-by: Brian Flad --- aws/internal/service/ec2/waiter/status.go | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 8df3050a1e8..6a238c9370e 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -83,7 +83,6 @@ const ( ) // ClientVpnAuthorizationRuleStatus fetches the Client VPN authorization rule and its Status -// TODO: This should be in the waiters package, but has a dependency on isAWSErr() func ClientVpnAuthorizationRuleStatus(conn *ec2.EC2, authorizationRuleID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { endpointID, targetNetworkCidr, accessGroupID, err := tfec2.ClientVpnAuthorizationRuleParseID(authorizationRuleID) From 4403fca9bd516a645d89c5efe0aa9e99d6daf052 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 8 Jul 2020 09:33:42 -0700 Subject: [PATCH 44/44] Update aws/resource_aws_ec2_client_vpn_authorization_rule.go Removes unneeded checks Co-authored-by: Brian Flad --- aws/resource_aws_ec2_client_vpn_authorization_rule.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aws/resource_aws_ec2_client_vpn_authorization_rule.go b/aws/resource_aws_ec2_client_vpn_authorization_rule.go index 51fc23802dc..caafa974b7f 100644 --- a/aws/resource_aws_ec2_client_vpn_authorization_rule.go +++ b/aws/resource_aws_ec2_client_vpn_authorization_rule.go @@ -140,9 +140,7 @@ func resourceAwsEc2ClientVpnAuthorizationRuleDelete(d *schema.ResourceData, meta RevokeAllGroups: aws.Bool(d.Get("authorize_all_groups").(bool)), } if v, ok := d.GetOk("access_group_id"); ok { - if s, ok := v.(string); ok && s != "" { - input.AccessGroupId = aws.String(s) - } + input.AccessGroupId = aws.String(v.(string)) } log.Printf("[DEBUG] Revoking Client VPN authorization rule %q", d.Id())