diff --git a/contrib/terraform/upcloud/README.md b/contrib/terraform/upcloud/README.md index 6d35a4268..81472c100 100644 --- a/contrib/terraform/upcloud/README.md +++ b/contrib/terraform/upcloud/README.md @@ -134,10 +134,40 @@ terraform destroy --var-file cluster-settings.tfvars \ * `end_address`: End of address range to allow * `loadbalancer_enabled`: Enable managed load balancer * `loadbalancer_plan`: Plan to use for load balancer *(development|production-small)* +* `loadbalancer_legacy_network`: If the loadbalancer should use the deprecated network field instead of networks blocks. You probably want to have this set to false (default value) * `loadbalancers`: Ports to load balance and which machines to forward to. Key of this object will be used as the name of the load balancer frontends/backends * `port`: Port to load balance. * `target_port`: Port to the backend servers. * `backend_servers`: List of servers that traffic to the port should be forwarded to. +* `router_enable`: If a router should be connected to the private network or not +* `gateways`: Gateways that should be connected to the router, requires router_enable is set to true + * `features`: List of features for the gateway + * `plan`: Plan to use for the gateway + * `connections`: The connections and tunnel to create for the gateway + * `type`: What type of connection + * `local_routes`: Map of local routes for the connection + * `type`: Type of route + * `static_network`: Destination prefix of the route; needs to be a valid IPv4 prefix + * `remote_routes`: Map of local routes for the connection + * `type`: Type of route + * `static_network`: Destination prefix of the route; needs to be a valid IPv4 prefix + * `tunnels`: The tunnels to create for this connection + * `remote_address`: The remote address for the tunnel + * `ipsec_properties`: Set properties of IPSec, if not set, defaults will be used + * `child_rekey_time`: IKE child SA rekey time in seconds + * `dpd_delay`: Delay before sending Dead Peer Detection packets if no traffic is detected, in seconds + * `dpd_timeout`: Timeout period for DPD reply before considering the peer to be dead, in seconds + * `ike_lifetime`: Maximum IKE SA lifetime in seconds() + * `rekey_time`: IKE SA rekey time in seconds + * `phase1_algorithms`: List of Phase 1: Proposal algorithms + * `phase1_dh_group_numbers`: List of Phase 1 Diffie-Hellman group numbers + * `phase1_integrity_algorithms`: List of Phase 1 integrity algorithms + * `phase2_algorithms`: List of Phase 2: Security Association algorithms + * `phase2_dh_group_numbers`: List of Phase 2 Diffie-Hellman group numbers + * `phase2_integrity_algorithms`: List of Phase 2 integrity algorithms +* `gateway_vpn_psks`: Separate variable for providing psks for connection tunnels. Environment variable can be exported in the following format `export TF_VAR_gateway_vpn_psks='{"${gateway-name}-${connecton-name}-tunnel":{psk:"..."}}'` +* `static_routes`: Static routes to apply to the router, requires `router_enable` is set to true +* `network_peerings`: Other UpCloud private networks to peer with, requires `router_enable` is set to true * `server_groups`: Group servers together * `servers`: The servers that should be included in the group. * `anti_affinity_policy`: Defines if a server group is an anti-affinity group. Setting this to "strict" or yes" will result in all servers in the group being placed on separate compute hosts. The value can be "strict", "yes" or "no". "strict" refers to strict policy doesn't allow servers in the same server group to be on the same host. "yes" refers to best-effort policy and tries to put servers on different hosts, but this is not guaranteed. diff --git a/contrib/terraform/upcloud/cluster-settings.tfvars b/contrib/terraform/upcloud/cluster-settings.tfvars index 7e1296fd3..65c33a4fc 100644 --- a/contrib/terraform/upcloud/cluster-settings.tfvars +++ b/contrib/terraform/upcloud/cluster-settings.tfvars @@ -153,3 +153,46 @@ server_groups = { # anti_affinity_policy = "yes" # } } + +router_enable = false +gateways = { + # "gateway" : { + # features: [ "vpn" ] + # plan = "production" + # connections = { + # "connection" = { + # name = "connection" + # type = "ipsec" + # remote_routes = { + # "them" = { + # type = "static" + # static_network = "1.2.3.4/24" + # } + # } + # local_routes = { + # "me" = { + # type = "static" + # static_network = "4.3.2.1/24" + # } + # } + # tunnels = { + # "tunnel1" = { + # remote_address = "1.2.3.4" + # } + # } + # } + # } + # } +} +# gateway_vpn_psks = {} # Should be loaded as an environment variable +static_routes = { + # "route": { + # route: "1.2.3.4/24" + # nexthop: "4.3.2.1" + # } +} +network_peerings = { + # "peering": { + # remote_network: "uuid" + # } +} diff --git a/contrib/terraform/upcloud/main.tf b/contrib/terraform/upcloud/main.tf index 78fc0786f..d6a7e95d2 100644 --- a/contrib/terraform/upcloud/main.tf +++ b/contrib/terraform/upcloud/main.tf @@ -36,8 +36,15 @@ module "kubernetes" { loadbalancer_enabled = var.loadbalancer_enabled loadbalancer_plan = var.loadbalancer_plan loadbalancer_outbound_proxy_protocol = var.loadbalancer_proxy_protocol ? "v2" : "" + loadbalancer_legacy_network = var.loadbalancer_legacy_network loadbalancers = var.loadbalancers + router_enable = var.router_enable + gateways = var.gateways + gateway_vpn_psks = var.gateway_vpn_psks + static_routes = var.static_routes + network_peerings = var.network_peerings + server_groups = var.server_groups } diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf index 91c8b9ec7..54b9bf226 100644 --- a/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf +++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf @@ -20,6 +20,36 @@ locals { ] ]) + gateway_connections = flatten([ + for gateway_name, gateway in var.gateways : [ + for connection_name, connection in gateway.connections : { + "gateway_id" = upcloud_gateway.gateway[gateway_name].id + "gateway_name" = gateway_name + "connection_name" = connection_name + "type" = connection.type + "local_routes" = connection.local_routes + "remote_routes" = connection.remote_routes + } + ] + ]) + + gateway_connection_tunnels = flatten([ + for gateway_name, gateway in var.gateways : [ + for connection_name, connection in gateway.connections : [ + for tunnel_name, tunnel in connection.tunnels : { + "gateway_id" = upcloud_gateway.gateway[gateway_name].id + "gateway_name" = gateway_name + "connection_id" = upcloud_gateway_connection.gateway_connection["${gateway_name}-${connection_name}"].id + "connection_name" = connection_name + "tunnel_name" = tunnel_name + "local_address_name" = tolist(upcloud_gateway.gateway[gateway_name].address).0.name + "remote_address" = tunnel.remote_address + "ipsec_properties" = tunnel.ipsec_properties + } + ] + ] + ]) + # If prefix is set, all resources will be prefixed with "${var.prefix}-" # Else don't prefix with anything resource-prefix = "%{if var.prefix != ""}${var.prefix}-%{endif}" @@ -30,10 +60,13 @@ resource "upcloud_network" "private" { zone = var.zone ip_network { - address = var.private_network_cidr - dhcp = true - family = "IPv4" + address = var.private_network_cidr + dhcp_default_route = var.router_enable + dhcp = true + family = "IPv4" } + + router = var.router_enable ? upcloud_router.router[0].id : null } resource "upcloud_storage" "additional_disks" { @@ -516,16 +549,31 @@ resource "upcloud_loadbalancer" "lb" { name = "${local.resource-prefix}lb" plan = var.loadbalancer_plan zone = var.private_cloud ? var.public_zone : var.zone - networks { - name = "Private-Net" - type = "private" - family = "IPv4" - network = upcloud_network.private.id + network = var.loadbalancer_legacy_network ? upcloud_network.private.id : null + + dynamic "networks" { + for_each = var.loadbalancer_legacy_network ? [] : [1] + + content { + name = "Private-Net" + type = "private" + family = "IPv4" + network = upcloud_network.private.id + } + } + + dynamic "networks" { + for_each = var.loadbalancer_legacy_network ? [] : [1] + + content { + name = "Public-Net" + type = "public" + family = "IPv4" + } } - networks { - name = "Public-Net" - type = "public" - family = "IPv4" + + lifecycle { + ignore_changes = [ maintenance_dow, maintenance_time ] } } @@ -547,8 +595,21 @@ resource "upcloud_loadbalancer_frontend" "lb_frontend" { mode = "tcp" port = each.value.port default_backend_name = upcloud_loadbalancer_backend.lb_backend[each.key].name - networks { - name = "Public-Net" + + dynamic "networks" { + for_each = var.loadbalancer_legacy_network ? [] : [1] + + content { + name = "Public-Net" + } + } + + dynamic "networks" { + for_each = each.value.allow_internal_frontend ? [1] : [] + + content{ + name = "Private-Net" + } } } @@ -579,3 +640,111 @@ resource "upcloud_server_group" "server_groups" { ignore_changes = [members] } } + +resource "upcloud_router" "router" { + count = var.router_enable ? 1 : 0 + + name = "${local.resource-prefix}router" + + dynamic "static_route" { + for_each = var.static_routes + + content { + name = static_route.key + + nexthop = static_route.value["nexthop"] + route = static_route.value["route"] + } + } + +} + +resource "upcloud_gateway" "gateway" { + for_each = var.router_enable ? var.gateways : {} + name = "${local.resource-prefix}${each.key}-gateway" + zone = var.zone + + features = each.value.features + plan = each.value.plan + + router { + id = upcloud_router.router[0].id + } +} + +resource "upcloud_gateway_connection" "gateway_connection" { + for_each = { + for gc in local.gateway_connections : "${gc.gateway_name}-${gc.connection_name}" => gc + } + + gateway = each.value.gateway_id + name = "${local.resource-prefix}${each.key}-gateway-connection" + type = each.value.type + + dynamic "local_route" { + for_each = each.value.local_routes + + content { + name = local_route.key + type = local_route.value["type"] + static_network = local_route.value["static_network"] + } + } + + dynamic "remote_route" { + for_each = each.value.remote_routes + + content { + name = remote_route.key + type = remote_route.value["type"] + static_network = remote_route.value["static_network"] + } + } +} + +resource "upcloud_gateway_connection_tunnel" "gateway_connection_tunnel" { + for_each = { + for gct in local.gateway_connection_tunnels : "${gct.gateway_name}-${gct.connection_name}-${gct.tunnel_name}-tunnel" => gct + } + + connection_id = each.value.connection_id + name = each.key + local_address_name = each.value.local_address_name + remote_address = each.value.remote_address + + ipsec_auth_psk { + psk = var.gateway_vpn_psks[each.key].psk + } + + dynamic "ipsec_properties" { + for_each = each.value.ipsec_properties != null ? { "ip": each.value.ipsec_properties } : {} + + content { + child_rekey_time = ipsec_properties.value["child_rekey_time"] + dpd_delay = ipsec_properties.value["dpd_delay"] + dpd_timeout = ipsec_properties.value["dpd_timeout"] + ike_lifetime = ipsec_properties.value["ike_lifetime"] + rekey_time = ipsec_properties.value["rekey_time"] + phase1_algorithms = ipsec_properties.value["phase1_algorithms"] + phase1_dh_group_numbers = ipsec_properties.value["phase1_dh_group_numbers"] + phase1_integrity_algorithms = ipsec_properties.value["phase1_integrity_algorithms"] + phase2_algorithms = ipsec_properties.value["phase2_algorithms"] + phase2_dh_group_numbers = ipsec_properties.value["phase2_dh_group_numbers"] + phase2_integrity_algorithms = ipsec_properties.value["phase2_integrity_algorithms"] + } + } +} + +resource "upcloud_network_peering" "peering" { + for_each = var.network_peerings + + name = "${local.resource-prefix}${each.key}" + + network { + uuid = upcloud_network.private.id + } + + peer_network { + uuid = each.value.remote_network + } +} diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf index 4c41cc08f..afff410ca 100644 --- a/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf +++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf @@ -98,13 +98,19 @@ variable "loadbalancer_outbound_proxy_protocol" { type = string } +variable "loadbalancer_legacy_network" { + type = bool + default = false +} + variable "loadbalancers" { description = "Load balancers" type = map(object({ - port = number - target_port = number - backend_servers = list(string) + port = number + target_port = number + allow_internal_frontend = optional(bool) + backend_servers = list(string) })) } @@ -115,3 +121,72 @@ variable "server_groups" { anti_affinity_policy = string })) } + +variable "router_enable" { + description = "If a router should be enabled and connected to the private network or not" + + type = bool +} + +variable "gateways" { + description = "Gateways that should be connected to the router, requires router_enable is set to true" + + type = map(object({ + features = list(string) + plan = optional(string) + connections = optional(map(object({ + type = string + local_routes = optional(map(object({ + type = string + static_network = string + }))) + remote_routes = optional(map(object({ + type = string + static_network = string + }))) + tunnels = optional(map(object({ + remote_address = string + ipsec_properties = optional(object({ + child_rekey_time = number + dpd_delay = number + dpd_timeout = number + ike_lifetime = number + rekey_time = number + phase1_algorithms = set(string) + phase1_dh_group_numbers = set(string) + phase1_integrity_algorithms = set(string) + phase2_algorithms = set(string) + phase2_dh_group_numbers = set(string) + phase2_integrity_algorithms = set(string) + })) + }))) + }))) + })) +} + +variable "gateway_vpn_psks" { + description = "Separate variable for providing psks for connection tunnels" + + type = map(object({ + psk = string + })) + default = {} + sensitive = true +} + +variable "static_routes" { + description = "Static routes to apply to the router, requires router_enable is set to true" + + type = map(object({ + nexthop = string + route = string + })) +} + +variable "network_peerings" { + description = "Other UpCloud private networks to peer with, requires router_enable is set to true" + + type = map(object({ + remote_network = string + })) +} diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf index 81a2d1b57..4db5980d2 100644 --- a/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf +++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { upcloud = { source = "UpCloudLtd/upcloud" - version = "~>5.6.0" + version = "~>5.9.0" } } required_version = ">= 0.13" diff --git a/contrib/terraform/upcloud/variables.tf b/contrib/terraform/upcloud/variables.tf index 0ea81b99a..db398d7a5 100644 --- a/contrib/terraform/upcloud/variables.tf +++ b/contrib/terraform/upcloud/variables.tf @@ -136,13 +136,21 @@ variable "loadbalancer_proxy_protocol" { default = false } +variable "loadbalancer_legacy_network" { + description = "If the loadbalancer should use the deprecated network field instead of networks blocks. You probably want to have this set to false" + + type = bool + default = false +} + variable "loadbalancers" { description = "Load balancers" type = map(object({ - port = number - target_port = number - backend_servers = list(string) + port = number + target_port = number + allow_internal_frontend = optional(bool, false) + backend_servers = list(string) })) default = {} } @@ -156,3 +164,76 @@ variable "server_groups" { default = {} } + +variable "router_enable" { + description = "If a router should be enabled and connected to the private network or not" + + type = bool + default = false +} + +variable "gateways" { + description = "Gateways that should be connected to the router, requires router_enable is set to true" + + type = map(object({ + features = list(string) + plan = optional(string) + connections = optional(map(object({ + type = string + local_routes = optional(map(object({ + type = string + static_network = string + })), {}) + remote_routes = optional(map(object({ + type = string + static_network = string + })), {}) + tunnels = optional(map(object({ + remote_address = string + ipsec_properties = optional(object({ + child_rekey_time = number + dpd_delay = number + dpd_timeout = number + ike_lifetime = number + rekey_time = number + phase1_algorithms = set(string) + phase1_dh_group_numbers = set(string) + phase1_integrity_algorithms = set(string) + phase2_algorithms = set(string) + phase2_dh_group_numbers = set(string) + phase2_integrity_algorithms = set(string) + })) + })), {}) + })), {}) + })) + default = {} +} + +variable "gateway_vpn_psks" { + description = "Separate variable for providing psks for connection tunnels" + + type = map(object({ + psk = string + })) + default = {} + sensitive = true +} + +variable "static_routes" { + description = "Static routes to apply to the router, requires router_enable is set to true" + + type = map(object({ + nexthop = string + route = string + })) + default = {} +} + +variable "network_peerings" { + description = "Other UpCloud private networks to peer with, requires router_enable is set to true" + + type = map(object({ + remote_network = string + })) + default = {} +} diff --git a/contrib/terraform/upcloud/versions.tf b/contrib/terraform/upcloud/versions.tf index 81a2d1b57..4db5980d2 100644 --- a/contrib/terraform/upcloud/versions.tf +++ b/contrib/terraform/upcloud/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { upcloud = { source = "UpCloudLtd/upcloud" - version = "~>5.6.0" + version = "~>5.9.0" } } required_version = ">= 0.13"