Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_ec2_client_vpn_endpoint_network_association: Adding option to apply additional security groups to target network #7500

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions aws/resource_aws_ec2_client_vpn_network_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEc2ClientVpnNetworkAssociationCreate,
Read: resourceAwsEc2ClientVpnNetworkAssociationRead,
Update: resourceAwsEc2ClientVpnNetworkAssociationUpdate,
Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete,

Schema: map[string]*schema.Schema{
Expand All @@ -29,8 +30,11 @@ func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource {
},
"security_groups": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
MaxItems: 5,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"status": {
Type: schema.TypeString,
Expand Down Expand Up @@ -68,11 +72,44 @@ 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()
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 v, ok := d.GetOk("security_groups"); ok {
sgReq := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{
ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)),
VpcId: targetNetwork.VpcId,
SecurityGroupIds: expandStringSet(v.(*schema.Set)),
}

_, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(sgReq)
if err != nil {
return fmt.Errorf("Error applying security groups to Client VPN network association: %s", err)
}
}

return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta)
}

func resourceAwsEc2ClientVpnNetworkAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

if d.HasChange("security_groups") {
input := &ec2.ApplySecurityGroupsToClientVpnTargetNetworkInput{
ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)),
SecurityGroupIds: expandStringSet(d.Get("security_groups").(*schema.Set)),
VpcId: aws.String(d.Get("vpc_id").(string)),
}

if _, err := conn.ApplySecurityGroupsToClientVpnTargetNetwork(input); err != nil {
return fmt.Errorf("error applying security groups to Client VPN Target Network: %s", err)
}
}

return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta)
}

Expand Down
274 changes: 273 additions & 1 deletion aws/resource_aws_ec2_client_vpn_network_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) {
})
}

func TestAccAwsEc2ClientVpnNetworkAssociation_securityGroups(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: testAccEc2ClientVpnNetworkAssociationTwoSecurityGroups(rStr),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1),
resource.TestCheckResourceAttr("aws_ec2_client_vpn_network_association.test", "security_groups.#", "2"),
),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include another TestStep here that performs some form of update 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm noticing with the Update function is that ApplySecurityGroupsToClientVpnTargetNetwork call doesn't zero out security_groups it just replaces ones that are already there. Going from 2 security groups to zero means that the call ignores the empty request and leaves the security groups untouched. Weird, but not unmanageable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that was what I was trying to say in #7500 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I see what you are saying. See my latest commit. I was able to get the behavior I'm looking for by making security_groups a required parameter (to account for the API adding the default VPC security group in the absence of custom groups). Not ideal but it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch that, Travis doesn't like the Required + Computed part.

{
Config: testAccEc2ClientVpnNetworkAssociationOneSecurityGroup(rStr),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1),
resource.TestCheckResourceAttr("aws_ec2_client_vpn_network_association.test", "security_groups.#", "1"),
),
},
},
})
}

func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

Expand Down Expand Up @@ -196,7 +223,252 @@ resource "aws_ec2_client_vpn_endpoint" "test" {

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}"
subnet_id = "${aws_subnet.test.id}"
security_groups = [""]
}
`, rName, rName, rName)
}

func testAccEc2ClientVpnNetworkAssociationTwoSecurityGroups(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 "aws_security_group" "test1" {
name = "terraform_acceptance_test_example_1"
description = "Used in the terraform acceptance tests"
vpc_id = "${aws_vpc.test.id}"

ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["10.1.1.0/24"]
}

ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["10.1.1.0/24"]
}

egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["10.1.0.0/16"]
}
}

resource "aws_security_group" "test2" {
name = "terraform_acceptance_test_example_2"
description = "Used in the terraform acceptance tests"
vpc_id = "${aws_vpc.test.id}"

ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["10.1.2.0/24"]
}

ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["10.1.2.0/24"]
}

egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["10.1.0.0/16"]
}
}

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}"
security_groups = ["${aws_security_group.test1.id}", "${aws_security_group.test2.id}"]
}
`, rName, rName, rName)
}

func testAccEc2ClientVpnNetworkAssociationOneSecurityGroup(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 "aws_security_group" "test1" {
name = "terraform_acceptance_test_example_1"
description = "Used in the terraform acceptance tests"
vpc_id = "${aws_vpc.test.id}"

ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["10.1.1.0/24"]
}

ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["10.1.1.0/24"]
}

egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["10.1.0.0/16"]
}
}

resource "aws_security_group" "test2" {
name = "terraform_acceptance_test_example_2"
description = "Used in the terraform acceptance tests"
vpc_id = "${aws_vpc.test.id}"

ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["10.1.2.0/24"]
}

ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["10.1.2.0/24"]
}

egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["10.1.0.0/16"]
}
}

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}"
security_groups = ["${aws_security_group.test1.id}"]
}
`, rName, rName, rName)
}
20 changes: 16 additions & 4 deletions website/docs/r/ec2_client_vpn_network_association.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,23 @@ 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).

## Example Usage
## Example Usage (w/ default VPC security group)

```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}"
client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}"
subnet_id = "${aws_subnet.example.id}"
security_groups = [""]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be omitted in this case.

}
```

## Example Usage (w/ custom security groups)

```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}"
security_groups = ["${aws_security_group.example1.id}", "${aws_security_group.example2.id}"]
}
```

Expand All @@ -26,12 +37,13 @@ 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.
* `security_groups` - (Required) A list of up to five custom security groups to apply to the target network. Not populating this parameter will result in the subnet inheriting the default VPC security group. Regardless of what you choose, you must define this parameter due to the AWS API automatically assigning the default VPC security group if no other groups are included in the call.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be updated back to Optional.

Suggested change
* `security_groups` - (Required) A list of up to five custom security groups to apply to the target network. Not populating this parameter will result in the subnet inheriting the default VPC security group. Regardless of what you choose, you must define this parameter due to the AWS API automatically assigning the default VPC security group if no other groups are included in the call.
* `security_groups` - (Optional) A list of up to five custom VPC security groups to apply to the target network. Not populating this parameter will result in the subnet inheriting the default VPC security group. To remove all custom VPC security groups, this list must be configured to contain only the default VPC security group.


## 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.
* `vpc_id` - The ID of the VPC in which the target network (subnet) is located.
* `status` - The current state of the target network association.
* `vpc_id` - The ID of the VPC in which the target network (subnet) is located.