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_client_vpn_endpoint: Adding new resource to manage AWS Client VPN endpoints #7009

Merged
merged 8 commits into from
Feb 4, 2019
Prev Previous commit
Next Next commit
changes based on review feedback
  • Loading branch information
slapula committed Jan 16, 2019
commit 928d2a7b7e9f12395052fd8863cfd7193976437f
160 changes: 62 additions & 98 deletions aws/resource_aws_ec2_client_vpn_endpoint.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package aws

import (
"bytes"
"fmt"
"log"

"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/schema"
"github.com/hashicorp/terraform/helper/validation"
)
@@ -42,23 +40,29 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource {
Required: true,
},
"transport_protocol": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "udp",
ValidateFunc: validation.StringInSlice([]string{"udp", "tcp"}, false),
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: ec2.TransportProtocolUdp,
ValidateFunc: validation.StringInSlice([]string{
ec2.TransportProtocolTcp,
ec2.TransportProtocolUdp,
}, false),
},
"authentication_options": {
Type: schema.TypeSet,
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"certificate-authentication", "directory-service-authentication"}, false),
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
ec2.ClientVpnAuthenticationTypeCertificateAuthentication,
ec2.ClientVpnAuthenticationTypeDirectoryServiceAuthentication,
}, false),
},
"active_directory_id": {
slapula marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeString,
@@ -74,7 +78,7 @@ func resourceAwsEc2ClientVpnEndpoint() *schema.Resource {
},
},
"connection_log_options": {
Type: schema.TypeSet,
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
@@ -124,29 +128,45 @@ func resourceAwsEc2ClientVpnEndpointCreate(d *schema.ResourceData, meta interfac
}

if v, ok := d.GetOk("authentication_options"); ok {
vpnAuthRequest := []*ec2.ClientVpnAuthenticationRequest{}
authSet := v.(*schema.Set)
authOptsSet := v.([]interface{})
attrs := authOptsSet[0].(map[string]interface{})

for _, authObject := range authSet.List() {
auth := authObject.(map[string]interface{})
authOptsReq := &ec2.ClientVpnAuthenticationRequest{
Type: aws.String(attrs["type"].(string)),
}

authObject := expandEc2ClientVpnAuthenticationRequest(auth)
vpnAuthRequest = append(vpnAuthRequest, authObject)
if attrs["type"].(string) == "certificate-authentication" {
authOptsReq.MutualAuthentication = &ec2.CertificateAuthenticationRequest{
ClientRootCertificateChainArn: aws.String(attrs["root_certificate_chain_arn"].(string)),
}
}
req.AuthenticationOptions = vpnAuthRequest

if attrs["type"].(string) == "directory-service-authentication" {
authOptsReq.ActiveDirectory = &ec2.DirectoryServiceAuthenticationRequest{
DirectoryId: aws.String(attrs["active_directory_id"].(string)),
}
}

req.AuthenticationOptions = []*ec2.ClientVpnAuthenticationRequest{authOptsReq}
}

if v, ok := d.GetOk("connection_log_options"); ok {
var connLogRequest *ec2.ConnectionLogOptions
connSet := v.(*schema.Set)
connLogSet := v.([]interface{})
attrs := connLogSet[0].(map[string]interface{})

connLogReq := &ec2.ConnectionLogOptions{
Enabled: aws.Bool(attrs["enabled"].(bool)),
}

for _, connObject := range connSet.List() {
connData := connObject.(map[string]interface{})
if attrs["enabled"].(bool) && attrs["cloudwatch_log_group"].(string) != "" {
connLogReq.CloudwatchLogGroup = aws.String(attrs["cloudwatch_log_group"].(string))
}

connLogRequest = expandEc2ConnectionLogOptionsRequest(connData)
if attrs["enabled"].(bool) && attrs["cloudwatch_log_stream"].(string) != "" {
connLogReq.CloudwatchLogStream = aws.String(attrs["cloudwatch_log_stream"].(string))
}

req.ConnectionLogOptions = connLogRequest
req.ConnectionLogOptions = connLogReq
}

log.Printf("[DEBUG] Creating Client VPN endpoint: %#v", req)
@@ -239,16 +259,22 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac

if d.HasChange("connection_log_options") {
if v, ok := d.GetOk("connection_log_options"); ok {
var connLogRequest *ec2.ConnectionLogOptions
connSet := v.(*schema.Set)
connSet := v.([]interface{})
attrs := connSet[0].(map[string]interface{})

for _, connObject := range connSet.List() {
connData := connObject.(map[string]interface{})
connReq := &ec2.ConnectionLogOptions{
Enabled: aws.Bool(attrs["enabled"].(bool)),
}

connLogRequest = expandEc2ConnectionLogOptionsRequest(connData)
if attrs["enabled"].(bool) && attrs["cloudwatch_log_group"].(string) != "" {
connReq.CloudwatchLogGroup = aws.String(attrs["cloudwatch_log_group"].(string))
}

req.ConnectionLogOptions = connLogRequest
if attrs["enabled"].(bool) && attrs["cloudwatch_log_stream"].(string) != "" {
connReq.CloudwatchLogStream = aws.String(attrs["cloudwatch_log_stream"].(string))
}

req.ConnectionLogOptions = connReq
}
}

@@ -260,43 +286,7 @@ func resourceAwsEc2ClientVpnEndpointUpdate(d *schema.ResourceData, meta interfac
return resourceAwsEc2ClientVpnEndpointRead(d, meta)
}

func expandEc2ConnectionLogOptionsRequest(data map[string]interface{}) *ec2.ConnectionLogOptions {
req := &ec2.ConnectionLogOptions{
Enabled: aws.Bool(data["enabled"].(bool)),
}

if data["enabled"].(bool) == true && data["cloudwatch_log_group"].(string) != "" {
req.CloudwatchLogGroup = aws.String(data["cloudwatch_log_group"].(string))
}

if data["enabled"].(bool) == true && data["cloudwatch_log_stream"].(string) != "" {
req.CloudwatchLogStream = aws.String(data["cloudwatch_log_stream"].(string))
}

return req
}

func expandEc2ClientVpnAuthenticationRequest(data map[string]interface{}) *ec2.ClientVpnAuthenticationRequest {
req := &ec2.ClientVpnAuthenticationRequest{
Type: aws.String(data["type"].(string)),
}

if data["type"].(string) == "certificate-authentication" {
req.MutualAuthentication = &ec2.CertificateAuthenticationRequest{
ClientRootCertificateChainArn: aws.String(data["root_certificate_chain_arn"].(string)),
}
}

if data["type"].(string) == "directory-service-authentication" {
req.ActiveDirectory = &ec2.DirectoryServiceAuthenticationRequest{
DirectoryId: aws.String(data["active_directory_id"].(string)),
}
}

return req
}

func flattenConnLoggingConfig(lopts *ec2.ConnectionLogResponseOptions) *schema.Set {
func flattenConnLoggingConfig(lopts *ec2.ConnectionLogResponseOptions) []map[string]interface{} {
m := make(map[string]interface{})
if lopts.CloudwatchLogGroup != nil {
m["cloudwatch_log_group"] = *lopts.CloudwatchLogGroup
@@ -305,23 +295,10 @@ func flattenConnLoggingConfig(lopts *ec2.ConnectionLogResponseOptions) *schema.S
m["cloudwatch_log_stream"] = *lopts.CloudwatchLogStream
}
m["enabled"] = *lopts.Enabled
return schema.NewSet(connLoggingConfigHash, []interface{}{m})
return []map[string]interface{}{m}
}

func connLoggingConfigHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
if m["cloudwatch_log_group"] != nil {
buf.WriteString(fmt.Sprintf("%s-", m["cloudwatch_log_group"].(string)))
}
if m["cloudwatch_log_stream"] != nil {
buf.WriteString(fmt.Sprintf("%s-", m["cloudwatch_log_stream"].(string)))
}
buf.WriteString(fmt.Sprintf("%t-", m["enabled"].(bool)))
return hashcode.String(buf.String())
}

func flattenAuthOptsConfig(aopts []*ec2.ClientVpnAuthentication) *schema.Set {
func flattenAuthOptsConfig(aopts []*ec2.ClientVpnAuthentication) []map[string]interface{} {
m := make(map[string]interface{})
if aopts[0].MutualAuthentication != nil {
m["root_certificate_chain_arn"] = *aopts[0].MutualAuthentication.ClientRootCertificateChain
@@ -330,18 +307,5 @@ func flattenAuthOptsConfig(aopts []*ec2.ClientVpnAuthentication) *schema.Set {
m["active_directory_id"] = *aopts[0].ActiveDirectory.DirectoryId
}
m["type"] = *aopts[0].Type
return schema.NewSet(authOptsConfigHash, []interface{}{m})
}

func authOptsConfigHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
if m["root_certificate_chain_arn"] != nil {
buf.WriteString(fmt.Sprintf("%s-", m["root_certificate_chain_arn"].(string)))
}
if m["active_directory_id"] != nil {
buf.WriteString(fmt.Sprintf("%s-", m["active_directory_id"].(string)))
}
buf.WriteString(fmt.Sprintf("%s-", m["type"].(string)))
return hashcode.String(buf.String())
return []map[string]interface{}{m}
}
100 changes: 100 additions & 0 deletions aws/resource_aws_ec2_client_vpn_endpoint_test.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,34 @@ 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"),
),
},

{
ResourceName: "aws_ec2_client_vpn_endpoint.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAwsEc2ClientVpnEndpoint_msAD(t *testing.T) {
rStr := acctest.RandString(5)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProvidersWithTLS,
CheckDestroy: testAccCheckAwsEc2ClientVpnEndpointDestroy,
Steps: []resource.TestStep{
{
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"),
),
},

@@ -280,3 +308,75 @@ resource "aws_ec2_client_vpn_endpoint" "test" {
}
`, rName)
}

func testAccEc2ClientVpnEndpointConfigWithMicrosoftAD(rName string) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "test1" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
slapula marked this conversation as resolved.
Show resolved Hide resolved
}

resource "aws_subnet" "test2" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1d"
}

resource "aws_directory_service_directory" "test" {
name = "corp.notexample.com"
password = "SuperSecretPassw0rd"
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"
server_certificate_arn = "${aws_acm_certificate.cert.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)
}