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

Update Magic Transit IPsec provider #1685

Merged
merged 7 commits into from
Jun 8, 2022
Merged
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
3 changes: 3 additions & 0 deletions .changelog/1685.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/cloudflare_ipsec_tunnel: add support for `healthcheck_enabled`, `health_check_target`, `healthcheck_type`, `psk`
```
64 changes: 43 additions & 21 deletions docs/resources/cloudflare_ipsec_tunnel.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
---
layout: "cloudflare"
page_title: "Cloudflare: cloudflare_ipsec_tunnel"
description: Provides a resource which manages IPsec tunnels for Magic Transit.
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "cloudflare_ipsec_tunnel Resource - Cloudflare"
subcategory: ""
description: |-
Provides a resource, that manages IPsec tunnels for Magic Transit.
---

# cloudflare_ipsec_tunnel
# cloudflare_ipsec_tunnel (Resource)

Provides a resource, that manages IPsec tunnels for Magic Transit.

## Example Usage

```hcl
```terraform
resource "cloudflare_ipsec_tunnel" "example" {
account_id = "c4a7362d577a6c3019a474fd6f485821"
name = "IPsec_1"
customer_endpoint = "203.0.113.1"
cloudflare_endpoint = "203.0.113.1"
interface_address = "192.0.2.0/31"
description = "Tunnel for ISP X"
account_id = "c4a7362d577a6c3019a474fd6f485821"
name = "IPsec_1"
customer_endpoint = "203.0.113.1"
cloudflare_endpoint = "203.0.113.1"
interface_address = "192.0.2.0/31"
description = "Tunnel for ISP X"
health_check_enabled = true
health_check_target = "203.0.113.1"
health_check_type = "reply"
psk = "asdf12341234"
}
```

## Argument Reference
<!-- schema generated by tfplugindocs -->
## Schema

The following arguments are supported:
### Required

- `account_id` - (Required) The ID of the account where the tunnel is being created.
- `name` - (Required) Name of the IPsec tunnel.
- `customer_endpoint` - (Required) IP address assigned to the customer side of the IPsec tunnel.
- `cloudflare_endpoint` - (Required) IP address assigned to the Cloudflare side of the IPsec tunnel.
- `interface_address` - (Required) 31-bit prefix (/31 in CIDR notation) supporting 2 hosts, one for each side of the tunnel.
- `description` - (Optional) An optional description of the IPsec tunnel.
- `cloudflare_endpoint` (String) IP address assigned to the Cloudflare side of the IPsec tunnel.
- `customer_endpoint` (String) IP address assigned to the customer side of the IPsec tunnel.
- `interface_address` (String) 31-bit prefix (/31 in CIDR notation) supporting 2 hosts, one for each side of the tunnel.
- `name` (String) Name of the IPsec tunnel.

### Optional

- `account_id` (String) The account identifier to target for the resource.
- `description` (String) An optional description of the IPsec tunnel.
- `fqdn_id` (String) `remote_id` in the form of a fqdn. This value is generated by cloudflare.
- `health_check_enabled` (Boolean) Specifies if ICMP tunnel health checks are enabled. Default: `true`.
- `health_check_target` (String) The IP address of the customer endpoint that will receive tunnel health checks. Default: `<customer_gre_endpoint>`.
- `health_check_type` (String) Specifies the ICMP echo type for the health check (`request` or `reply`). Available values: `"request"`, `"reply"` Default: `reply`.
- `hex_id` (String) `remote_id` as a hex string. This value is generated by cloudflare.
- `psk` (String, Sensitive) Pre shared key to be used with the IPsec tunnel. If left unset, it will be autogenerated.
- `remote_id` (String) ID to be used while setting up the IPsec tunnel. This value is generated by cloudflare.
- `user_id` (String) `remote_id` in the form of an email address. This value is generated by cloudflare.

### Read-Only

- `id` (String) The ID of this resource.

## Import

An existing IPsec tunnel can be imported using the account ID and tunnel ID
Import is supported using the following syntax:

```
```shell
$ terraform import cloudflare_ipsec_tunnel.example d41d8cd98f00b204e9800998ecf8427e/cb029e245cfdd66dc8d2e570d5dd3322
```
1 change: 1 addition & 0 deletions examples/resources/cloudflare_ipsec_tunnel/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$ terraform import cloudflare_ipsec_tunnel.example <account_id>/<tunnel_id>
12 changes: 12 additions & 0 deletions examples/resources/cloudflare_ipsec_tunnel/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "cloudflare_ipsec_tunnel" "example" {
account_id = "c4a7362d577a6c3019a474fd6f485821"
name = "IPsec_1"
customer_endpoint = "203.0.113.1"
cloudflare_endpoint = "203.0.113.1"
interface_address = "192.0.2.0/31"
description = "Tunnel for ISP X"
health_check_enabled = true
health_check_target = "203.0.113.1"
health_check_type = "reply"
psk = "asdf12341234"
}
42 changes: 41 additions & 1 deletion internal/provider/resource_cloudflare_ipsec_tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func resourceCloudflareIPsecTunnel() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: resourceCloudflareIPsecTunnelImport,
},
Description: "Provides a resource, that manages IPsec tunnels for Magic Transit.",
}
}

Expand All @@ -39,6 +40,19 @@ func resourceCloudflareIPsecTunnelCreate(ctx context.Context, d *schema.Resource

d.SetId(newTunnel[0].ID)

// If PSK is not specified, call generate PSK and populate the field
psk, pskOk := d.Get("psk").(string)
if !pskOk || psk == "" {
psk, _, err = client.GenerateMagicTransitIPsecTunnelPSK(ctx, accountID, d.Id())
if err != nil {
defer d.SetId("")
tflog.Error(ctx, fmt.Sprintf("error creating PSK: %s %s", accountID, d.Id()))
// Need to delete the tunnel
return resourceCloudflareIPsecTunnelDelete(ctx, d, meta)
}
d.Set("psk", psk)
}

return resourceCloudflareIPsecTunnelRead(ctx, d, meta)
}

Expand Down Expand Up @@ -79,6 +93,15 @@ func resourceCloudflareIPsecTunnelRead(ctx context.Context, d *schema.ResourceDa
d.Set("customer_endpoint", tunnel.CustomerEndpoint)
d.Set("cloudflare_endpoint", tunnel.CloudflareEndpoint)
d.Set("interface_address", tunnel.InterfaceAddress)
d.Set("health_check_enabled", tunnel.HealthCheck.Enabled)
d.Set("health_check_target", tunnel.HealthCheck.Target)
d.Set("health_check_type", tunnel.HealthCheck.Type)

// Set Remote Identities
d.Set("hex_id", tunnel.RemoteIdentities.HexID)
d.Set("fqdn_id", tunnel.RemoteIdentities.FQDNID)
d.Set("user_id", tunnel.RemoteIdentities.UserID)
d.Set("remote_id", accountID+"_"+d.Id())

if len(tunnel.Description) > 0 {
d.Set("description", tunnel.Description)
Expand All @@ -90,12 +113,24 @@ func resourceCloudflareIPsecTunnelRead(ctx context.Context, d *schema.ResourceDa
func resourceCloudflareIPsecTunnelUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
accountID := d.Get("account_id").(string)
client := meta.(*cloudflare.API)

_, err := client.UpdateMagicTransitIPsecTunnel(ctx, accountID, d.Id(), IPsecTunnelFromResource(d))
if err != nil {
return diag.FromErr(errors.Wrap(err, fmt.Sprintf("error updating IPsec tunnel %q", d.Id())))
}

// Note: PSK field is expected to be populated during create. The only reason
// it can be empty is when the resource wants to regenerate it.
psk, pskOk := d.Get("psk").(string)
if !pskOk || psk == "" {
psk, _, err = client.GenerateMagicTransitIPsecTunnelPSK(ctx, accountID, d.Id())
if err != nil {
// Return Update PSK generation failed
return diag.FromErr(errors.Wrap(err, fmt.Sprintf("error regenerating PSK: %s %s", accountID, d.Id())))
} else {
d.Set("psk", psk)
}
}

return resourceCloudflareIPsecTunnelRead(ctx, d, meta)
}

Expand Down Expand Up @@ -126,5 +161,10 @@ func IPsecTunnelFromResource(d *schema.ResourceData) cloudflare.MagicTransitIPse
tunnel.Description = description.(string)
}

psk, pskOk := d.GetOk("psk")
if pskOk {
tunnel.Psk = psk.(string)
}

return tunnel
}
53 changes: 48 additions & 5 deletions internal/provider/resource_cloudflare_ipsec_tunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestAccCloudflareIPsecTunnelExists(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_ipsec_tunnel.%s", rnd)
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
psk := "asdf1234"

var Tunnel cloudflare.MagicTransitIPsecTunnel

Expand All @@ -26,14 +27,18 @@ func TestAccCloudflareIPsecTunnelExists(t *testing.T) {
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID),
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID, psk),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareIPsecTunnelExists(name, &Tunnel),
resource.TestCheckResourceAttr(name, "description", rnd),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "customer_endpoint", "203.0.113.1"),
resource.TestCheckResourceAttr(name, "cloudflare_endpoint", "162.159.64.41"),
resource.TestCheckResourceAttr(name, "interface_address", "10.212.0.9/31"),
resource.TestCheckResourceAttr(name, "health_check_enabled", "true"),
resource.TestCheckResourceAttr(name, "health_check_target", "203.0.113.1"),
resource.TestCheckResourceAttr(name, "health_check_type", "request"),
resource.TestCheckResourceAttr(name, "psk", "asdf1234"),
),
},
},
Expand Down Expand Up @@ -69,6 +74,7 @@ func TestAccCloudflareIPsecTunnelUpdateDescription(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_ipsec_tunnel.%s", rnd)
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
psk := "asdf1234"

var Tunnel cloudflare.MagicTransitIPsecTunnel

Expand All @@ -77,14 +83,14 @@ func TestAccCloudflareIPsecTunnelUpdateDescription(t *testing.T) {
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID),
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID, psk),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareIPsecTunnelExists(name, &Tunnel),
resource.TestCheckResourceAttr(name, "description", rnd),
),
},
{
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd+"-updated", accountID),
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd+"-updated", accountID, psk),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareIPsecTunnelExists(name, &Tunnel),
resource.TestCheckResourceAttr(name, "description", rnd+"-updated"),
Expand All @@ -94,7 +100,40 @@ func TestAccCloudflareIPsecTunnelUpdateDescription(t *testing.T) {
})
}

func testAccCheckCloudflareIPsecTunnelSimple(ID, description, accountID string) string {
func TestAccCloudflareIPsecTunnelUpdatePsk(t *testing.T) {
skipMagicTransitTestForNonConfiguredDefaultZone(t)

rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_ipsec_tunnel.%s", rnd)
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
psk := "asdf1234"
pskUpdated := "1234asd"

var Tunnel cloudflare.MagicTransitIPsecTunnel

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckAccount(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID, psk),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareIPsecTunnelExists(name, &Tunnel),
resource.TestCheckResourceAttr(name, "psk", psk),
),
},
{
Config: testAccCheckCloudflareIPsecTunnelSimple(rnd, rnd, accountID, pskUpdated),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareIPsecTunnelExists(name, &Tunnel),
resource.TestCheckResourceAttr(name, "psk", pskUpdated),
),
},
},
})
}

func testAccCheckCloudflareIPsecTunnelSimple(ID, description, accountID, psk string) string {
return fmt.Sprintf(`
resource "cloudflare_ipsec_tunnel" "%[1]s" {
account_id = "%[3]s"
Expand All @@ -103,5 +142,9 @@ func testAccCheckCloudflareIPsecTunnelSimple(ID, description, accountID string)
cloudflare_endpoint = "162.159.64.41"
interface_address = "10.212.0.9/31"
description = "%[2]s"
}`, ID, description, accountID)
health_check_enabled = true
health_check_target = "203.0.113.1"
health_check_type = "request"
psk = "%[4]s"
}`, ID, description, accountID, psk)
}
82 changes: 71 additions & 11 deletions internal/provider/schema_cloudflare_ipsec_tunnel.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package provider

import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceCloudflareIPsecTunnelSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
Expand All @@ -11,24 +16,79 @@ func resourceCloudflareIPsecTunnelSchema() map[string]*schema.Schema {
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
Description: "Name of the IPsec tunnel.",
},
"customer_endpoint": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
Description: "IP address assigned to the customer side of the IPsec tunnel.",
},
"cloudflare_endpoint": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
Description: "IP address assigned to the Cloudflare side of the IPsec tunnel.",
},
"interface_address": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
Description: "31-bit prefix (/31 in CIDR notation) supporting 2 hosts, one for each side of the tunnel.",
},
"description": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
Description: "An optional description of the IPsec tunnel.",
},
"health_check_enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
Description: "Specifies if ICMP tunnel health checks are enabled. Default: `true`.",
},
"health_check_target": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "The IP address of the customer endpoint that will receive tunnel health checks. Default: `<customer_gre_endpoint>`.",
},
"health_check_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"request", "reply"}, false),
Description: fmt.Sprintf("Specifies the ICMP echo type for the health check (`request` or `reply`). %s Default: `reply`.", renderAvailableDocumentationValuesStringSlice([]string{"request", "reply"})),
},
"psk": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Sensitive: true,
Description: "Pre shared key to be used with the IPsec tunnel. If left unset, it will be autogenerated.",
},
"hex_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "`remote_id` as a hex string. This value is generated by cloudflare.",
},
"user_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "`remote_id` in the form of an email address. This value is generated by cloudflare.",
},
"fqdn_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "`remote_id` in the form of a fqdn. This value is generated by cloudflare.",
},
"remote_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "ID to be used while setting up the IPsec tunnel. This value is generated by cloudflare.",
},
}
}
Loading