-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.tf
266 lines (229 loc) · 9.98 KB
/
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
terraform {
required_version = ">= 1.1.0"
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudinit = {
source = "hashicorp/cloudinit"
}
}
}
#
# Pull default zones and the service account. Both can be overridden in variables if needed.
#
data "google_compute_zones" "zones_in_region" {
region = local.region
}
data "google_compute_default_service_account" "default" {}
data "google_client_config" "default" {}
#
# Apply defaults if not overriden in variables, sanitize inputs
#
locals {
# if service account is not passed explicitly in variable, pick the default Compute Engine account
service_account = coalesce(var.service_account, data.google_compute_default_service_account.default.email)
# derive region from zones if provided, otherwise use the region from variable, as last resort use default region from provider
region = coalesce(try(join("-", slice(split("-", var.zones[0]), 0, 2)), null), var.region, data.google_client_config.default.region)
#sanitize labels
labels = { for k, v in var.labels : k => replace(lower(v), " ", "_") }
# If prefix is defined, add a "-" spacer after it
prefix = length(var.prefix) > 0 && substr(var.prefix, -1, 1) != "-" ? "${var.prefix}-" : var.prefix
# Auto-set NIC type to GVNIC if ARM image was selected
nic_type = var.image.arch == "arm" ? "GVNIC" : var.nic_type
# Pick explicit or detected zones and save to locals. Limit auto-zones to 3
zones = var.zones != null ? var.zones : slice(data.google_compute_zones.zones_in_region.names, 0, 3)
# List of used UMIG zones optionally padded by some unsed to make it always 3. See umigs resource at the end of this file for details
zones3 = slice(concat(local.zones, compact([for i in data.google_compute_zones.zones_in_region.names : contains(local.zones, i) ? null : i])), 0, 3)
# Calculate last port for management or copy from vars. Used for FGT configuration bootstrap, ACL, and public IPs.
mgmt_port = var.mgmt_port != null ? var.mgmt_port : "port${length(var.subnets)}"
# Calculate FGSP port (last one) if set to "auto", otherwise use variable. Null means no dedicated port => FGSP over port1.
dedicated_fgsp_port = var.fgsp_port != "auto" ? var.fgsp_port : "port${length(var.subnets)}"
fgsp_port = coalesce(local.dedicated_fgsp_port, "port1" )
#
# Create common lists
#
hc_ranges_ilb = ["35.191.0.0/16", "130.211.0.0/22"]
hc_ranges_elb = ["35.191.0.0/16", "209.85.152.0/22", "209.85.204.0/22"]
ports_all = [ for indx in range(length(var.subnets)) : "port${indx+1}"]
//ports_internal = slice( local.ports_all, 1, length(var.subnets)-1 )
ports_internal = setsubtract( local.ports_all, setunion(var.ports_external, [local.dedicated_fgsp_port]))
fgts = [ for indx in range(var.cluster_size) : "fgt${indx+1}" ]
}
#
# Pull information about subnets we will connect to FortiGate instances. Subnets must
# already exist (can be created in parent module).
# Index by port name
#
data "google_compute_subnetwork" "connected" {
for_each = toset([for indx in range(length(var.subnets)) : "port${indx + 1}"]) #toset(var.subnets)
name = var.subnets[substr(each.value, 4, 1)-1]
region = local.region
}
#
# We'll use shortened region and zone names for some resource names. This is a standard shortening described in
# GCP security foundations.
#
locals {
region_short = replace(replace(replace(replace(replace(replace(replace(replace(replace(local.region, "-south", "s"), "-east", "e"), "-central", "c"), "-north", "n"), "-west", "w"), "europe", "eu"), "australia", "au"), "northamerica", "na"), "southamerica", "sa")
zones_short = [ for zone in local.zones3 :
"${local.region_short}${substr(zone, length(local.region) + 1, 1)}"
]
}
#
# Create FortiGate instances with secondary logdisks and configuration.
#
resource "google_compute_disk" "logdisk" {
count = var.cluster_size
name = "${local.prefix}disk-logdisk${count.index + 1}-${local.zones_short[count.index%length(local.zones)]}"
size = var.logdisk_size
type = "pd-ssd"
zone = local.zones[count.index]
}
#
# Prepare bootstrap data
# - part 1 is optional FortiFlex license token
# - part 2 is bootstrap configuration script built from fgt_config.tftpl template
#
data "cloudinit_config" "fgt" {
count = var.cluster_size
gzip = false
base64_encode = false
dynamic "part" {
for_each = try(var.flex_tokens[count.index], "") == "" ? [] : [1]
content {
filename = "license"
content_type = "text/plain; charset=\"us-ascii\""
content = <<-EOF
LICENSE-TOKEN: ${var.flex_tokens[count.index]}
EOF
}
}
part {
filename = "config"
content_type = "text/plain; charset=\"us-ascii\""
content = templatefile("${path.module}/base_config.tftpl", {
hostname = "${local.prefix}fgt${count.index + 1}-${local.zones_short[count.index%length(local.zones)]}"
healthcheck_port = var.healthcheck_port
fgt_config = "" #var.fgt_config
# all private addresses for given instance. ordered by subnet/nic index0
prv_ips = { for indx, addr in google_compute_address.prv : split("_", indx)[0] => addr.address if tonumber(split("_", indx)[1]) == count.index }
ilb_ips = google_compute_address.ilb
## reverse indexing in case we wanted more subnets per port in future
subnets = { for port, subnet in data.google_compute_subnetwork.connected :
subnet.ip_cidr_range => {
"dev" : port,
"name" : subnet.name
}
}
gateways = { for port, subnet in data.google_compute_subnetwork.connected : port=>subnet.gateway_address }
ha_indx = count.index
# each private address on last interface except for matching the instance index
ha_peers = [for key, addr in google_compute_address.prv : addr.address if tonumber(split("_", key)[1]) != count.index && split("_", key)[0] == local.fgsp_port]
frontends = concat([for eip in var.frontends : try(local.eip_all[eip], local.eip_all[eip.name])], [for eipobj in var.frontends_obj : eipobj.address])
mgmt_port = local.mgmt_port
mgmt_port_public = var.mgmt_port_public
fortimanager = var.fortimanager
fgt_config = var.fgt_config
})
}
}
#
# Find image either based on version+arch+lic ...
#
module "fgtimage" {
count = var.image.version == "" ? 0 : 1
source = "./modules/fgt-get-image"
ver = var.image.version
arch = var.image.arch
lic = "${try(var.license_files[0], "")}${try(var.flex_tokens[0], "")}" != "" ? "byol" : var.image.lic
}
# ... or based on family/name
data "google_compute_image" "by_family_name" {
count = var.image.version == "" ? 1 : 0
project = var.image.project
family = var.image.name == "" ? var.image.family : null
name = var.image.name != "" ? var.image.name : null
lifecycle {
postcondition {
condition = !(("${try(var.license_files[0], "")}${try(var.flex_tokens[0], "")}" != "") && strcontains(self.name, "ondemand"))
error_message = "You provided a FortiGate BYOL (or Flex) license, but you're attempting to deploy a PAYG image. This would result in a double license fee. \nUpdate module's 'image' parameter to fix this error.\n\nCurrent var.image value: \n {%{for k, v in var.image}%{if tostring(v) != ""}\n ${k}=${v}%{endif}%{endfor}\n }"
}
}
}
# ... and pick one
locals {
fgt_image = var.image.version == "" ? data.google_compute_image.by_family_name[0] : module.fgtimage[0].image
}
#
# Deploy VMs
#
resource "google_compute_instance" "fgt_vm" {
count = var.cluster_size
zone = local.zones[count.index % length(local.zones)]
name = "${local.prefix}vm-${local.fgts[count.index]}-${local.zones_short[count.index%length(local.zones)]}"
machine_type = var.machine_type
can_ip_forward = true
tags = var.fgt_tags
boot_disk {
initialize_params {
image = local.fgt_image.self_link
labels = var.labels
}
}
attached_disk {
source = google_compute_disk.logdisk[count.index].name
}
service_account {
email = local.service_account
scopes = ["cloud-platform"]
}
metadata = {
user-data = data.cloudinit_config.fgt[count.index].rendered
license = fileexists(try(var.license_files[count.index], "null")) ? file(var.license_files[count.index]) : null
serial-port-enable = var.serial_port_enable
oslogin-enable = var.oslogin_enable
}
dynamic "network_interface" {
for_each = data.google_compute_subnetwork.connected
content {
subnetwork = network_interface.value.name
nic_type = local.nic_type
network_ip = google_compute_address.prv["${network_interface.key}_${count.index}"].address
dynamic "access_config" {
for_each = var.mgmt_port_public && local.mgmt_port == network_interface.key ? [1] : []
content {
nat_ip = google_compute_address.mgmt[local.fgts[count.index]].address
}
}
}
}
} //fgt-vm
#
# Common Load Balancer resources
#
resource "google_compute_region_health_check" "health_check" {
name = "${local.prefix}healthcheck-http${var.healthcheck_port}-${local.region_short}"
region = local.region
timeout_sec = 2
check_interval_sec = 2
http_health_check {
port = var.healthcheck_port
}
}
resource "google_compute_instance_group" "fgt_umigs" {
# note that the number of UMIGs must be known before applying (before reading zone list if no variable is given)
# so, the number of UMIGs cannot be dynamic. We limit auto-detected zones and var length to 3, so let's stick to 3.
# If deployed to 2 zones only, we still create a dummy 3rd UMIG. To avoid inambiguity it should be located in an unused zone -
# that's were we take an unused zone from those available in the region.
count = 3
name = "${local.prefix}umig-${local.zones_short[count.index]}"
zone = local.zones3[count.index]
instances = matchkeys(
google_compute_instance.fgt_vm[*].self_link,
google_compute_instance.fgt_vm[*].zone,
[local.zones3[count.index]])
}