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

Adds support for Magic Transit IPsec tunnels #787

Merged
merged 6 commits into from
Jan 23, 2022
Merged
Changes from 3 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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -28,23 +28,24 @@ The current feature list includes:
* [x] Cache purging
* [x] Cloudflare IPs
* [x] Custom hostnames
* [x] DNS Firewall
* [x] DNS Records
* [x] Firewall (partial)
* [x] Gateway Locations
* [x] [Keyless SSL](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/)
* [x] [Load Balancing](https://blog.cloudflare.com/introducing-load-balancing-intelligent-failover-with-cloudflare/)
* [x] [Logpush Jobs](https://developers.cloudflare.com/logs/logpush/)
* [x] Magic Transit / Magic WAN
* [x] Notifications
* [ ] Organization Administration
* [x] [Origin CA](https://blog.cloudflare.com/universal-ssl-encryption-all-the-way-to-the-origin-for-free/)
* [x] [Railgun](https://www.cloudflare.com/railgun/) administration
* [x] Rate Limiting
* [x] User Administration (partial)
* [x] DNS Firewall
* [x] Web Application Firewall (WAF)
* [x] Workers KV
* [x] Zone Lockdown and User-Agent Block rules
* [x] Zones
* [x] Workers KV
* [x] Notifications
* [x] Gateway Locations

Pull Requests are welcome, but please open an issue (or comment in an existing
issue) to discuss any non-trivial changes before submitting code.
190 changes: 190 additions & 0 deletions magic_transit_ipsec_tunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package cloudflare

import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"net/http"
"time"
)

// Magic Transit IPsec Tunnel Error messages.
const (
errMagicTransitIPsecTunnelNotModified = "When trying to modify IPsec tunnel, API returned modified: false"
errMagicTransitIPsecTunnelNotDeleted = "When trying to delete IPsec tunnel, API returned deleted: false"
)

// MagicTransitIPsecTunnel contains information about an IPsec tunnel.
type MagicTransitIPsecTunnel struct {
ID string `json:"id,omitempty"`
CreatedOn *time.Time `json:"created_on,omitempty"`
ModifiedOn *time.Time `json:"modified_on,omitempty"`
Name string `json:"name"`
CustomerEndpoint string `json:"customer_endpoint"`
CloudflareEndpoint string `json:"cloudflare_endpoint"`
InterfaceAddress string `json:"interface_address"`
Description string `json:"description,omitempty"`
}

// ListMagicTransitIPsecTunnelsResponse contains a response including IPsec tunnels.
type ListMagicTransitIPsecTunnelsResponse struct {
Response
Result struct {
IPsecTunnels []MagicTransitIPsecTunnel `json:"ipsec_tunnels"`
} `json:"result"`
}

// GetMagicTransitIPsecTunnelResponse contains a response including zero or one IPsec tunnels.
type GetMagicTransitIPsecTunnelResponse struct {
Response
Result struct {
IPsecTunnel MagicTransitIPsecTunnel `json:"ipsec_tunnel"`
} `json:"result"`
}

// CreateMagicTransitIPsecTunnelsRequest is an array of IPsec tunnels to create.
type CreateMagicTransitIPsecTunnelsRequest struct {
IPsecTunnels []MagicTransitIPsecTunnel `json:"ipsec_tunnels"`
}

// UpdateMagicTransitIPsecTunnelResponse contains a response after updating an IPsec Tunnel.
type UpdateMagicTransitIPsecTunnelResponse struct {
Response
Result struct {
Modified bool `json:"modified"`
ModifiedIPsecTunnel MagicTransitIPsecTunnel `json:"modified_ipsec_tunnel"`
} `json:"result"`
}

// DeleteMagicTransitIPsecTunnelResponse contains a response after deleting an IPsec Tunnel.
type DeleteMagicTransitIPsecTunnelResponse struct {
Response
Result struct {
Deleted bool `json:"deleted"`
DeletedIPsecTunnel MagicTransitIPsecTunnel `json:"deleted_ipsec_tunnel"`
} `json:"result"`
}

// ListMagicTransitIPsecTunnels lists all IPsec tunnels for a given account
//
// API reference: https://api.cloudflare.com/#magic-ipsec-tunnels-list-ipsec-tunnels
func (api *API) ListMagicTransitIPsecTunnels(ctx context.Context) ([]MagicTransitIPsecTunnel, error) {
if err := api.checkAccountID(); err != nil {
return []MagicTransitIPsecTunnel{}, err
}

uri := fmt.Sprintf("/accounts/%s/magic/ipsec_tunnels", api.AccountID)
jacobbednarz marked this conversation as resolved.
Show resolved Hide resolved
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return []MagicTransitIPsecTunnel{}, err
}

result := ListMagicTransitIPsecTunnelsResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return []MagicTransitIPsecTunnel{}, errors.Wrap(err, errUnmarshalError)
}

return result.Result.IPsecTunnels, nil
}

// GetMagicTransitIPsecTunnel returns zero or one IPsec tunnel
//
// API reference: https://api.cloudflare.com/#magic-ipsec-tunnels-ipsec-tunnel-details
func (api *API) GetMagicTransitIPsecTunnel(ctx context.Context, id string) (MagicTransitIPsecTunnel, error) {
if err := api.checkAccountID(); err != nil {
return MagicTransitIPsecTunnel{}, err
}

uri := fmt.Sprintf("/accounts/%s/magic/ipsec_tunnels/%s", api.AccountID, id)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return MagicTransitIPsecTunnel{}, err
}

result := GetMagicTransitIPsecTunnelResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return MagicTransitIPsecTunnel{}, errors.Wrap(err, errUnmarshalError)
}

return result.Result.IPsecTunnel, nil
}

// CreateMagicTransitIPsecTunnels creates one or more IPsec tunnels
//
// API reference: https://api.cloudflare.com/#magic-ipsec-tunnels-create-ipsec-tunnels
func (api *API) CreateMagicTransitIPsecTunnels(ctx context.Context, tunnels []MagicTransitIPsecTunnel) ([]MagicTransitIPsecTunnel, error) {
if err := api.checkAccountID(); err != nil {
return []MagicTransitIPsecTunnel{}, err
}

uri := fmt.Sprintf("/accounts/%s/magic/ipsec_tunnels", api.AccountID)
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, CreateMagicTransitIPsecTunnelsRequest{
IPsecTunnels: tunnels,
})

if err != nil {
return []MagicTransitIPsecTunnel{}, err
}

result := ListMagicTransitIPsecTunnelsResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return []MagicTransitIPsecTunnel{}, errors.Wrap(err, errUnmarshalError)
}

return result.Result.IPsecTunnels, nil
}

// UpdateMagicTransitIPsecTunnel updates an IPsec tunnel
//
// API reference: https://api.cloudflare.com/#magic-ipsec-tunnels-update-ipsec-tunnel
func (api *API) UpdateMagicTransitIPsecTunnel(ctx context.Context, id string, tunnel MagicTransitIPsecTunnel) (MagicTransitIPsecTunnel, error) {
if err := api.checkAccountID(); err != nil {
return MagicTransitIPsecTunnel{}, err
}

uri := fmt.Sprintf("/accounts/%s/magic/ipsec_tunnels/%s", api.AccountID, id)
res, err := api.makeRequestContext(ctx, http.MethodPut, uri, tunnel)

if err != nil {
return MagicTransitIPsecTunnel{}, err
}

result := UpdateMagicTransitIPsecTunnelResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return MagicTransitIPsecTunnel{}, errors.Wrap(err, errUnmarshalError)
}

if !result.Result.Modified {
return MagicTransitIPsecTunnel{}, errors.New(errMagicTransitIPsecTunnelNotModified)
}

return result.Result.ModifiedIPsecTunnel, nil
}

// DeleteMagicTransitIPsecTunnel deletes an IPsec Tunnel
//
// API reference: https://api.cloudflare.com/#magic-ipsec-tunnels-delete-ipsec-tunnel
func (api *API) DeleteMagicTransitIPsecTunnel(ctx context.Context, id string) (MagicTransitIPsecTunnel, error) {
if err := api.checkAccountID(); err != nil {
return MagicTransitIPsecTunnel{}, err
}

uri := fmt.Sprintf("/accounts/%s/magic/ipsec_tunnels/%s", api.AccountID, id)
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)

if err != nil {
return MagicTransitIPsecTunnel{}, err
}

result := DeleteMagicTransitIPsecTunnelResponse{}
if err := json.Unmarshal(res, &result); err != nil {
return MagicTransitIPsecTunnel{}, errors.Wrap(err, errUnmarshalError)
}

if !result.Result.Deleted {
return MagicTransitIPsecTunnel{}, errors.New(errMagicTransitIPsecTunnelNotDeleted)
}

return result.Result.DeletedIPsecTunnel, nil
}
259 changes: 259 additions & 0 deletions magic_transit_ipsec_tunnel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package cloudflare

import (
"context"
"fmt"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestListMagicTransitIPsecTunnels(t *testing.T) {
setup(UsingAccount("foo"))
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"ipsec_tunnels": [
{
"id": "c4a7362d577a6c3019a474fd6f485821",
"created_on": "2017-06-14T00:00:00Z",
"modified_on": "2017-06-14T05:20:00Z",
"name": "IPsec_1",
"customer_endpoint": "203.0.113.1",
"cloudflare_endpoint": "203.0.113.2",
"interface_address": "192.0.2.0/31",
"description": "Tunnel for ISP X"
}
]
}
}`)
}

mux.HandleFunc("/accounts/foo/magic/ipsec_tunnels", handler)

createdOn, _ := time.Parse(time.RFC3339, "2017-06-14T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2017-06-14T05:20:00Z")

want := []MagicTransitIPsecTunnel{
{
ID: "c4a7362d577a6c3019a474fd6f485821",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
Name: "IPsec_1",
CustomerEndpoint: "203.0.113.1",
CloudflareEndpoint: "203.0.113.2",
InterfaceAddress: "192.0.2.0/31",
Description: "Tunnel for ISP X",
},
}

actual, err := client.ListMagicTransitIPsecTunnels(context.Background())
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestGetMagicTransitIPsecTunnel(t *testing.T) {
setup(UsingAccount("foo"))
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"ipsec_tunnel": {
"id": "c4a7362d577a6c3019a474fd6f485821",
"created_on": "2017-06-14T00:00:00Z",
"modified_on": "2017-06-14T05:20:00Z",
"name": "IPsec_1",
"customer_endpoint": "203.0.113.1",
"cloudflare_endpoint": "203.0.113.2",
"interface_address": "192.0.2.0/31",
"description": "Tunnel for ISP X"
}
}
}`)
}

mux.HandleFunc("/accounts/foo/magic/ipsec_tunnels/c4a7362d577a6c3019a474fd6f485821", handler)

createdOn, _ := time.Parse(time.RFC3339, "2017-06-14T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2017-06-14T05:20:00Z")

want := MagicTransitIPsecTunnel{
ID: "c4a7362d577a6c3019a474fd6f485821",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
Name: "IPsec_1",
CustomerEndpoint: "203.0.113.1",
CloudflareEndpoint: "203.0.113.2",
InterfaceAddress: "192.0.2.0/31",
Description: "Tunnel for ISP X",
}

actual, err := client.GetMagicTransitIPsecTunnel(context.Background(), "c4a7362d577a6c3019a474fd6f485821")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestCreateMagicTransitIPsecTunnels(t *testing.T) {
setup(UsingAccount("foo"))
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"ipsec_tunnels": [
{
"id": "c4a7362d577a6c3019a474fd6f485821",
"created_on": "2017-06-14T00:00:00Z",
"modified_on": "2017-06-14T05:20:00Z",
"name": "IPsec_1",
"customer_endpoint": "203.0.113.1",
"cloudflare_endpoint": "203.0.113.2",
"interface_address": "192.0.2.0/31",
"description": "Tunnel for ISP X"
}
]
}
}`)
}

mux.HandleFunc("/accounts/foo/magic/ipsec_tunnels", handler)

createdOn, _ := time.Parse(time.RFC3339, "2017-06-14T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2017-06-14T05:20:00Z")

want := []MagicTransitIPsecTunnel{{
ID: "c4a7362d577a6c3019a474fd6f485821",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
Name: "IPsec_1",
CustomerEndpoint: "203.0.113.1",
CloudflareEndpoint: "203.0.113.2",
InterfaceAddress: "192.0.2.0/31",
Description: "Tunnel for ISP X",
}}

actual, err := client.CreateMagicTransitIPsecTunnels(context.Background(), want)
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestUpdateMagicTransitIPsecTunnel(t *testing.T) {
setup(UsingAccount("foo"))
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"modified": true,
"modified_ipsec_tunnel": {
"id": "c4a7362d577a6c3019a474fd6f485821",
"created_on": "2017-06-14T00:00:00Z",
"modified_on": "2017-06-14T05:20:00Z",
"name": "IPsec_1",
"customer_endpoint": "203.0.113.1",
"cloudflare_endpoint": "203.0.113.2",
"interface_address": "192.0.2.0/31",
"description": "Tunnel for ISP X"
}
}
}`)
}

mux.HandleFunc("/accounts/foo/magic/ipsec_tunnels/c4a7362d577a6c3019a474fd6f485821", handler)

createdOn, _ := time.Parse(time.RFC3339, "2017-06-14T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2017-06-14T05:20:00Z")

want := MagicTransitIPsecTunnel{
ID: "c4a7362d577a6c3019a474fd6f485821",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
Name: "IPsec_1",
CustomerEndpoint: "203.0.113.1",
CloudflareEndpoint: "203.0.113.2",
InterfaceAddress: "192.0.2.0/31",
Description: "Tunnel for ISP X",
}

actual, err := client.UpdateMagicTransitIPsecTunnel(context.Background(), "c4a7362d577a6c3019a474fd6f485821", want)
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestDeleteMagicTransitIPsecTunnel(t *testing.T) {
setup(UsingAccount("foo"))
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"deleted": true,
"deleted_ipsec_tunnel": {
"id": "c4a7362d577a6c3019a474fd6f485821",
"created_on": "2017-06-14T00:00:00Z",
"modified_on": "2017-06-14T05:20:00Z",
"name": "IPsec_1",
"customer_endpoint": "203.0.113.1",
"cloudflare_endpoint": "203.0.113.2",
"interface_address": "192.0.2.0/31",
"description": "Tunnel for ISP X"
}
}
}`)
}

mux.HandleFunc("/accounts/foo/magic/ipsec_tunnels/c4a7362d577a6c3019a474fd6f485821", handler)

createdOn, _ := time.Parse(time.RFC3339, "2017-06-14T00:00:00Z")
modifiedOn, _ := time.Parse(time.RFC3339, "2017-06-14T05:20:00Z")

want := MagicTransitIPsecTunnel{
ID: "c4a7362d577a6c3019a474fd6f485821",
CreatedOn: &createdOn,
ModifiedOn: &modifiedOn,
Name: "IPsec_1",
CustomerEndpoint: "203.0.113.1",
CloudflareEndpoint: "203.0.113.2",
InterfaceAddress: "192.0.2.0/31",
Description: "Tunnel for ISP X",
}

actual, err := client.DeleteMagicTransitIPsecTunnel(context.Background(), "c4a7362d577a6c3019a474fd6f485821")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}