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

Add vpn client protocol support for virtual network gateway #946

Merged
merged 12 commits into from Jun 15, 2018
91 changes: 90 additions & 1 deletion azurerm/resource_arm_virtual_network_gateway.go
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"log"
"regexp"
"strings"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2017-09-01/network"
Expand All @@ -23,6 +24,8 @@ func resourceArmVirtualNetworkGateway() *schema.Resource {
State: schema.ImportStatePassthrough,
},

CustomizeDiff: resourceArmVirtualNetworkGatewayCustomizeDiff,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Expand Down Expand Up @@ -138,9 +141,25 @@ func resourceArmVirtualNetworkGateway() *schema.Resource {
Type: schema.TypeString,
},
},
"vpn_client_protocols": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
string(network.IkeV2),
string(network.SSTP),
}, true),
},
},
"root_certificate": {
Type: schema.TypeSet,
Required: true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

What happens if neither root_certificate or radius_server_address are set? if that is an error condition we should use a CustomizeDiff to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the root_certificate and radius_server_address are set, this is accepted by azure with no root cert and the vpnclient cannot be downloaded. I've added a CustomizeDiff so that if a radius_server_address is set is had to be a valid IPv4 address and the secret shall not be empty.

Optional: true,

ConflictsWith: []string{
"vpn_client_configuration.0.radius_server_address",
"vpn_client_configuration.0.radius_server_secret",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Expand All @@ -158,6 +177,10 @@ func resourceArmVirtualNetworkGateway() *schema.Resource {
"revoked_certificate": {
Type: schema.TypeSet,
Optional: true,
ConflictsWith: []string{
"vpn_client_configuration.0.radius_server_address",
"vpn_client_configuration.0.radius_server_secret",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Expand All @@ -172,6 +195,26 @@ func resourceArmVirtualNetworkGateway() *schema.Resource {
},
Set: hashVirtualNetworkGatewayRevokedCert,
},
"radius_server_address": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{
"vpn_client_configuration.0.root_certificate",
"vpn_client_configuration.0.revoked_certificate",
},
ValidateFunc: validation.StringMatch(
regexp.MustCompile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"),
"The radius server address must be a valid IPv4 address",
),
},
"radius_server_secret": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{
"vpn_client_configuration.0.root_certificate",
"vpn_client_configuration.0.revoked_certificate",
},
},
},
},
},
Expand Down Expand Up @@ -493,12 +536,24 @@ func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *netw
revokedCerts = append(revokedCerts, r)
}

var vpnClientProtocols []network.VpnClientProtocol
for _, vpnClientProtocol := range conf["vpn_client_protocols"].(*schema.Set).List() {
p := network.VpnClientProtocol(vpnClientProtocol.(string))
vpnClientProtocols = append(vpnClientProtocols, p)
}

confRadiusServerAddress := conf["radius_server_address"].(string)
confRadiusServerSecret := conf["radius_server_secret"].(string)

return &network.VpnClientConfiguration{
VpnClientAddressPool: &network.AddressSpace{
AddressPrefixes: &addresses,
},
VpnClientRootCertificates: &rootCerts,
VpnClientRevokedCertificates: &revokedCerts,
VpnClientProtocols: &vpnClientProtocols,
RadiusServerAddress: &confRadiusServerAddress,
RadiusServerSecret: &confRadiusServerSecret,
}
}

Expand Down Expand Up @@ -576,6 +631,22 @@ func flattenArmVirtualNetworkGatewayVpnClientConfig(cfg *network.VpnClientConfig
}
flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRevokedCert, revokedCerts)

vpnClientProtocols := &schema.Set{F: schema.HashString}
if vpnProtocols := cfg.VpnClientProtocols; vpnProtocols != nil {
for _, protocol := range *vpnProtocols {
vpnClientProtocols.Add(string(protocol))
}
}
flat["vpn_client_protocols"] = vpnClientProtocols

if v := cfg.RadiusServerAddress; v != nil {
flat["radius_server_address"] = *v
}

if v := cfg.RadiusServerSecret; v != nil {
flat["radius_server_secret"] = *v
}

return []interface{}{flat}
}

Expand Down Expand Up @@ -660,3 +731,21 @@ func validateArmVirtualNetworkGatewayExpressRouteSku() schema.SchemaValidateFunc
string(network.VirtualNetworkGatewaySkuTierUltraPerformance),
}, true)
}

func resourceArmVirtualNetworkGatewayCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error {

if vpnClient, ok := diff.GetOk("vpn_client_configuration"); ok {
if vpnClientConfig, ok := vpnClient.([]interface{})[0].(map[string]interface{}); ok {
hasRadiusAddress := vpnClientConfig["radius_server_address"] != ""
hasRadiusSecret := vpnClientConfig["radius_server_secret"] != ""

if hasRadiusAddress && !hasRadiusSecret {
return fmt.Errorf("if radius_server_address is set radius_server_secret must also be set")
}
if !hasRadiusAddress && hasRadiusSecret {
return fmt.Errorf("if radius_server_secret is set radius_server_address must also be set")
}
}
}
return nil
}
93 changes: 91 additions & 2 deletions azurerm/resource_arm_virtual_network_gateway_test.go
Expand Up @@ -12,6 +12,7 @@ import (

func TestAccAzureRMVirtualNetworkGateway_basic(t *testing.T) {
ri := acctest.RandInt()
resourceName := "azurerm_virtual_network_gateway.test"
config := testAccAzureRMVirtualNetworkGateway_basic(ri, testLocation())

resource.Test(t, resource.TestCase{
Expand All @@ -22,7 +23,8 @@ func TestAccAzureRMVirtualNetworkGateway_basic(t *testing.T) {
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMVirtualNetworkGatewayExists("azurerm_virtual_network_gateway.test"),
testCheckAzureRMVirtualNetworkGatewayExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "sku", "Basic"),
),
},
},
Expand All @@ -31,6 +33,7 @@ func TestAccAzureRMVirtualNetworkGateway_basic(t *testing.T) {

func TestAccAzureRMVirtualNetworkGateway_lowerCaseSubnetName(t *testing.T) {
ri := acctest.RandInt()
resourceName := "azurerm_virtual_network_gateway.test"
config := testAccAzureRMVirtualNetworkGateway_lowerCaseSubnetName(ri, testLocation())

resource.Test(t, resource.TestCase{
Expand All @@ -41,7 +44,8 @@ func TestAccAzureRMVirtualNetworkGateway_lowerCaseSubnetName(t *testing.T) {
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMVirtualNetworkGatewayExists("azurerm_virtual_network_gateway.test"),
testCheckAzureRMVirtualNetworkGatewayExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "sku", "Basic"),
),
},
},
Expand Down Expand Up @@ -86,6 +90,28 @@ func TestAccAzureRMVirtualNetworkGateway_activeActive(t *testing.T) {
})
}

func TestAccAzureRMVirtualNetworkGateway_vpnClientConfig(t *testing.T) {
ri := acctest.RandInt()
resourceName := "azurerm_virtual_network_gateway.test"
config := testAccAzureRMVirtualNetworkGateway_vpnClientConfig(ri, testLocation())

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMVirtualNetworkGatewayDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMVirtualNetworkGatewayExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.radius_server_address", "1.2.3.4"),
resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.vpn_client_protocols.#", "2"),
),
},
},
})
}

func testCheckAzureRMVirtualNetworkGatewayExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
name, resourceGroup, err := getArmResourceNameAndGroup(s, name)
Expand Down Expand Up @@ -274,6 +300,7 @@ resource "azurerm_virtual_network_gateway" "test" {
}

func testAccAzureRMVirtualNetworkGateway_activeActive(rInt int, location string) string {

return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
Expand All @@ -294,6 +321,7 @@ resource "azurerm_subnet" "test" {
address_prefix = "10.0.1.0/24"
}


resource "azurerm_public_ip" "first" {
name = "acctestpip1-%d"
location = "${azurerm_resource_group.test.location}"
Expand All @@ -303,29 +331,34 @@ resource "azurerm_public_ip" "first" {

resource "azurerm_public_ip" "second" {
name = "acctestpip2-%d"

location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
public_ip_address_allocation = "Dynamic"
}

resource "azurerm_virtual_network_gateway" "test" {
depends_on = ["azurerm_public_ip.first", "azurerm_public_ip.second"]
name = "acctestvng-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"

type = "Vpn"
vpn_type = "RouteBased"
sku = "VpnGw1"

active_active = true
enable_bgp = true

ip_configuration {
name = "gw-ip1"
public_ip_address_id = "${azurerm_public_ip.first.id}"

private_ip_address_allocation = "Dynamic"
subnet_id = "${azurerm_subnet.test.id}"
}


ip_configuration {
name = "gw-ip2"
public_ip_address_id = "${azurerm_public_ip.second.id}"
Expand All @@ -338,4 +371,60 @@ resource "azurerm_virtual_network_gateway" "test" {
}
}
`, rInt, location, rInt, rInt, rInt, rInt)

}

func testAccAzureRMVirtualNetworkGateway_vpnClientConfig(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctestvn-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
address_space = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "test" {
name = "GatewaySubnet"
resource_group_name = "${azurerm_resource_group.test.name}"
virtual_network_name = "${azurerm_virtual_network.test.name}"
address_prefix = "10.0.1.0/24"
}

resource "azurerm_public_ip" "test" {
name = "acctestpip-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
public_ip_address_allocation = "Dynamic"
}

resource "azurerm_virtual_network_gateway" "test" {
depends_on = ["azurerm_public_ip.test"]
name = "acctestvng-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"

type = "Vpn"
vpn_type = "RouteBased"
sku = "VpnGw1"

ip_configuration {
public_ip_address_id = "${azurerm_public_ip.test.id}"
private_ip_address_allocation = "Dynamic"
subnet_id = "${azurerm_subnet.test.id}"
}

vpn_client_configuration {
address_space = ["10.2.0.0/24"]
vpn_client_protocols = ["SSTP", "IkeV2"]

radius_server_address = "1.2.3.4"
radius_server_secret = "1234"
}
}
`, rInt, location, rInt, rInt, rInt)
}
13 changes: 12 additions & 1 deletion website/docs/r/virtual_network_gateway.html.markdown
Expand Up @@ -170,12 +170,23 @@ The `vpn_client_configuration` block supports:
vpn clients will be taken. You can provide more than one address space, e.g.
in CIDR notation.

* `root_certificate` - (Required) One or more `root_certificate` blocks which are
* `vpn_client_protocol` - (Optional) List of the protocols supported by the vpn client.
The supported values are "SSTP" and "IkeV2".

* `root_certificate` - (Optional) One or more `root_certificate` blocks which are
defined below. These root certificates are used to sign the client certificate
used by the VPN clients to connect to the gateway.
This setting is incompatible with the use of `radius_server_address` and `radius_server_secret`.

* `revoked_certificate` - (Optional) One or more `revoked_certificate` blocks which
are defined below.
This setting is incompatible with the use of `radius_server_address` and `radius_server_secret`.

* `radius_server_address` - (Optional) The address of the Radius server.
This setting is incompatible with the use of `root_certificate` and `revoked_certificate`.

* `radius_server_secret` - (Optional) The secret used by the Radius server.
This setting is incompatible with the use of `root_certificate` and `revoked_certificate`.

The `bgp_settings` block supports:

Expand Down