From ec1fe3be88a67c5cda515875eaebcefdbc02a1de Mon Sep 17 00:00:00 2001 From: ka-myl Date: Thu, 14 Mar 2024 10:30:39 +0100 Subject: [PATCH] feature(gateway): add VPNaaS support (#306) --- .gitignore | 1 + CHANGELOG.md | 3 + upcloud/gateway.go | 136 ++++- upcloud/gateway_test.go | 165 ++++- upcloud/request/gateway.go | 152 +++++ upcloud/request/gateway_test.go | 90 ++- .../{gateway.yaml => gatewaynat.yaml} | 0 upcloud/service/fixtures/gatewayvpn.yaml | 194 ++++++ .../fixtures/gatewayvpnconnections.yaml | 242 ++++++++ .../fixtures/gatewayvpnconnectiontunnels.yaml | 268 ++++++++ upcloud/service/gateway.go | 77 +++ upcloud/service/gateway_test.go | 576 +++++++++++++++++- upcloud/service/service.go | 1 + 13 files changed, 1897 insertions(+), 8 deletions(-) rename upcloud/service/fixtures/{gateway.yaml => gatewaynat.yaml} (100%) create mode 100644 upcloud/service/fixtures/gatewayvpn.yaml create mode 100644 upcloud/service/fixtures/gatewayvpnconnections.yaml create mode 100644 upcloud/service/fixtures/gatewayvpnconnectiontunnels.yaml diff --git a/.gitignore b/.gitignore index 33d74439..975c7154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.vscode /go.work /go.work.sum temp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index dd35f92f..1f82a3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +### Added +- **Experimental**, Gateway: support for VPN feature. Note that VPN feature is currently in beta, you can learn more about it on the [product page](https://upcloud.com/resources/docs/networking#nat-and-vpn-gateways) + ## [8.2.0] ### Added diff --git a/upcloud/gateway.go b/upcloud/gateway.go index ee311596..f48576c7 100644 --- a/upcloud/gateway.go +++ b/upcloud/gateway.go @@ -5,12 +5,23 @@ import "time" // GatewayConfiguredStatus represents a desired status of the service type GatewayConfiguredStatus string -// GatewayConfiguredStatus represents a current actual status of the service +// GatewayConfiguredStatus represents current, actual status of the service type GatewayOperationalState string // GatewayFeature represents a feature of the service type GatewayFeature string +// GatewayTunnelOperationalState represents current, actual status of the tunnel +type GatewayTunnelOperationalState string + +type ( + GatewayConnectionType string + GatewayRouteType string + GatewayIPSecAuthType string + GatewayIPSecAlgorithm string + GatewayIPSecIntegrityAlgorithm string +) + const ( GatewayConfiguredStatusStarted GatewayConfiguredStatus = "started" GatewayConfiguredStatusStopped GatewayConfiguredStatus = "stopped" @@ -30,14 +41,55 @@ const ( GatewayOperationalStateDeleteLinkNetwork GatewayOperationalState = "delete-link-network" GatewayOperationalStateDeleteService GatewayOperationalState = "delete-service" - // GatewayFeatureNAT is the network address translation (NAT) feature of the network gateway + GatewayTunnelOperationalStateUninitialized GatewayTunnelOperationalState = "uninitialized" + GatewayTunnelOperationalStateCreated GatewayTunnelOperationalState = "created" + GatewayTunnelOperationalStateConnecting GatewayTunnelOperationalState = "connecting" + GatewayTunnelOperationalStateEstabilished GatewayTunnelOperationalState = "established" + GatewayTunnelOperationalStateRekeying GatewayTunnelOperationalState = "rekeying" + GatewayTunnelOperationalStateRekeyed GatewayTunnelOperationalState = "rekeyed" + GatewayTunnelOperationalStateDeleting GatewayTunnelOperationalState = "deleting" + GatewayTunnelOperationalStateDestroying GatewayTunnelOperationalState = "destroying" + GatewayTunnelOperationalStateUnknown GatewayTunnelOperationalState = "unknown" + + // GatewayFeatureNAT is a Network Address Translation (NAT) service that offers a way for cloud servers in SDN private networks to connect to the Internet through the public IP assigned to the network gateway service GatewayFeatureNAT GatewayFeature = "nat" + + // GatewayFeatureVPN is a Virtual Private Network (VPN) service used to establish an encrypted network connection when using public networks + // Please note that VPN feature is currently in beta. You can learn more about it on its [product page] + // Also note that VPN is available only in some of the gateway plans. To check which plans support VPN, you can use the GetGatewayPlans method. + // + // [product page]: https://upcloud.com/resources/docs/networking#nat-and-vpn-gateways + GatewayFeatureVPN GatewayFeature = "vpn" + + GatewayConnectionTypeIPSec GatewayConnectionType = "ipsec" + + GatewayRouteTypeStatic GatewayRouteType = "static" + + GatewayTunnelIPSecAuthTypePSK GatewayIPSecAuthType = "psk" + + GatewayIPSecAlgorithm_aes128gcm16 GatewayIPSecAlgorithm = "aes128gcm16" + GatewayIPSecAlgorithm_aes128gcm128 GatewayIPSecAlgorithm = "aes128gcm128" + GatewayIPSecAlgorithm_aes192gcm16 GatewayIPSecAlgorithm = "aes192gcm16" + GatewayIPSecAlgorithm_aes192gcm128 GatewayIPSecAlgorithm = "aes192gcm128" + GatewayIPSecAlgorithm_aes256gcm16 GatewayIPSecAlgorithm = "aes256gcm16" + GatewayIPSecAlgorithm_aes256gcm128 GatewayIPSecAlgorithm = "aes256gcm128" + GatewayIPSecAlgorithm_aes128 GatewayIPSecAlgorithm = "aes128" + GatewayIPSecAlgorithm_aes192 GatewayIPSecAlgorithm = "aes192" + GatewayIPSecAlgorithm_aes256 GatewayIPSecAlgorithm = "aes256" + + GatewayIPSecIntegrityAlgorithm_aes128gmac GatewayIPSecIntegrityAlgorithm = "aes128gmac" + GatewayIPSecIntegrityAlgorithm_aes256gmac GatewayIPSecIntegrityAlgorithm = "aes256gmac" + GatewayIPSecIntegrityAlgorithm_sha1 GatewayIPSecIntegrityAlgorithm = "sha1" + GatewayIPSecIntegrityAlgorithm_sha256 GatewayIPSecIntegrityAlgorithm = "sha256" + GatewayIPSecIntegrityAlgorithm_sha384 GatewayIPSecIntegrityAlgorithm = "sha384" + GatewayIPSecIntegrityAlgorithm_sha512 GatewayIPSecIntegrityAlgorithm = "sha512" ) type Gateway struct { UUID string `json:"uuid,omitempty"` Name string `json:"name,omitempty"` Zone string `json:"zone,omitempty"` + Plan string `json:"plan,omitempty"` Labels []Label `json:"labels,omitempty"` ConfiguredStatus GatewayConfiguredStatus `json:"configured_status,omitempty"` OperationalState GatewayOperationalState `json:"operational_state,omitempty"` @@ -46,6 +98,7 @@ type Gateway struct { CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` Addresses []GatewayAddress `json:"addresses,omitempty"` + Connections []GatewayConnection `json:"connections,omitempty"` } type GatewayAddress struct { @@ -57,3 +110,82 @@ type GatewayRouter struct { CreatedAt time.Time `json:"created_at,omitempty"` UUID string `json:"uuid,omitempty"` } + +type GatewayConnection struct { + Name string `json:"name,omitempty"` + Type GatewayConnectionType `json:"type,omitempty"` + LocalRoutes []GatewayRoute `json:"local_routes,omitempty"` + RemoteRoutes []GatewayRoute `json:"remote_routes,omitempty"` + Tunnels []GatewayTunnel `json:"tunnels,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` +} + +type GatewayRoute struct { + Name string `json:"name,omitempty"` + StaticNetwork string `json:"static_network,omitempty"` + Type GatewayRouteType `json:"type,omitempty"` +} + +type GatewayTunnel struct { + Name string `json:"name,omitempty"` + LocalAddress GatewayTunnelLocalAddress `json:"local_address,omitempty"` + RemoteAddress GatewayTunnelRemoteAddress `json:"remote_address,omitempty"` + IPSec GatewayTunnelIPSec `json:"ipsec,omitempty"` + OperationalState GatewayTunnelOperationalState `json:"operational_state,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` +} + +type GatewayTunnelLocalAddress struct { + // Name of the UpCloud gateway address; should correspond to the name of one of the gateway address structs + Name string `json:"name,omitempty"` +} + +type GatewayTunnelRemoteAddress struct { + // Address is a remote peer address VPN will connect to; must be global non-private unicast IP address. + Address string `json:"address,omitempty"` +} + +type GatewayTunnelIPSec struct { + // Tunnel IPSec authentication object + Authentication GatewayTunnelIPSecAuth `json:"authentication,omitempty"` + // IKE SA rekey time in seconds + RekeyTime int `json:"rekey_time,omitempty"` + // IKE child SA rekey time in seconds + ChildRekeyTime int `json:"child_rekey_time,omitempty"` + // Delay before sending Dead Peer Detection packets if no traffic is detected, in seconds + DPDDelay int `json:"dpd_delay,omitempty"` + // Timeout period for DPD reply before considering the peer to be dead, in seconds + DPDTimeout int `json:"dpd_timeout,omitempty"` + // Maximum IKE SA lifetime in seconds + IKELifetime int `json:"ike_lifetime,omitempty"` + // List of Phase 1: Proposal algorithms + Phase1Algorithms []GatewayIPSecAlgorithm `json:"phase1_algorithms,omitempty"` + // List of Phase 1 integrity algorithms + Phase1IntegrityAlgorithms []GatewayIPSecIntegrityAlgorithm `json:"phase1_integrity_algorithms,omitempty"` + // List of Phase 1 Diffie-Hellman group numbers + Phase1DHGroupNumbers []int `json:"phase1_dh_group_numbers,omitempty"` + // List of Phase 2: Security Association algorithms + Phase2Algorithms []GatewayIPSecAlgorithm `json:"phase2_algorithms,omitempty"` + // List of Phase 2 integrity algorithms + Phase2IntegrityAlgorithms []GatewayIPSecIntegrityAlgorithm `json:"phase2_integrity_algorithms,omitempty"` + // List of Phase 2 Diffie-Hellman group numbers + Phase2DHGroupNumbers []int `json:"phase2_dh_group_numbers,omitempty"` +} + +type GatewayTunnelIPSecAuth struct { + Authentication GatewayIPSecAuthType `json:"authentication,omitempty"` + // PSK is a user-provided pre-shared key. + // Note that this field is only meant to be used when providing API with your pre-shared key; it will always be empty in API responses + PSK string `json:"psk,omitempty"` +} + +type GatewayPlan struct { + Name string `json:"name,omitempty"` + PerGatewayBandwidthMbps int `json:"per_gateway_bandwidth_mbps,omitempty"` + PerGatewayMaxConnections int `json:"per_gateway_max_connections,omitempty"` + ServerNumber int `json:"server_number,omitempty"` + SupportedFeatures []GatewayFeature `json:"supported_features,omitempty"` + VPNTunnelAmount int `json:"vpn_tunnel_amount,omitempty"` +} diff --git a/upcloud/gateway_test.go b/upcloud/gateway_test.go index 2ca97b06..a4137054 100644 --- a/upcloud/gateway_test.go +++ b/upcloud/gateway_test.go @@ -2,6 +2,9 @@ package upcloud import ( "testing" + "time" + + "github.com/stretchr/testify/require" ) func TestGateway(t *testing.T) { @@ -18,7 +21,8 @@ func TestGateway(t *testing.T) { "configured_status": "started", "created_at": "2022-12-01T09:04:08.529138Z", "features": [ - "nat" + "nat", + "vpn" ], "name": "example-gateway", "operational_state": "running", @@ -36,10 +40,66 @@ func TestGateway(t *testing.T) { ], "updated_at": "2022-12-01T19:04:08.529138Z", "uuid": "10c153e0-12e4-4dea-8748-4f34850ff76d", - "zone": "fi-hel1" + "zone": "fi-hel1", + "plan": "advanced", + "connections": [ + { + "name": "example-connection", + "type": "ipsec", + "local_routes": [ + { + "name": "upcloud-example-route", + "type": "static", + "static_network": "10.0.0.0/24" + } + ], + "remote_routes": [ + { + "name": "remote-example-route", + "type": "static", + "static_network": "10.0.1.0/24" + } + ], + "tunnels": [ + { + "name": "example-tunnel-1", + "local_address": { + "name": "public-ip-1" + }, + "remote_address": { + "address": "100.10.0.111" + }, + "ipsec": { + "authentication": { + "authentication": "psk" + }, + "child_rekey_time": 1440, + "dpd_delay": 30, + "dpd_timeout": 120, + "ike_lifetime": 86400, + "phase1_algorithms": ["aes128gcm128", "aes256gcm128"], + "phase1_dh_group_numbers": [14, 16, 18, 19, 20, 21], + "phase1_integrity_algorithms": ["aes128gmac", "aes256gmac", "sha256", "sha384", "sha512"], + "phase2_algorithms": ["aes128gcm128", "aes256gcm128"], + "phase2_dh_group_numbers": [14, 16, 18, 19, 20, 21], + "phase2_integrity_algorithms": ["aes128gmac", "aes256gmac", "sha256", "sha384", "sha512"], + "rekey_time": 14400 + }, + "operational_state": "established", + "created_at": "2022-12-01T09:04:08.529138Z", + "updated_at": "2022-12-01T09:04:08.529138Z" + } + ], + "created_at": "2022-12-01T09:04:08.529138Z", + "updated_at": "2022-12-01T09:04:08.529138Z" + } + ] } ` + timestamp, err := time.Parse(time.RFC3339, "2022-12-01T09:04:08.529138Z") + require.NoError(t, err) + gateway := &Gateway{ Addresses: []GatewayAddress{{ Address: "192.0.2.96", @@ -49,6 +109,7 @@ func TestGateway(t *testing.T) { CreatedAt: timeParse("2022-12-01T09:04:08.529138Z"), Features: []GatewayFeature{ GatewayFeatureNAT, + GatewayFeatureVPN, }, Name: "example-gateway", OperationalState: "running", @@ -64,7 +125,107 @@ func TestGateway(t *testing.T) { UpdatedAt: timeParse("2022-12-01T19:04:08.529138Z"), UUID: "10c153e0-12e4-4dea-8748-4f34850ff76d", Zone: "fi-hel1", + Plan: "advanced", + Connections: []GatewayConnection{ + { + Name: "example-connection", + Type: GatewayConnectionTypeIPSec, + LocalRoutes: []GatewayRoute{ + { + Name: "upcloud-example-route", + Type: GatewayRouteTypeStatic, + StaticNetwork: "10.0.0.0/24", + }, + }, + RemoteRoutes: []GatewayRoute{ + { + Name: "remote-example-route", + Type: GatewayRouteTypeStatic, + StaticNetwork: "10.0.1.0/24", + }, + }, + Tunnels: []GatewayTunnel{ + { + Name: "example-tunnel-1", + LocalAddress: GatewayTunnelLocalAddress{ + Name: "public-ip-1", + }, + RemoteAddress: GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: GatewayTunnelIPSec{ + Authentication: GatewayTunnelIPSecAuth{ + Authentication: GatewayTunnelIPSecAuthTypePSK, + }, + ChildRekeyTime: 1440, + DPDDelay: 30, + DPDTimeout: 120, + IKELifetime: 86400, + Phase1Algorithms: []GatewayIPSecAlgorithm{ + GatewayIPSecAlgorithm_aes128gcm128, + GatewayIPSecAlgorithm_aes256gcm128, + }, + Phase1DHGroupNumbers: []int{14, 16, 18, 19, 20, 21}, + Phase1IntegrityAlgorithms: []GatewayIPSecIntegrityAlgorithm{ + GatewayIPSecIntegrityAlgorithm_aes128gmac, + GatewayIPSecIntegrityAlgorithm_aes256gmac, + GatewayIPSecIntegrityAlgorithm_sha256, + GatewayIPSecIntegrityAlgorithm_sha384, + GatewayIPSecIntegrityAlgorithm_sha512, + }, + Phase2Algorithms: []GatewayIPSecAlgorithm{ + GatewayIPSecAlgorithm_aes128gcm128, + GatewayIPSecAlgorithm_aes256gcm128, + }, + Phase2DHGroupNumbers: []int{14, 16, 18, 19, 20, 21}, + Phase2IntegrityAlgorithms: []GatewayIPSecIntegrityAlgorithm{ + GatewayIPSecIntegrityAlgorithm_aes128gmac, + GatewayIPSecIntegrityAlgorithm_aes256gmac, + GatewayIPSecIntegrityAlgorithm_sha256, + GatewayIPSecIntegrityAlgorithm_sha384, + GatewayIPSecIntegrityAlgorithm_sha512, + }, + RekeyTime: 14400, + }, + OperationalState: GatewayTunnelOperationalStateEstabilished, + CreatedAt: timestamp, + UpdatedAt: timestamp, + }, + }, + CreatedAt: timestamp, + UpdatedAt: timestamp, + }, + }, } testJSON(t, &Gateway{}, gateway, jsonStr) } + +func TestGatewayPlan(t *testing.T) { + t.Parallel() + + jsonStr := ` + { + "name": "advanced", + "per_gateway_bandwidth_mbps": 10000, + "per_gateway_max_connections": 100000, + "server_number": 2, + "supported_features": [ + "nat", + "vpn" + ], + "vpn_tunnel_amount": 10 + } + ` + + plan := &GatewayPlan{ + Name: "advanced", + PerGatewayBandwidthMbps: 10000, + PerGatewayMaxConnections: 100000, + ServerNumber: 2, + SupportedFeatures: []GatewayFeature{GatewayFeatureNAT, GatewayFeatureVPN}, + VPNTunnelAmount: 10, + } + + testJSON(t, &GatewayPlan{}, plan, jsonStr) +} diff --git a/upcloud/request/gateway.go b/upcloud/request/gateway.go index a16191b7..c8a8dd8d 100644 --- a/upcloud/request/gateway.go +++ b/upcloud/request/gateway.go @@ -1,6 +1,7 @@ package request import ( + "encoding/json" "fmt" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" @@ -8,6 +9,12 @@ import ( const gatewayBaseURL string = "/gateway" +type GetGatewayPlansRequest struct{} + +func (r *GetGatewayPlansRequest) RequestURL() string { + return fmt.Sprintf("%s/plans", gatewayBaseURL) +} + type GetGatewaysRequest struct { Filters []QueryFilter } @@ -39,6 +46,9 @@ type CreateGatewayRequest struct { Routers []GatewayRouter `json:"routers,omitempty"` Labels []upcloud.Label `json:"labels,omitempty"` ConfiguredStatus upcloud.GatewayConfiguredStatus `json:"configured_status,omitempty"` + Plan string `json:"plan,omitempty"` + Addresses []upcloud.GatewayAddress `json:"addresses,omitempty"` + Connections []GatewayConnection `json:"connections,omitempty"` } func (r *CreateGatewayRequest) RequestURL() string { @@ -48,8 +58,10 @@ func (r *CreateGatewayRequest) RequestURL() string { type ModifyGatewayRequest struct { UUID string `json:"-"` Name string `json:"name,omitempty"` + Plan string `json:"plan,omitempty"` ConfiguredStatus upcloud.GatewayConfiguredStatus `json:"configured_status,omitempty"` Labels []upcloud.Label `json:"labels,omitempty"` + Connections []GatewayConnection `json:"connections,omitempty"` } func (r *ModifyGatewayRequest) RequestURL() string { @@ -63,3 +75,143 @@ type DeleteGatewayRequest struct { func (r *DeleteGatewayRequest) RequestURL() string { return fmt.Sprintf("%s/%s", gatewayBaseURL, r.UUID) } + +type GatewayConnection struct { + Name string `json:"name,omitempty"` + Type upcloud.GatewayConnectionType `json:"type,omitempty"` + LocalRoutes []upcloud.GatewayRoute `json:"local_routes,omitempty"` + RemoteRoutes []upcloud.GatewayRoute `json:"remote_routes,omitempty"` + Tunnels []GatewayTunnel `json:"tunnels,omitempty"` +} + +type GetGatewayConnectionsRequest struct { + ServiceUUID string `json:"-"` +} + +func (r *GetGatewayConnectionsRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections", gatewayBaseURL, r.ServiceUUID) +} + +type GetGatewayConnectionRequest struct { + ServiceUUID string `json:"-"` + Name string `json:"-"` +} + +func (r *GetGatewayConnectionRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s", gatewayBaseURL, r.ServiceUUID, r.Name) +} + +type CreateGatewayConnectionRequest struct { + ServiceUUID string `json:"-"` + Connection GatewayConnection +} + +func (r *CreateGatewayConnectionRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Connection) +} + +func (r *CreateGatewayConnectionRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/", gatewayBaseURL, r.ServiceUUID) +} + +type ModifyGatewayConnection struct { + LocalRoutes []upcloud.GatewayRoute `json:"local_routes,omitempty"` + RemoteRoutes []upcloud.GatewayRoute `json:"remote_routes,omitempty"` + Tunnels []GatewayTunnel `json:"tunnels,omitempty"` +} + +type ModifyGatewayConnectionRequest struct { + ServiceUUID string `json:"-"` + Name string `json:"-"` + Connection ModifyGatewayConnection +} + +func (r *ModifyGatewayConnectionRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s", gatewayBaseURL, r.ServiceUUID, r.Name) +} + +func (r *ModifyGatewayConnectionRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Connection) +} + +type DeleteGatewayConnectionRequest struct { + ServiceUUID string `json:"-"` + Name string `json:"-"` +} + +func (r *DeleteGatewayConnectionRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s", gatewayBaseURL, r.ServiceUUID, r.Name) +} + +type GatewayTunnel struct { + Name string `json:"name,omitempty"` + LocalAddress upcloud.GatewayTunnelLocalAddress `json:"local_address,omitempty"` + RemoteAddress upcloud.GatewayTunnelRemoteAddress `json:"remote_address,omitempty"` + IPSec upcloud.GatewayTunnelIPSec `json:"ipsec,omitempty"` + OperationalState upcloud.GatewayTunnelOperationalState `json:"operational_state,omitempty"` +} + +type GetGatewayConnectionTunnelsRequest struct { + ServiceUUID string `json:"-"` + ConnectionName string `json:"-"` +} + +func (r *GetGatewayConnectionTunnelsRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s/tunnels", gatewayBaseURL, r.ServiceUUID, r.ConnectionName) +} + +type GetGatewayConnectionTunnelRequest struct { + ServiceUUID string `json:"-"` + ConnectionName string `json:"-"` + Name string `json:"-"` +} + +func (r *GetGatewayConnectionTunnelRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s/tunnels/%s", gatewayBaseURL, r.ServiceUUID, r.ConnectionName, r.Name) +} + +type CreateGatewayConnectionTunnelRequest struct { + ServiceUUID string `json:"-"` + ConnectionName string `json:"-"` + Tunnel GatewayTunnel +} + +func (r *CreateGatewayConnectionTunnelRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s/tunnels", gatewayBaseURL, r.ServiceUUID, r.ConnectionName) +} + +func (r *CreateGatewayConnectionTunnelRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Tunnel) +} + +type ModifyGatewayTunnel struct { + Name string `json:"name,omitempty"` + LocalAddress *upcloud.GatewayTunnelLocalAddress `json:"local_address,omitempty"` + RemoteAddress *upcloud.GatewayTunnelRemoteAddress `json:"remote_address,omitempty"` + IPSec *upcloud.GatewayTunnelIPSec `json:"ipsec,omitempty"` +} + +type ModifyGatewayConnectionTunnelRequest struct { + ServiceUUID string `json:"-"` + ConnectionName string `json:"-"` + Name string `json:"-"` + Tunnel ModifyGatewayTunnel +} + +func (r *ModifyGatewayConnectionTunnelRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s/tunnels/%s", gatewayBaseURL, r.ServiceUUID, r.ConnectionName, r.Name) +} + +func (r *ModifyGatewayConnectionTunnelRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Tunnel) +} + +type DeleteGatewayConnectionTunnelRequest struct { + ServiceUUID string `json:"-"` + ConnectionName string `json:"-"` + Name string `json:"-"` +} + +func (r *DeleteGatewayConnectionTunnelRequest) RequestURL() string { + return fmt.Sprintf("%s/%s/connections/%s/tunnels/%s", gatewayBaseURL, r.ServiceUUID, r.ConnectionName, r.Name) +} diff --git a/upcloud/request/gateway_test.go b/upcloud/request/gateway_test.go index b8bc4e63..6196fd9f 100644 --- a/upcloud/request/gateway_test.go +++ b/upcloud/request/gateway_test.go @@ -56,7 +56,51 @@ func TestCreateGatewayRequest(t *testing.T) { "uuid": "0485d477-8d8f-4c97-9bef-731933187538" } ], - "configured_status": "started" + "configured_status": "started", + "plan": "random-plan", + "addresses": [ + { + "name": "my-public-ip" + } + ], + "connections": [ + { + "name": "example-connection", + "type": "ipsec", + "local_routes": [ + { + "name": "upcloud-example-route", + "type": "static", + "static_network": "10.0.0.0/24" + } + ], + "remote_routes": [ + { + "name": "remote-example-route", + "type": "static", + "static_network": "10.0.1.0/24" + } + ], + "tunnels": [ + { + "name": "example-tunnel-1", + "local_address": { + "name": "public-ip-1" + }, + "remote_address": { + "address": "100.10.0.111" + }, + "ipsec": { + "authentication": { + "authentication": "psk", + "psk": "pskpsksk" + } + } + } + ] + + } + ] } ` r := CreateGatewayRequest{ @@ -69,7 +113,51 @@ func TestCreateGatewayRequest(t *testing.T) { }, }, ConfiguredStatus: upcloud.GatewayConfiguredStatusStarted, + Plan: "random-plan", + Addresses: []upcloud.GatewayAddress{ + { + Name: "my-public-ip", + }, + }, + Connections: []GatewayConnection{ + { + Name: "example-connection", + Type: upcloud.GatewayConnectionTypeIPSec, + LocalRoutes: []upcloud.GatewayRoute{ + { + Name: "upcloud-example-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.0.0/24", + }, + }, + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-example-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.1.0/24", + }, + }, + Tunnels: []GatewayTunnel{ + { + Name: "example-tunnel-1", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: "public-ip-1", + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: "pskpsksk", + }, + }, + }, + }, + }, + }, } + got, err := json.Marshal(&r) require.NoError(t, err) assert.Equal(t, gatewayBaseURL, r.RequestURL()) diff --git a/upcloud/service/fixtures/gateway.yaml b/upcloud/service/fixtures/gatewaynat.yaml similarity index 100% rename from upcloud/service/fixtures/gateway.yaml rename to upcloud/service/fixtures/gatewaynat.yaml diff --git a/upcloud/service/fixtures/gatewayvpn.yaml b/upcloud/service/fixtures/gatewayvpn.yaml new file mode 100644 index 00000000..6a19d017 --- /dev/null +++ b/upcloud/service/fixtures/gatewayvpn.yaml @@ -0,0 +1,194 @@ +--- +version: 1 +interactions: +- request: + body: '{"router":{"name":"test-router-vpn"}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router + method: POST + response: + body: | + { + "router" : { + "attached_network_gateways" : [], + "attached_networks" : { + "network" : [] + }, + "labels" : [], + "name" : "test-router-vpn", + "static_routes" : [], + "type" : "normal", + "uuid" : "04b6f559-6f7c-462e-ac52-1351a0b68f4b" + } + } + headers: + Content-Length: + - "290" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Wed, 06 Mar 2024 12:36:05 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/plans + method: GET + response: + body: '[{"name":"advanced","per_gateway_bandwidth_mbps":1000,"per_gateway_max_connections":100000,"server_number":2,"supported_features":["nat","vpn"],"vpn_tunnel_amount":10},{"name":"development","per_gateway_bandwidth_mbps":500,"per_gateway_max_connections":10000,"server_number":1,"supported_features":["nat"],"vpn_tunnel_amount":0},{"name":"production","per_gateway_bandwidth_mbps":1000,"per_gateway_max_connections":50000,"server_number":2,"supported_features":["nat","vpn"],"vpn_tunnel_amount":2},{"name":"standard","per_gateway_bandwidth_mbps":500,"per_gateway_max_connections":20000,"server_number":2,"supported_features":["nat"],"vpn_tunnel_amount":0}]' + headers: + Content-Length: + - "654" + Content-Type: + - application/json + Date: + - Wed, 06 Mar 2024 12:36:06 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"name":"test-vpn","zone":"pl-waw1","features":["vpn"],"routers":[{"uuid":"04b6f559-6f7c-462e-ac52-1351a0b68f4b"}],"configured_status":"started","plan":"advanced","addresses":[{"name":"my-public-ip"}],"connections":[{"name":"example-connection","type":"ipsec","local_routes":[{"name":"local-route","static_network":"10.0.0.0/24","type":"static"}],"remote_routes":[{"name":"remote-route","static_network":"10.0.1.0/24","type":"static"}],"tunnels":[{"name":"example-tunnel","local_address":{"name":"my-public-ip"},"remote_address":{"address":"100.10.0.111"},"ipsec":{"authentication":{"authentication":"psk","psk":"key123wouldkeepitthatwaybuthastobelonger"}}}]}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway + method: POST + response: + body: '{"addresses":[{"name":"my-public-ip"}],"configured_status":"started","connections":[{"created_at":"2024-03-06T12:36:06.774284Z","local_routes":[{"name":"local-route","static_network":"10.0.0.0/24","type":"static"}],"name":"example-connection","remote_routes":[{"name":"remote-route","static_network":"10.0.1.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-06T12:36:06.774284Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-public-ip"},"name":"example-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-06T12:36:06.774284Z"}],"type":"ipsec","updated_at":"2024-03-06T12:36:06.774284Z"}],"created_at":"2024-03-06T12:36:06.774284Z","features":["vpn"],"labels":[],"name":"test-vpn","operational_state":"pending","plan":"advanced","routers":[{"created_at":"2024-03-06T12:36:06.774284Z","uuid":"04b6f559-6f7c-462e-ac52-1351a0b68f4b"}],"updated_at":"2024-03-06T12:36:06.774284Z","uuid":"10807184-012f-4acd-847f-7b13f268b0ef","zone":"pl-waw1"}' + headers: + Content-Length: + - "1484" + Content-Type: + - application/json + Date: + - Wed, 06 Mar 2024 12:36:07 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: '{"name":"updated"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10807184-012f-4acd-847f-7b13f268b0ef + method: PATCH + response: + body: '{"addresses":[{"name":"my-public-ip"}],"configured_status":"started","connections":[{"created_at":"2024-03-06T12:36:06.774284Z","local_routes":[{"name":"local-route","static_network":"10.0.0.0/24","type":"static"}],"name":"example-connection","remote_routes":[{"name":"remote-route","static_network":"10.0.1.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-06T12:36:06.774284Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-public-ip"},"name":"example-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-06T12:36:07.372248Z"}],"type":"ipsec","updated_at":"2024-03-06T12:36:07.372248Z"}],"created_at":"2024-03-06T12:36:06.774284Z","features":["vpn"],"labels":[],"name":"updated","operational_state":"pending","plan":"advanced","routers":[{"created_at":"2024-03-06T12:36:06.774284Z","uuid":"04b6f559-6f7c-462e-ac52-1351a0b68f4b"}],"updated_at":"2024-03-06T12:36:07.372248Z","uuid":"10807184-012f-4acd-847f-7b13f268b0ef","zone":"pl-waw1"}' + headers: + Content-Length: + - "1483" + Content-Type: + - application/json + Date: + - Wed, 06 Mar 2024 12:36:07 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"connections":[{"name":"example-connection2","type":"ipsec","local_routes":[{"name":"local-route2","static_network":"11.0.0.0/24","type":"static"}],"remote_routes":[{"name":"remote-route2","static_network":"11.0.1.0/24","type":"static"}],"tunnels":[{"name":"example-tunnel2","local_address":{"name":"my-public-ip"},"remote_address":{"address":"200.10.0.111"},"ipsec":{"authentication":{"authentication":"psk","psk":"key123wouldkeepitthatwaybuthastobelonger"}}}]}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10807184-012f-4acd-847f-7b13f268b0ef + method: PATCH + response: + body: '{"addresses":[{"name":"my-public-ip"}],"configured_status":"started","connections":[{"created_at":"2024-03-06T12:36:07.507221Z","local_routes":[{"name":"local-route2","static_network":"11.0.0.0/24","type":"static"}],"name":"example-connection2","remote_routes":[{"name":"remote-route2","static_network":"11.0.1.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-06T12:36:07.507221Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-public-ip"},"name":"example-tunnel2","operational_state":"uninitialized","remote_address":{"address":"200.10.0.111"},"updated_at":"2024-03-06T12:36:07.507221Z"}],"type":"ipsec","updated_at":"2024-03-06T12:36:07.507221Z"}],"created_at":"2024-03-06T12:36:06.774284Z","features":["vpn"],"labels":[],"name":"updated","operational_state":"pending","plan":"advanced","routers":[{"created_at":"2024-03-06T12:36:06.774284Z","uuid":"04b6f559-6f7c-462e-ac52-1351a0b68f4b"}],"updated_at":"2024-03-06T12:36:07.507221Z","uuid":"10807184-012f-4acd-847f-7b13f268b0ef","zone":"pl-waw1"}' + headers: + Content-Length: + - "1487" + Content-Type: + - application/json + Date: + - Wed, 06 Mar 2024 12:36:07 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10807184-012f-4acd-847f-7b13f268b0ef + method: DELETE + response: + body: "" + headers: + Date: + - Wed, 06 Mar 2024 12:36:08 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router/04b6f559-6f7c-462e-ac52-1351a0b68f4b + method: DELETE + response: + body: "" + headers: + Date: + - Wed, 06 Mar 2024 12:36:13 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" diff --git a/upcloud/service/fixtures/gatewayvpnconnections.yaml b/upcloud/service/fixtures/gatewayvpnconnections.yaml new file mode 100644 index 00000000..44f299b1 --- /dev/null +++ b/upcloud/service/fixtures/gatewayvpnconnections.yaml @@ -0,0 +1,242 @@ +--- +version: 1 +interactions: +- request: + body: '{"router":{"name":"test-router-vpn-conn"}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router + method: POST + response: + body: | + { + "router" : { + "attached_network_gateways" : [], + "attached_networks" : { + "network" : [] + }, + "labels" : [], + "name" : "test-router-vpn-conn", + "static_routes" : [], + "type" : "normal", + "uuid" : "04437f95-9d06-40e9-ad8a-378976912ea7" + } + } + headers: + Content-Length: + - "295" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 07 Mar 2024 13:40:59 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: '{"name":"test-vpn-conn","zone":"pl-waw1","features":["vpn"],"routers":[{"uuid":"04437f95-9d06-40e9-ad8a-378976912ea7"}],"configured_status":"started","plan":"advanced","addresses":[{"name":"my-ip-address"}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway + method: POST + response: + body: '{"addresses":[{"name":"my-ip-address"}],"configured_status":"started","connections":[],"created_at":"2024-03-07T13:41:00.42508Z","features":["vpn"],"labels":[],"name":"test-vpn-conn","operational_state":"pending","plan":"advanced","routers":[{"created_at":"2024-03-07T13:41:00.42508Z","uuid":"04437f95-9d06-40e9-ad8a-378976912ea7"}],"updated_at":"2024-03-07T13:41:00.42508Z","uuid":"10cb0bf6-2bb0-4043-8a1a-a98347261fd3","zone":"pl-waw1"}' + headers: + Content-Length: + - "438" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:41:00 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: '{"name":"added-connection","type":"ipsec","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"name":"added-tunnel","local_address":{"name":"my-ip-address"},"remote_address":{"address":"100.10.0.111"},"ipsec":{"authentication":{"authentication":"psk","psk":"psk1234567890psk1234567890"}}}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3/connections/ + method: POST + response: + body: '{"created_at":"2024-03-07T13:41:00.665782Z","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"name":"added-connection","remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-07T13:41:00.665782Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:41:00.665782Z"}],"type":"ipsec","updated_at":"2024-03-07T13:41:00.665782Z"}' + headers: + Content-Length: + - "1046" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:41:01 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3/connections + method: GET + response: + body: '[{"created_at":"2024-03-07T13:41:00.665782Z","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"name":"added-connection","remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-07T13:41:00.665782Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:41:00.665782Z"}],"type":"ipsec","updated_at":"2024-03-07T13:41:00.665782Z"}]' + headers: + Content-Length: + - "1048" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:41:01 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3/connections/added-connection + method: GET + response: + body: '{"created_at":"2024-03-07T13:41:00.665782Z","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"name":"added-connection","remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-07T13:41:00.665782Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:41:00.665782Z"}],"type":"ipsec","updated_at":"2024-03-07T13:41:00.665782Z"}' + headers: + Content-Length: + - "1046" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:41:01 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"remote_routes":[{"name":"remote-route-updated","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"name":"added-tunnel-updated","local_address":{"name":"my-ip-address"},"remote_address":{"address":"100.10.0.111"},"ipsec":{"authentication":{"authentication":"psk","psk":"psk1234567890psk1234567890"}}}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3/connections/added-connection + method: PATCH + response: + body: '{"created_at":"2024-03-07T13:41:00.665782Z","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"name":"added-connection","remote_routes":[{"name":"remote-route-updated","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[{"created_at":"2024-03-07T13:41:01.603394Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel-updated","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:41:01.603394Z"}],"type":"ipsec","updated_at":"2024-03-07T13:41:01.603394Z"}' + headers: + Content-Length: + - "1062" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:41:02 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3/connections/added-connection + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:41:02 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/10cb0bf6-2bb0-4043-8a1a-a98347261fd3 + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:41:02 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router/04437f95-9d06-40e9-ad8a-378976912ea7 + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:41:07 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" diff --git a/upcloud/service/fixtures/gatewayvpnconnectiontunnels.yaml b/upcloud/service/fixtures/gatewayvpnconnectiontunnels.yaml new file mode 100644 index 00000000..525d13bc --- /dev/null +++ b/upcloud/service/fixtures/gatewayvpnconnectiontunnels.yaml @@ -0,0 +1,268 @@ +--- +version: 1 +interactions: +- request: + body: '{"router":{"name":"test-router-vpn-conn-tunnels"}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router + method: POST + response: + body: | + { + "router" : { + "attached_network_gateways" : [], + "attached_networks" : { + "network" : [] + }, + "labels" : [], + "name" : "test-router-vpn-conn-tunnels", + "static_routes" : [], + "type" : "normal", + "uuid" : "04101658-36d5-499c-b391-82b61bd291e4" + } + } + headers: + Content-Length: + - "303" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 07 Mar 2024 13:39:27 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: '{"name":"test-vpn-conn-tunnels","zone":"pl-waw1","features":["vpn"],"routers":[{"uuid":"04101658-36d5-499c-b391-82b61bd291e4"}],"configured_status":"started","plan":"advanced","addresses":[{"name":"my-ip-address"}],"connections":[{"name":"example-conn","type":"ipsec","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}]}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway + method: POST + response: + body: '{"addresses":[{"name":"my-ip-address"}],"configured_status":"started","connections":[{"created_at":"2024-03-07T13:39:28.283434Z","local_routes":[{"name":"local-route","static_network":"10.0.1.0/24","type":"static"}],"name":"example-conn","remote_routes":[{"name":"remote-route","static_network":"10.0.2.0/24","type":"static"}],"tunnels":[],"type":"ipsec","updated_at":"2024-03-07T13:39:28.283434Z"}],"created_at":"2024-03-07T13:39:28.283434Z","features":["vpn"],"labels":[],"name":"test-vpn-conn-tunnels","operational_state":"pending","plan":"advanced","routers":[{"created_at":"2024-03-07T13:39:28.283434Z","uuid":"04101658-36d5-499c-b391-82b61bd291e4"}],"updated_at":"2024-03-07T13:39:28.283434Z","uuid":"104fc0ec-d28f-418e-9324-a5724269800e","zone":"pl-waw1"}' + headers: + Content-Length: + - "762" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:28 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels + method: GET + response: + body: '[]' + headers: + Content-Length: + - "2" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:28 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"name":"added-tunnel","local_address":{"name":"my-ip-address"},"remote_address":{"address":"100.10.0.111"},"ipsec":{"authentication":{"authentication":"psk","psk":"psk1234567890psk1234567890"}}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels + method: POST + response: + body: '{"created_at":"2024-03-07T13:39:29.123663Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:39:29.123663Z"}' + headers: + Content-Length: + - "729" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:29 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 201 Created + code: 201 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels + method: GET + response: + body: '[{"created_at":"2024-03-07T13:39:29.123663Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:39:29.123663Z"}]' + headers: + Content-Length: + - "731" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:29 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels/added-tunnel + method: GET + response: + body: '{"created_at":"2024-03-07T13:39:29.123663Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"added-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.111"},"updated_at":"2024-03-07T13:39:29.123663Z"}' + headers: + Content-Length: + - "729" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:29 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"name":"updated-tunnel","remote_address":{"address":"100.10.0.222"},"ipsec":{"authentication":{"psk":"updatedsuperduperpsk1234566778"}}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels/added-tunnel + method: PATCH + response: + body: '{"created_at":"2024-03-07T13:39:29.123663Z","ipsec":{"authentication":{"authentication":"psk"},"child_rekey_time":1440,"dpd_delay":30,"dpd_timeout":120,"ike_lifetime":86400,"phase1_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase1_dh_group_numbers":[14,16,18,19,20,21],"phase1_integrity_algorithms":["sha256","sha384","sha512"],"phase2_algorithms":["aes128","aes256","aes128gcm128","aes256gcm128"],"phase2_dh_group_numbers":[14,16,18,19,20,21],"phase2_integrity_algorithms":["sha256","sha384","sha512"],"rekey_time":14400},"local_address":{"name":"my-ip-address"},"name":"updated-tunnel","operational_state":"uninitialized","remote_address":{"address":"100.10.0.222"},"updated_at":"2024-03-07T13:39:30.00003Z"}' + headers: + Content-Length: + - "730" + Content-Type: + - application/json + Date: + - Thu, 07 Mar 2024 13:39:30 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e/connections/example-conn/tunnels/updated-tunnel + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:39:30 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/gateway/104fc0ec-d28f-418e-9324-a5724269800e + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:39:30 GMT + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.0.0 + url: https://api.upcloud.com/1.3/router/04101658-36d5-499c-b391-82b61bd291e4 + method: DELETE + response: + body: "" + headers: + Date: + - Thu, 07 Mar 2024 13:39:35 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 204 No Content + code: 204 + duration: "" diff --git a/upcloud/service/gateway.go b/upcloud/service/gateway.go index 4672fa46..980c04f1 100644 --- a/upcloud/service/gateway.go +++ b/upcloud/service/gateway.go @@ -8,11 +8,31 @@ import ( ) type Gateway interface { + GetGatewayPlans(ctx context.Context) ([]upcloud.GatewayPlan, error) GetGateways(ctx context.Context, f ...request.QueryFilter) ([]upcloud.Gateway, error) GetGateway(ctx context.Context, r *request.GetGatewayRequest) (*upcloud.Gateway, error) CreateGateway(ctx context.Context, r *request.CreateGatewayRequest) (*upcloud.Gateway, error) ModifyGateway(ctx context.Context, r *request.ModifyGatewayRequest) (*upcloud.Gateway, error) DeleteGateway(ctx context.Context, r *request.DeleteGatewayRequest) error + + GetGatewayConnections(ctx context.Context, r *request.GetGatewayConnectionsRequest) ([]upcloud.GatewayConnection, error) + GetGatewayConnection(ctx context.Context, r *request.GetGatewayConnectionRequest) (*upcloud.GatewayConnection, error) + CreateGatewayConnection(ctx context.Context, r *request.CreateGatewayConnectionRequest) (*upcloud.GatewayConnection, error) + ModifyGatewayConnection(ctx context.Context, r *request.ModifyGatewayConnectionRequest) (*upcloud.GatewayConnection, error) + DeleteGatewayConnection(ctx context.Context, r *request.DeleteGatewayConnectionRequest) error + + GetGatewayConnectionTunnels(ctx context.Context, r *request.GetGatewayConnectionTunnelsRequest) ([]upcloud.GatewayTunnel, error) + GetGatewayConnectionTunnel(ctx context.Context, r *request.GetGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) + CreateGatewayConnectionTunnel(ctx context.Context, r *request.CreateGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) + // ModifyGatewayConnectionTunnel(ctx context.Context, r *request.ModifyGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) + DeleteGatewayConnectionTunnel(ctx context.Context, r *request.DeleteGatewayConnectionTunnelRequest) error +} + +// GetGatewayPlans retrieves a list of all available plans for network gateway service +func (s *Service) GetGatewayPlans(ctx context.Context) ([]upcloud.GatewayPlan, error) { + r := request.GetGatewayPlansRequest{} + p := []upcloud.GatewayPlan{} + return p, s.get(ctx, r.RequestURL(), &p) } // GetGateways retrieves a list of network gateways within an account. @@ -44,3 +64,60 @@ func (s *Service) ModifyGateway(ctx context.Context, r *request.ModifyGatewayReq func (s *Service) DeleteGateway(ctx context.Context, r *request.DeleteGatewayRequest) error { return s.delete(ctx, r) } + +// GetGatewayConnections retrieves a list of specific gateway connections +func (s *Service) GetGatewayConnections(ctx context.Context, r *request.GetGatewayConnectionsRequest) ([]upcloud.GatewayConnection, error) { + p := []upcloud.GatewayConnection{} + return p, s.get(ctx, r.RequestURL(), &p) +} + +// GetGatewayConnection retrieves details of a specific network gateway connection +func (s *Service) GetGatewayConnection(ctx context.Context, r *request.GetGatewayConnectionRequest) (*upcloud.GatewayConnection, error) { + p := upcloud.GatewayConnection{} + return &p, s.get(ctx, r.RequestURL(), &p) +} + +// CreateGatewayConnection creates a new connection for a specific gateway +func (s *Service) CreateGatewayConnection(ctx context.Context, r *request.CreateGatewayConnectionRequest) (*upcloud.GatewayConnection, error) { + p := upcloud.GatewayConnection{} + return &p, s.create(ctx, r, &p) +} + +func (s *Service) ModifyGatewayConnection(ctx context.Context, r *request.ModifyGatewayConnectionRequest) (*upcloud.GatewayConnection, error) { + p := upcloud.GatewayConnection{} + return &p, s.modify(ctx, r, &p) +} + +// DeleteGatewayConnection deletes a specific connection of a network gateway +func (s *Service) DeleteGatewayConnection(ctx context.Context, r *request.DeleteGatewayConnectionRequest) error { + return s.delete(ctx, r) +} + +// GetGatewayConnectionTunnels retrieves tunnels for specific connection of specific gateway +func (s *Service) GetGatewayConnectionTunnels(ctx context.Context, r *request.GetGatewayConnectionTunnelsRequest) ([]upcloud.GatewayTunnel, error) { + p := []upcloud.GatewayTunnel{} + return p, s.get(ctx, r.RequestURL(), &p) +} + +// GetGatewayConnectionTunnel retrieves a single tunnel details for specific connection of specific gateway +func (s *Service) GetGatewayConnectionTunnel(ctx context.Context, r *request.GetGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) { + p := upcloud.GatewayTunnel{} + return &p, s.get(ctx, r.RequestURL(), &p) +} + +// CreateGatewayConnectionTunnel creates a tunnel for specific connection of specific gateway +func (s *Service) CreateGatewayConnectionTunnel(ctx context.Context, r *request.CreateGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) { + p := upcloud.GatewayTunnel{} + return &p, s.create(ctx, r, &p) +} + +// ModifyGatewayConnectionTunnel modifies a single tunnel for specific connection of specific gateway +func (s *Service) ModifyGatewayConnectionTunnel(ctx context.Context, r *request.ModifyGatewayConnectionTunnelRequest) (*upcloud.GatewayTunnel, error) { + p := upcloud.GatewayTunnel{} + return &p, s.modify(ctx, r, &p) +} + +// DeleteGatewayConnectionTunnel deletes a tunnel for specific connection of specific gateway +func (s *Service) DeleteGatewayConnectionTunnel(ctx context.Context, r *request.DeleteGatewayConnectionTunnelRequest) error { + return s.delete(ctx, r) +} diff --git a/upcloud/service/gateway_test.go b/upcloud/service/gateway_test.go index 5268a655..f537b731 100644 --- a/upcloud/service/gateway_test.go +++ b/upcloud/service/gateway_test.go @@ -10,15 +10,73 @@ import ( "time" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" ) -func TestGateway(t *testing.T) { +func TestGatewayPlans(t *testing.T) { t.Parallel() - record(t, "gateway", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { + plansResponse := ` + [ + { + "name": "advanced", + "per_gateway_bandwidth_mbps": 10000, + "per_gateway_max_connections": 100000, + "server_number": 2, + "supported_features": [ + "nat", + "vpn" + ], + "vpn_tunnel_amount": 10 + }, + { + "name": "production", + "per_gateway_bandwidth_mbps": 1000, + "per_gateway_max_connections": 50000, + "server_number": 2, + "supported_features": [ + "nat", + "vpn" + ], + "vpn_tunnel_amount": 2 + } + ] + ` + + srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, fmt.Sprintf("/%s/gateway/plans", client.APIVersion), r.URL.Path) + _, _ = fmt.Fprint(w, plansResponse) + })) + defer srv.Close() + + res, err := svc.GetGatewayPlans(context.Background()) + assert.NoError(t, err) + assert.Len(t, res, 2) + + firstPlan := res[0] + secondPlan := res[1] + + assert.Equal(t, "advanced", firstPlan.Name) + assert.Equal(t, 10000, firstPlan.PerGatewayBandwidthMbps) + assert.Equal(t, 100000, firstPlan.PerGatewayMaxConnections) + assert.Equal(t, 2, firstPlan.ServerNumber) + assert.Len(t, firstPlan.SupportedFeatures, 2) + assert.Equal(t, upcloud.GatewayFeatureNAT, firstPlan.SupportedFeatures[0]) + assert.Equal(t, upcloud.GatewayFeatureVPN, firstPlan.SupportedFeatures[1]) + assert.Equal(t, 10, firstPlan.VPNTunnelAmount) + + // Just check the name, no need to check all the properties again + assert.Equal(t, "production", secondPlan.Name) +} + +func TestNATGateway(t *testing.T) { + t.Parallel() + + record(t, "gatewaynat", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { router, err := svc.CreateRouter(ctx, &request.CreateRouterRequest{ Name: "test-router", }) @@ -80,6 +138,518 @@ func TestGateway(t *testing.T) { }) } +func TestVPNGateway(t *testing.T) { + t.Parallel() + + record(t, "gatewayvpn", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { + router, err := svc.CreateRouter(ctx, &request.CreateRouterRequest{Name: "test-router-vpn"}) + if !assert.NoError(t, err) { + return + } + defer func() { + err = svc.DeleteRouter(ctx, &request.DeleteRouterRequest{UUID: router.UUID}) + assert.NoError(t, err) + }() + + plans, err := svc.GetGatewayPlans(ctx) + if !assert.NoError(t, err) { + return + } + if !assert.GreaterOrEqual(t, len(plans), 2, "plans response has less than 2 plans") { + return + } + + psk := "key123wouldkeepitthatwaybuthastobelonger" + gw, err := svc.CreateGateway(ctx, &request.CreateGatewayRequest{ + Name: "test-vpn", + Zone: "pl-waw1", + Routers: []request.GatewayRouter{ + { + UUID: router.UUID, + }, + }, + Plan: plans[0].Name, + ConfiguredStatus: upcloud.GatewayConfiguredStatusStarted, + Features: []upcloud.GatewayFeature{ + upcloud.GatewayFeatureVPN, + }, + Addresses: []upcloud.GatewayAddress{ + { + Name: "my-public-ip", + }, + }, + Connections: []request.GatewayConnection{ + { + Name: "example-connection", + Type: upcloud.GatewayConnectionTypeIPSec, + LocalRoutes: []upcloud.GatewayRoute{ + { + Name: "local-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.0.0/24", + }, + }, + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.1.0/24", + }, + }, + Tunnels: []request.GatewayTunnel{ + { + Name: "example-tunnel", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: "my-public-ip", + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: psk, + }, + }, + }, + }, + }, + }, + }) + + if !assert.NoError(t, err) { + return + } + + defer func() { + err = svc.DeleteGateway(ctx, &request.DeleteGatewayRequest{UUID: gw.UUID}) + assert.NoError(t, err) + + err = waitGatewayToDelete(ctx, rec, svc, gw.UUID) + assert.NoError(t, err) + }() + + // Check plan + assert.NotEmpty(t, gw.Plan) + + // Check addresses + assert.Len(t, gw.Addresses, 1) + assert.Equal(t, "my-public-ip", gw.Addresses[0].Name) + + // Check connections + assert.Len(t, gw.Connections, 1) + + connection := gw.Connections[0] + assert.Equal(t, "example-connection", connection.Name) + assert.Equal(t, upcloud.GatewayConnectionTypeIPSec, connection.Type) + assert.Len(t, connection.LocalRoutes, 1) + assert.Len(t, connection.RemoteRoutes, 1) + assert.Len(t, connection.Tunnels, 1) + + // Check connection local routes + localRoute := connection.LocalRoutes[0] + assert.Equal(t, "local-route", localRoute.Name) + assert.Equal(t, upcloud.GatewayRouteTypeStatic, localRoute.Type) + assert.Equal(t, "10.0.0.0/24", localRoute.StaticNetwork) + + // Check connection remote routes + remoteRoute := connection.RemoteRoutes[0] + assert.Equal(t, "remote-route", remoteRoute.Name) + assert.Equal(t, upcloud.GatewayRouteTypeStatic, remoteRoute.Type) + assert.Equal(t, "10.0.1.0/24", remoteRoute.StaticNetwork) + + // Check connection tunnel + tunnel := connection.Tunnels[0] + assert.Equal(t, "example-tunnel", tunnel.Name) + assert.Equal(t, "my-public-ip", tunnel.LocalAddress.Name) + assert.Equal(t, "100.10.0.111", tunnel.RemoteAddress.Address) + assert.Equal(t, upcloud.GatewayTunnelIPSecAuthTypePSK, tunnel.IPSec.Authentication.Authentication) + + // Now check that we can modify just name, without affecting other fields + gw, err = svc.ModifyGateway(ctx, &request.ModifyGatewayRequest{ + UUID: gw.UUID, + Name: "updated", + }) + + assert.NoError(t, err) + assert.Equal(t, "updated", gw.Name) + assert.Len(t, gw.Addresses, 1) + assert.Len(t, gw.Connections, 1) + assert.Len(t, gw.Connections[0].Tunnels, 1) + + // Now let's see if we can modify other fields + gw, err = svc.ModifyGateway(ctx, &request.ModifyGatewayRequest{ + UUID: gw.UUID, + Connections: []request.GatewayConnection{ + { + Name: "example-connection2", + Type: upcloud.GatewayConnectionTypeIPSec, + LocalRoutes: []upcloud.GatewayRoute{ + { + Name: "local-route2", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "11.0.0.0/24", + }, + }, + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-route2", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "11.0.1.0/24", + }, + }, + Tunnels: []request.GatewayTunnel{ + { + Name: "example-tunnel2", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: "my-public-ip", + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "200.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: psk, + }, + }, + }, + }, + }, + }, + }) + + // Check connections + assert.Len(t, gw.Connections, 1) + + connection = gw.Connections[0] + assert.Equal(t, "example-connection2", connection.Name) + assert.Equal(t, upcloud.GatewayConnectionTypeIPSec, connection.Type) + assert.Len(t, connection.LocalRoutes, 1) + assert.Len(t, connection.RemoteRoutes, 1) + assert.Len(t, connection.Tunnels, 1) + + // Check connection local routes + localRoute = connection.LocalRoutes[0] + assert.Equal(t, "local-route2", localRoute.Name) + assert.Equal(t, upcloud.GatewayRouteTypeStatic, localRoute.Type) + assert.Equal(t, "11.0.0.0/24", localRoute.StaticNetwork) + + // Check connection remote routes + remoteRoute = connection.RemoteRoutes[0] + assert.Equal(t, "remote-route2", remoteRoute.Name) + assert.Equal(t, upcloud.GatewayRouteTypeStatic, remoteRoute.Type) + assert.Equal(t, "11.0.1.0/24", remoteRoute.StaticNetwork) + + // Check connection tunnel + tunnel = connection.Tunnels[0] + assert.Equal(t, "example-tunnel2", tunnel.Name) + assert.Equal(t, "my-public-ip", tunnel.LocalAddress.Name) + assert.Equal(t, "200.10.0.111", tunnel.RemoteAddress.Address) + assert.Equal(t, upcloud.GatewayTunnelIPSecAuthTypePSK, tunnel.IPSec.Authentication.Authentication) + }) +} + +func TestVPNGatewayConnections(t *testing.T) { + t.Parallel() + + record(t, "gatewayvpnconnections", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { + router, err := svc.CreateRouter(ctx, &request.CreateRouterRequest{Name: "test-router-vpn-conn"}) + if !assert.NoError(t, err) { + return + } + defer func() { + err = svc.DeleteRouter(ctx, &request.DeleteRouterRequest{UUID: router.UUID}) + assert.NoError(t, err) + }() + + gw, err := svc.CreateGateway(ctx, &request.CreateGatewayRequest{ + Name: "test-vpn-conn", + Zone: "pl-waw1", + Routers: []request.GatewayRouter{ + { + UUID: router.UUID, + }, + }, + Plan: "advanced", + Features: []upcloud.GatewayFeature{upcloud.GatewayFeatureVPN}, + ConfiguredStatus: upcloud.GatewayConfiguredStatusStarted, + Addresses: []upcloud.GatewayAddress{ + { + Name: "my-ip-address", + }, + }, + }) + + if !assert.NoError(t, err) { + return + } + + defer func() { + err = svc.DeleteGateway(ctx, &request.DeleteGatewayRequest{UUID: gw.UUID}) + assert.NoError(t, err) + + err = waitGatewayToDelete(ctx, rec, svc, gw.UUID) + assert.NoError(t, err) + }() + + // Now try to create a connection + conn, err := svc.CreateGatewayConnection(ctx, &request.CreateGatewayConnectionRequest{ + ServiceUUID: gw.UUID, + Connection: request.GatewayConnection{ + Name: "added-connection", + Type: upcloud.GatewayConnectionTypeIPSec, + LocalRoutes: []upcloud.GatewayRoute{ + { + Name: "local-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.1.0/24", + }, + }, + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.2.0/24", + }, + }, + Tunnels: []request.GatewayTunnel{ + { + Name: "added-tunnel", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: gw.Addresses[0].Name, + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: "psk1234567890psk1234567890", + }, + }, + }, + }, + }, + }) + assert.NoError(t, err) + assert.Equal(t, "added-connection", conn.Name) + assert.Len(t, conn.LocalRoutes, 1) + assert.Len(t, conn.RemoteRoutes, 1) + assert.Len(t, conn.Tunnels, 1) + assert.Equal(t, "local-route", conn.LocalRoutes[0].Name) + assert.Equal(t, "remote-route", conn.RemoteRoutes[0].Name) + assert.Equal(t, "added-tunnel", conn.Tunnels[0].Name) + + // Now check if listing and getting the details work + connList, err := svc.GetGatewayConnections(ctx, &request.GetGatewayConnectionsRequest{ServiceUUID: gw.UUID}) + assert.NoError(t, err) + assert.Len(t, connList, 1) + + connDetails, err := svc.GetGatewayConnection(ctx, &request.GetGatewayConnectionRequest{ + ServiceUUID: gw.UUID, + Name: conn.Name, + }) + assert.NoError(t, err) + assert.Equal(t, conn.Name, connDetails.Name) + + // Test modifying a connection + conn, err = svc.ModifyGatewayConnection(ctx, &request.ModifyGatewayConnectionRequest{ + ServiceUUID: gw.UUID, + Name: conn.Name, + Connection: request.ModifyGatewayConnection{ + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-route-updated", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.2.0/24", + }, + }, + Tunnels: []request.GatewayTunnel{ + { + Name: "added-tunnel-updated", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: gw.Addresses[0].Name, + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: "psk1234567890psk1234567890", + }, + }, + }, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "local-route", conn.LocalRoutes[0].Name) // LocalRoutes should stay the same as before, they weren't modified + assert.Equal(t, "remote-route-updated", conn.RemoteRoutes[0].Name) + assert.Equal(t, "added-tunnel-updated", conn.Tunnels[0].Name) + + // Now delete the connection + err = svc.DeleteGatewayConnection(ctx, &request.DeleteGatewayConnectionRequest{ + ServiceUUID: gw.UUID, + Name: conn.Name, + }) + assert.NoError(t, err) + }) +} + +func TestVPNGatewayConnectionTunnels(t *testing.T) { + t.Parallel() + + record(t, "gatewayvpnconnectiontunnels", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { + router, err := svc.CreateRouter(ctx, &request.CreateRouterRequest{Name: "test-router-vpn-conn-tunnels"}) + if !assert.NoError(t, err) { + return + } + defer func() { + err = svc.DeleteRouter(ctx, &request.DeleteRouterRequest{UUID: router.UUID}) + assert.NoError(t, err) + }() + + connName := "example-conn" + tunnelName := "added-tunnel" + + gw, err := svc.CreateGateway(ctx, &request.CreateGatewayRequest{ + Name: "test-vpn-conn-tunnels", + Zone: "pl-waw1", + Routers: []request.GatewayRouter{ + { + UUID: router.UUID, + }, + }, + Plan: "advanced", + Features: []upcloud.GatewayFeature{upcloud.GatewayFeatureVPN}, + ConfiguredStatus: upcloud.GatewayConfiguredStatusStarted, + Addresses: []upcloud.GatewayAddress{ + { + Name: "my-ip-address", + }, + }, + Connections: []request.GatewayConnection{ + { + Name: connName, + Type: upcloud.GatewayConnectionTypeIPSec, + LocalRoutes: []upcloud.GatewayRoute{ + { + Name: "local-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.1.0/24", + }, + }, + RemoteRoutes: []upcloud.GatewayRoute{ + { + Name: "remote-route", + Type: upcloud.GatewayRouteTypeStatic, + StaticNetwork: "10.0.2.0/24", + }, + }, + }, + }, + }) + + if !assert.NoError(t, err) { + return + } + + defer func() { + err = svc.DeleteGateway(ctx, &request.DeleteGatewayRequest{UUID: gw.UUID}) + assert.NoError(t, err) + + err = waitGatewayToDelete(ctx, rec, svc, gw.UUID) + assert.NoError(t, err) + }() + + // First get tunnels to see if it handles empty response + tunnels, err := svc.GetGatewayConnectionTunnels(ctx, &request.GetGatewayConnectionTunnelsRequest{ + ServiceUUID: gw.UUID, + ConnectionName: connName, + }) + + assert.NoError(t, err) + assert.Len(t, tunnels, 0) + + // Now let's create a tunnel + tunnel, err := svc.CreateGatewayConnectionTunnel(ctx, &request.CreateGatewayConnectionTunnelRequest{ + ServiceUUID: gw.UUID, + ConnectionName: gw.Connections[0].Name, + Tunnel: request.GatewayTunnel{ + Name: "added-tunnel", + LocalAddress: upcloud.GatewayTunnelLocalAddress{ + Name: gw.Addresses[0].Name, + }, + RemoteAddress: upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.111", + }, + IPSec: upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + Authentication: upcloud.GatewayTunnelIPSecAuthTypePSK, + PSK: "psk1234567890psk1234567890", + }, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "added-tunnel", tunnel.Name) + assert.Equal(t, gw.Addresses[0].Name, tunnel.LocalAddress.Name) + assert.Equal(t, "100.10.0.111", tunnel.RemoteAddress.Address) + assert.Equal(t, upcloud.GatewayTunnelIPSecAuthTypePSK, tunnel.IPSec.Authentication.Authentication) + + // Check if listing tunnels work + tunnels, err = svc.GetGatewayConnectionTunnels(ctx, &request.GetGatewayConnectionTunnelsRequest{ + ServiceUUID: gw.UUID, + ConnectionName: connName, + }) + assert.NoError(t, err) + assert.Len(t, tunnels, 1) + + // Check if getting details work + tunnel, err = svc.GetGatewayConnectionTunnel(ctx, &request.GetGatewayConnectionTunnelRequest{ + ServiceUUID: gw.UUID, + ConnectionName: connName, + Name: tunnelName, + }) + assert.NoError(t, err) + assert.Equal(t, "100.10.0.111", tunnel.RemoteAddress.Address) + + // Try modifying tunnel + updatedTunnelName := "updated-tunnel" + tunnel, err = svc.ModifyGatewayConnectionTunnel(ctx, &request.ModifyGatewayConnectionTunnelRequest{ + ServiceUUID: gw.UUID, + ConnectionName: connName, + Name: tunnelName, + Tunnel: request.ModifyGatewayTunnel{ + Name: updatedTunnelName, + RemoteAddress: &upcloud.GatewayTunnelRemoteAddress{ + Address: "100.10.0.222", + }, + IPSec: &upcloud.GatewayTunnelIPSec{ + Authentication: upcloud.GatewayTunnelIPSecAuth{ + PSK: "updatedsuperduperpsk1234566778", + }, + }, + }, + }) + assert.NoError(t, err) + assert.Equal(t, gw.Addresses[0].Name, tunnel.LocalAddress.Name) + assert.Equal(t, "100.10.0.222", tunnel.RemoteAddress.Address) + + // Delete the tunnel + err = svc.DeleteGatewayConnectionTunnel(ctx, &request.DeleteGatewayConnectionTunnelRequest{ + ServiceUUID: gw.UUID, + ConnectionName: connName, + Name: updatedTunnelName, + }) + assert.NoError(t, err) + }) +} + func waitGatewayToStart(ctx context.Context, rec *recorder.Recorder, svc *Service, UUID string) error { if rec.Mode() != recorder.ModeRecording { return nil @@ -128,11 +698,11 @@ func waitGatewayToDelete(ctx context.Context, rec *recorder.Recorder, svc *Servi for { _, err := svc.GetGateway(ctx, &request.GetGatewayRequest{UUID: UUID}) if err != nil { - log.Printf("ERROR: %+v", err) var ucErr *upcloud.Problem if errors.As(err, &ucErr) && ucErr.Status == http.StatusNotFound { return nil } + log.Printf("ERROR: %+v", err) return err } if time.Now().After(waitUntil) { diff --git a/upcloud/service/service.go b/upcloud/service/service.go index de1ed682..0aa1207c 100644 --- a/upcloud/service/service.go +++ b/upcloud/service/service.go @@ -48,6 +48,7 @@ type service interface { Permission Kubernetes ManagedObjectStorage + Gateway } var _ service = (*Service)(nil)