@veltrixsecops/app-sdk 3.2.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +68 -1
  2. package/dist/byol/index.cjs +3802 -0
  3. package/dist/byol/index.cjs.map +1 -0
  4. package/dist/byol/index.d.cts +578 -0
  5. package/dist/byol/index.d.ts +578 -0
  6. package/dist/byol/index.js +2587 -0
  7. package/dist/byol/index.js.map +1 -0
  8. package/dist/chunk-3WK4EVQ3.js +842 -0
  9. package/dist/chunk-3WK4EVQ3.js.map +1 -0
  10. package/dist/{chunk-EOOEHZGC.js → chunk-HIL6RIDG.js} +107 -3
  11. package/dist/chunk-HIL6RIDG.js.map +1 -0
  12. package/dist/{chunk-VWFTOFTI.js → chunk-Y6XBNZGR.js} +8 -2
  13. package/dist/chunk-Y6XBNZGR.js.map +1 -0
  14. package/dist/client/index.cjs +112 -2
  15. package/dist/client/index.cjs.map +1 -1
  16. package/dist/client/index.d.cts +118 -6
  17. package/dist/client/index.d.ts +118 -6
  18. package/dist/client/index.js +13 -1
  19. package/dist/connections/index.cjs +1787 -0
  20. package/dist/connections/index.cjs.map +1 -0
  21. package/dist/connections/index.d.cts +78 -0
  22. package/dist/connections/index.d.ts +78 -0
  23. package/dist/connections/index.js +754 -0
  24. package/dist/connections/index.js.map +1 -0
  25. package/dist/hooks/index.cjs.map +1 -1
  26. package/dist/hooks/index.d.cts +2 -2
  27. package/dist/hooks/index.d.ts +2 -2
  28. package/dist/hooks/index.js +1 -1
  29. package/dist/index.cjs +7 -0
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +3 -3
  32. package/dist/index.d.ts +3 -3
  33. package/dist/index.js +3 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/opentofu/index.cjs +138 -0
  36. package/dist/opentofu/index.cjs.map +1 -0
  37. package/dist/opentofu/index.d.cts +131 -0
  38. package/dist/opentofu/index.d.ts +131 -0
  39. package/dist/opentofu/index.js +109 -0
  40. package/dist/opentofu/index.js.map +1 -0
  41. package/dist/pipeline/index.cjs +7 -0
  42. package/dist/pipeline/index.cjs.map +1 -1
  43. package/dist/pipeline/index.d.cts +9 -2
  44. package/dist/pipeline/index.d.ts +9 -2
  45. package/dist/pipeline/index.js +3 -1
  46. package/dist/pipeline-beeT8dje.d.cts +408 -0
  47. package/dist/pipeline-beeT8dje.d.ts +408 -0
  48. package/dist/ui/index.cjs +136 -0
  49. package/dist/ui/index.cjs.map +1 -1
  50. package/dist/ui/index.d.cts +109 -1
  51. package/dist/ui/index.d.ts +109 -1
  52. package/dist/ui/index.js +35 -677
  53. package/dist/ui/index.js.map +1 -1
  54. package/dist/{use-app-context-OQlcmq7t.d.cts → use-app-context-Byv_fam0.d.cts} +206 -4
  55. package/dist/{use-app-context-OQlcmq7t.d.ts → use-app-context-Byv_fam0.d.ts} +206 -4
  56. package/opentofu/README.md +114 -0
  57. package/opentofu/modules/aws/main.tf +1008 -0
  58. package/opentofu/modules/aws/outputs.tf +90 -0
  59. package/opentofu/modules/aws/variables.tf +437 -0
  60. package/opentofu/modules/aws/versions.tf +23 -0
  61. package/opentofu/modules/azure/main.tf +920 -0
  62. package/opentofu/modules/azure/outputs.tf +72 -0
  63. package/opentofu/modules/azure/variables.tf +498 -0
  64. package/opentofu/modules/azure/versions.tf +27 -0
  65. package/opentofu/modules/gcp/main.tf +645 -0
  66. package/opentofu/modules/gcp/outputs.tf +73 -0
  67. package/opentofu/modules/gcp/variables.tf +428 -0
  68. package/opentofu/modules/gcp/versions.tf +27 -0
  69. package/opentofu/modules/hetzner/main.tf +432 -0
  70. package/opentofu/modules/hetzner/outputs.tf +72 -0
  71. package/opentofu/modules/hetzner/variables.tf +423 -0
  72. package/opentofu/modules/hetzner/versions.tf +26 -0
  73. package/package.json +33 -1
  74. package/dist/chunk-EOOEHZGC.js.map +0 -1
  75. package/dist/chunk-VWFTOFTI.js.map +0 -1
  76. package/dist/pipeline-A-gSsPRR.d.cts +0 -189
  77. package/dist/pipeline-A-gSsPRR.d.ts +0 -189
@@ -0,0 +1,73 @@
1
+ # =============================================================================
2
+ # GCP environment module — outputs.
3
+ #
4
+ # `resource_refs` is THE contract with the status-back path: a map of
5
+ # plan_key -> external cloud ref (GCP self-link / id). The CI apply reads this
6
+ # (via `tofu output -json`) and/or the parsed apply stream and emits
7
+ # `resource.status {planKey, status, externalRef}` per key, which the app's
8
+ # onEvent hook maps onto the matching BYOL-resource row. Output NAMES are
9
+ # identical to sdk/opentofu/modules/aws.
10
+ # =============================================================================
11
+
12
+ locals {
13
+ # Foundation-tier refs, included only when the tier is in the plan. Same
14
+ # plan_key contract + `foundation/<x>` keys as AWS; values are GCP self-links /
15
+ # ids (GCP LB/IP for the load-balancer + hec refs, in lieu of an ARN/DNS name).
16
+ infra_refs = merge(
17
+ { "foundation/network" = local.network_self_link },
18
+ local.has_storage ? { "foundation/storage" = google_storage_bucket.objstore[0].url } : {},
19
+ local.has_secrets ? { "foundation/secrets" = google_secret_manager_secret.env[0].id } : {},
20
+ local.has_license_file ? { "foundation/license-file" = google_secret_manager_secret.license[0].id } : {},
21
+ (local.dns_managed && local.has_tls && var.dns_domain != "") ? { "foundation/tls" = google_compute_managed_ssl_certificate.env[0].self_link } : {},
22
+ local.has_lb ? { "foundation/load-balancer" = google_compute_global_address.lb[0].self_link } : {},
23
+ local.dns_managed && local.has_dns && local.has_lb ? { "foundation/dns" = google_dns_record_set.env[0].name } : {},
24
+ # HEC is realized as an endpoint on the LB (external) or on the standalone
25
+ # instance; it maps to the LB's public IP when present. When there is no LB it
26
+ # is a post-config step (reported via deployment.step, not a discrete resource).
27
+ local.has_hec && local.has_lb ? { "ingest/hec" = google_compute_global_address.lb[0].address } : {},
28
+ )
29
+ }
30
+
31
+ output "resource_refs" {
32
+ description = "Map of plan_key -> external cloud reference (instance self-link / secret id / IP). Drives per-resource status-back."
33
+ value = merge(
34
+ { for k, inst in google_compute_instance.node : k => inst.self_link },
35
+ local.infra_refs,
36
+ )
37
+ }
38
+
39
+ output "subnet_id" {
40
+ description = "Primary compute subnetwork self-link."
41
+ value = local.compute_subnet_ids[0]
42
+ }
43
+
44
+ output "security_group_id" {
45
+ description = "The stack network tag — GCP's SG-to-SG identifier (applied to every instance and referenced by the firewall rules)."
46
+ value = local.stack_tag
47
+ }
48
+
49
+ output "network_id" {
50
+ description = "Self-link of the network this stack runs in — the created network for dedicated, the looked-up network for shared/existing."
51
+ value = local.network_self_link
52
+ }
53
+
54
+ output "instance_ids" {
55
+ description = "plan_key -> Compute Engine instance self-link for compute nodes."
56
+ value = { for k, inst in google_compute_instance.node : k => inst.self_link }
57
+ }
58
+
59
+ output "instance_private_ips" {
60
+ description = "plan_key -> private IP for compute nodes."
61
+ value = { for k, inst in google_compute_instance.node : k => inst.network_interface[0].network_ip }
62
+ }
63
+
64
+ output "node_fqdns" {
65
+ description = <<-EOT
66
+ plan_key -> function FQDN (e.g. idx1.<dns_domain>, sh1.<dns_domain>) for each
67
+ compute node. The bring-up layer uses these to build its inventory
68
+ (cluster/SHC peer resolution). Populated whenever dns_domain is set; the
69
+ matching A records are only created when a private zone is present
70
+ (create_private_zone or private_zone_id). Empty when dns_domain is unset.
71
+ EOT
72
+ value = local.node_fqdns
73
+ }
@@ -0,0 +1,428 @@
1
+ # =============================================================================
2
+ # GCP environment module — input variables.
3
+ #
4
+ # One BYOL environment (a "stack") = one dedicated /24 subnetwork carved from the
5
+ # shared Veltrix Network (VPC), plus one Compute Engine instance per plan item and
6
+ # the storage / secrets / TLS / LB / DNS a tool tier needs. The shared network is
7
+ # Veltrix-owned and is looked up (data source), NEVER created here.
8
+ #
9
+ # This is a faithful translation of sdk/opentofu/modules/aws: it honors the SAME
10
+ # variable + output contract. Spec-derived variables (foundation_kinds,
11
+ # compute_kinds, security_rules, load_balancer, dns_prefixes, waf_enabled,
12
+ # alb_auth) are byte-identical to AWS — they come from the app's InfraSpec via
13
+ # renderInfraVars and MUST match across clouds. Deployment variables keep the same
14
+ # NAMES as AWS; GCP specifics (project/zone/image/machine-type) are added.
15
+ #
16
+ # The `plan` list is the SAME topology the app persists as its BYOL-resource
17
+ # rows. Keying compute by `plan_key` is the contract that lets the CI apply report
18
+ # `resource.status` back per row (see outputs.tf + ci/emit-status.mjs).
19
+ # =============================================================================
20
+
21
+ # --- Identity / naming / tenancy ------------------------------------------
22
+
23
+ variable "app_id" {
24
+ description = "Owning app id, e.g. my-app. Used for naming + state key."
25
+ type = string
26
+ }
27
+
28
+ variable "customer_id" {
29
+ description = "Tenant (customer) id. Used for naming + label veltrix_customer."
30
+ type = string
31
+ }
32
+
33
+ variable "infrastructure_id" {
34
+ description = "BYOL infrastructure (environment/stack) id. Unique per stack."
35
+ type = string
36
+ }
37
+
38
+ variable "provider_code" {
39
+ description = "Cloud provider code (informational; this module is GCP). One of aws|azure|gcp|hetzner."
40
+ type = string
41
+ default = "gcp"
42
+ }
43
+
44
+ # --- GCP placement --------------------------------------------------------
45
+
46
+ variable "project" {
47
+ description = "GCP project id to deploy into. For BYOC this is the customer's project; for hosted it is the Veltrix project."
48
+ type = string
49
+ }
50
+
51
+ variable "region" {
52
+ description = "GCP region for regional resources (subnetwork, router/NAT, bucket), e.g. us-central1."
53
+ type = string
54
+ }
55
+
56
+ variable "zone" {
57
+ description = "GCP zone for zonal compute (instances + the LB instance group), e.g. us-central1-a. Must be within var.region."
58
+ type = string
59
+ }
60
+
61
+ # --- Network (mode: hosted-shared vs BYOC dedicated/existing) --------------
62
+ # network_mode is a DEPLOYMENT-TARGET var set by the worker per environment — it
63
+ # is NOT part of the app's InfraSpec (which describes only the tool). One app
64
+ # spec deploys hosted OR into a customer's own account, any cloud.
65
+
66
+ variable "network_mode" {
67
+ description = <<-EOT
68
+ How the environment's network is sourced:
69
+ shared — Veltrix-hosted: data-source the shared network (network_ref) and
70
+ create the IPAM-allocated per-stack subnetwork (subnet_cidr).
71
+ dedicated — BYOC: CREATE a fresh custom-mode network (auto_create_subnetworks
72
+ = false) + a subnetwork (vpc_cidr) + Cloud Router + Cloud NAT for
73
+ private egress, isolated per env.
74
+ existing — BYOC: data-source a customer-designated network (network_ref) and
75
+ create a subnetwork inside it (subnet_cidr).
76
+ EOT
77
+ type = string
78
+ default = "shared"
79
+
80
+ validation {
81
+ condition = contains(["shared", "dedicated", "existing"], var.network_mode)
82
+ error_message = "network_mode must be one of: shared, dedicated, existing."
83
+ }
84
+ }
85
+
86
+ variable "network_ref" {
87
+ description = <<-EOT
88
+ Reference to the network to deploy into, for network_mode = shared|existing.
89
+ GCP networks are identified by NAME (there is no tag-based lookup), so this is
90
+ the network name (e.g. `vpc-veltrix-usc1-shared` or a customer network name).
91
+ Ignored for network_mode = dedicated (the network is created).
92
+ EOT
93
+ type = string
94
+ default = ""
95
+ }
96
+
97
+ variable "network_lookup_by" {
98
+ description = <<-EOT
99
+ How to resolve network_ref. Kept for cross-cloud contract parity with AWS;
100
+ GCP has no tag-based network lookup, so both `tag` and `id` resolve
101
+ network_ref as the network NAME. Used for shared|existing.
102
+ EOT
103
+ type = string
104
+ default = "tag"
105
+
106
+ validation {
107
+ condition = contains(["tag", "id"], var.network_lookup_by)
108
+ error_message = "network_lookup_by must be either \"tag\" or \"id\"."
109
+ }
110
+ }
111
+
112
+ variable "subnet_cidr" {
113
+ description = <<-EOT
114
+ Per-stack subnetwork CIDR for network_mode = shared|existing (IPAM-allocated
115
+ for hosted). Ignored for dedicated (the subnetwork range is vpc_cidr). Empty is
116
+ allowed only when dedicated.
117
+ EOT
118
+ type = string
119
+ default = ""
120
+
121
+ validation {
122
+ condition = var.subnet_cidr == "" || can(cidrhost(var.subnet_cidr, 0))
123
+ error_message = "subnet_cidr must be empty or a valid IPv4 CIDR, e.g. 10.20.16.0/24."
124
+ }
125
+ }
126
+
127
+ variable "vpc_cidr" {
128
+ description = <<-EOT
129
+ CIDR for the subnetwork of the network CREATED when network_mode = dedicated
130
+ (BYOC). GCP custom-mode networks have no parent range; this whole CIDR becomes
131
+ the stack's single regional subnetwork. Ignored otherwise.
132
+ EOT
133
+ type = string
134
+ default = "10.60.0.0/16"
135
+
136
+ validation {
137
+ condition = can(cidrhost(var.vpc_cidr, 0))
138
+ error_message = "vpc_cidr must be a valid IPv4 CIDR, e.g. 10.60.0.0/16."
139
+ }
140
+ }
141
+
142
+ variable "admin_cidr" {
143
+ description = "CIDR allowed to reach management ports + the web UI when there is no LB. Typically the Veltrix control-plane / customer bastion range."
144
+ type = string
145
+ default = "10.0.0.0/8"
146
+ }
147
+
148
+ # --- The plan (topology) --------------------------------------------------
149
+
150
+ variable "plan" {
151
+ description = <<-EOT
152
+ Ordered resource plan from the app topology. One object per resource the
153
+ environment needs. `plan_key` is the stable key that maps 1:1 to a
154
+ BYOL-resource row; the CI apply emits resource.status per plan_key.
155
+ EOT
156
+ type = list(object({
157
+ plan_key = string
158
+ tier = string # foundation | control-plane | data | search | ingest
159
+ kind = string # network | storage | indexer | search-head | management-node | ...
160
+ name = optional(string, "")
161
+ role = optional(string, "")
162
+ region = optional(string, null)
163
+ # Multi-AZ placement: the GCP zone (e.g. us-central1-b) this node is pinned to
164
+ # (null = var.zone). GCP subnets are regional, so only the instance zone varies.
165
+ zone = optional(string, null)
166
+ roles = optional(list(string), [])
167
+ }))
168
+
169
+ validation {
170
+ condition = length(var.plan) > 0
171
+ error_message = "plan must contain at least one item."
172
+ }
173
+ }
174
+
175
+ # --- Per-tier compute sizing ----------------------------------------------
176
+
177
+ variable "machine_types" {
178
+ description = <<-EOT
179
+ Per-tier Compute Engine machine type override, keyed by tier
180
+ (foundation|control-plane|data|search|ingest). Missing tiers fall back to
181
+ default_machine_type. Kind-level overrides go in machine_types_by_kind.
182
+ EOT
183
+ type = map(string)
184
+ default = {}
185
+ }
186
+
187
+ variable "machine_types_by_kind" {
188
+ description = "Per-kind machine type override (wins over machine_types). e.g. { indexer = \"n2-standard-8\" }."
189
+ type = map(string)
190
+ default = {}
191
+ }
192
+
193
+ variable "default_machine_type" {
194
+ description = "Fallback machine type for any compute plan item with no tier/kind override."
195
+ type = string
196
+ default = "e2-medium"
197
+ }
198
+
199
+ variable "boot_disk_gb" {
200
+ description = "Boot disk size (GiB) for each compute instance."
201
+ type = number
202
+ default = 100
203
+ }
204
+
205
+ # --- Machine image / access -----------------------------------------------
206
+
207
+ variable "image" {
208
+ description = <<-EOT
209
+ Boot image for compute instances — an image self-link, `project/family`, or
210
+ image name. If empty, the module falls back to the latest Debian 12 image
211
+ (scaffold only) — production MUST supply a hardened, tool-preinstalled image.
212
+ EOT
213
+ type = string
214
+ default = ""
215
+ }
216
+
217
+ variable "ssh_public_key" {
218
+ description = <<-EOT
219
+ Optional SSH public key for break-glass access, in GCP metadata format
220
+ `user:ssh-rsa AAAA... comment`. Empty = none (published to the instance's
221
+ ssh-keys metadata when set).
222
+ EOT
223
+ type = string
224
+ default = ""
225
+ }
226
+
227
+ # --- Foundation options ----------------------------------------------------
228
+
229
+ variable "dns_domain" {
230
+ description = "Base domain for the environment (e.g. <cust>-<env>.veltrixsecops.com, or a customer domain in BYOC). Required only if the plan includes a dns item."
231
+ type = string
232
+ default = ""
233
+ }
234
+
235
+ variable "dns_mode" {
236
+ description = <<-EOT
237
+ How the PUBLIC (analyst-facing) DNS name + TLS cert are handled. The PRIVATE
238
+ intra-cluster zone is always created in the deploy account's network regardless.
239
+ managed — the module creates the public A record (→ the LB's global IP)
240
+ in public_dns_managed_zone + a Google-managed SSL cert (works
241
+ for hosted on the Veltrix zone AND a BYOC customer-owned zone —
242
+ both live in the deploy account).
243
+ delegated — BYOC cross-account: the WORKER writes the public record into
244
+ Veltrix's zone; the module makes NO public record/cert and uses
245
+ certificate_arn (a self-provided SSL cert self-link) on the
246
+ HTTPS proxy instead.
247
+ private-only — no public DNS; reached via the customer network (ZTNA/VPN).
248
+ EOT
249
+ type = string
250
+ default = "managed"
251
+
252
+ validation {
253
+ condition = contains(["managed", "delegated", "private-only"], var.dns_mode)
254
+ error_message = "dns_mode must be one of: managed, delegated, private-only."
255
+ }
256
+ }
257
+
258
+ variable "public_dns_managed_zone" {
259
+ description = "Cloud DNS managed zone NAME for the PUBLIC record, when dns_mode = managed. The zone must be in the DEPLOY project (Veltrix's for hosted, the customer's for BYOC customer-owned)."
260
+ type = string
261
+ default = ""
262
+ }
263
+
264
+ variable "certificate_arn" {
265
+ description = <<-EOT
266
+ Pre-provisioned SSL certificate reference for the HTTPS proxy, used when
267
+ dns_mode = delegated (the worker provisions + validates the cert). On GCP this
268
+ is a `google_compute_ssl_certificate` / managed-cert SELF-LINK (the name is
269
+ kept as `certificate_arn` for cross-cloud contract uniformity). Empty otherwise
270
+ (the module issues its own managed cert in dns_mode = managed).
271
+ EOT
272
+ type = string
273
+ default = ""
274
+ }
275
+
276
+ variable "alb_auth" {
277
+ description = <<-EOT
278
+ Optional OIDC/MFA enforced at the front door, in front of the tool's web UI.
279
+ The field shape is identical to AWS (Cognito) for cross-cloud contract parity.
280
+ On GCP the analog is Identity-Aware Proxy (IAP); it is a larger lift and is
281
+ DOCUMENTED AS A FOLLOW-ON — this module NO-OPS the variable (no IAP resources
282
+ are created). When enabled, all three user_pool_* fields are required so the
283
+ tfvars stay valid across clouds.
284
+ EOT
285
+ type = object({
286
+ enabled = optional(bool, false)
287
+ user_pool_arn = optional(string, "")
288
+ user_pool_client_id = optional(string, "")
289
+ user_pool_domain = optional(string, "")
290
+ })
291
+ default = {}
292
+
293
+ validation {
294
+ condition = !var.alb_auth.enabled || (
295
+ var.alb_auth.user_pool_arn != "" &&
296
+ var.alb_auth.user_pool_client_id != "" &&
297
+ var.alb_auth.user_pool_domain != ""
298
+ )
299
+ error_message = "When alb_auth.enabled is true, user_pool_arn, user_pool_client_id and user_pool_domain are all required."
300
+ }
301
+ }
302
+
303
+ variable "create_private_zone" {
304
+ description = <<-EOT
305
+ Create a Cloud DNS PRIVATE managed zone for dns_domain, associated with the
306
+ stack network, to hold per-node function FQDNs (idx1.<domain>, sh1.<domain>, ...).
307
+ Ignored when dns_domain is empty. Mutually complementary with private_zone_id:
308
+ set private_zone_id instead to reuse an existing zone.
309
+ EOT
310
+ type = bool
311
+ default = false
312
+ }
313
+
314
+ variable "private_zone_id" {
315
+ description = <<-EOT
316
+ Existing Cloud DNS private managed zone NAME to publish per-node function FQDNs
317
+ into. Takes precedence over create_private_zone when non-empty. Leave empty
318
+ (and set create_private_zone) to have the module create the zone. Named
319
+ private_zone_id for cross-cloud parity; on GCP it is the managed-zone name.
320
+ EOT
321
+ type = string
322
+ default = ""
323
+ }
324
+
325
+ # --- Declarative infra spec (rendered from the app's InfraSpec) -----------
326
+ # These are what make the module tool-agnostic. The app declares its ports /
327
+ # front-door / DNS as DATA (sdk/src/opentofu/spec.ts InfraSpec) and the SDK
328
+ # renders them here. NOTHING below is app-specific. This block is
329
+ # byte-identical to sdk/opentofu/modules/aws/variables.tf.
330
+
331
+ variable "foundation_kinds" {
332
+ description = <<-EOT
333
+ Plan `kind`s the module realizes as shared FOUNDATION infra (not compute).
334
+ Any plan item whose kind is NOT in this set (and not named by compute_kinds)
335
+ is a compute node. Kept in sync with FOUNDATION_KINDS in spec.ts.
336
+ EOT
337
+ type = list(string)
338
+ default = [
339
+ "network", "storage", "secrets", "tls",
340
+ "load-balancer", "dns", "license-file", "hec",
341
+ ]
342
+ }
343
+
344
+ variable "compute_kinds" {
345
+ description = <<-EOT
346
+ Optional explicit allow-list of compute kinds. When non-empty, ONLY these
347
+ kinds become compute nodes. When empty (default), compute = any plan item
348
+ whose kind is not in foundation_kinds (so an app's roles are compute
349
+ automatically).
350
+ EOT
351
+ type = list(string)
352
+ default = []
353
+ }
354
+
355
+ variable "security_rules" {
356
+ description = <<-EOT
357
+ Firewall ingress rules from the app's InfraSpec. Each rule opens `port` to one
358
+ or more `sources`: "self" (peer nodes — the stack network tag referencing
359
+ itself), "alb" (the front-door LB; on GCP this maps to Google's LB health-check
360
+ + proxy ranges 130.211.0.0/22 and 35.191.0.0/16; ignored when there is no load
361
+ balancer), or "admin" (var.admin_cidr). Replaces any hardcoded, tool-specific
362
+ port list.
363
+ EOT
364
+ type = list(object({
365
+ port = number
366
+ protocol = optional(string, "tcp")
367
+ sources = list(string)
368
+ description = optional(string, "")
369
+ }))
370
+ default = []
371
+
372
+ validation {
373
+ condition = alltrue([
374
+ for r in var.security_rules : alltrue([
375
+ for s in r.sources : contains(["self", "alb", "admin"], s)
376
+ ])
377
+ ])
378
+ error_message = "Every security_rules[*].sources entry must be one of: self, alb, admin."
379
+ }
380
+ }
381
+
382
+ variable "load_balancer" {
383
+ description = <<-EOT
384
+ Front-door LB spec from the app's InfraSpec. Null for headless / forwarder-
385
+ only tools. When set (and the plan carries a load-balancer item), the module
386
+ builds the health check + backend service + HTTPS proxy + forwarding rule from
387
+ this. `target_kinds` are the compute kinds that sit behind the LB.
388
+ EOT
389
+ type = object({
390
+ target_port = number
391
+ target_protocol = optional(string, "HTTP")
392
+ health_check_path = string
393
+ health_check_matcher = optional(string, "200-399")
394
+ health_check_protocol = optional(string, "")
395
+ target_kinds = list(string)
396
+ listener_port = optional(number, 443)
397
+ })
398
+ default = null
399
+ }
400
+
401
+ variable "dns_prefixes" {
402
+ description = <<-EOT
403
+ kind -> DNS label prefix for per-node function FQDNs (e.g.
404
+ { indexer = "idx", search-head = "sh", cluster-manager = "mgmt" }). A compute
405
+ kind absent from the map falls back to the kind string itself.
406
+ EOT
407
+ type = map(string)
408
+ default = {}
409
+ }
410
+
411
+ variable "waf_enabled" {
412
+ description = "Attach a Cloud Armor security policy (OWASP preconfigured rules + IP rate limit) to the LB backend service. Ignored when there is no load balancer."
413
+ type = bool
414
+ default = true
415
+ }
416
+
417
+ # --- Labels (threaded onto every labelable resource) ----------------------
418
+
419
+ variable "tags" {
420
+ description = <<-EOT
421
+ Canonical Veltrix tag set. Named `tags` for cross-cloud contract parity, but on
422
+ GCP these are SANITIZED into labels (lowercase [a-z0-9_-], key starts with a
423
+ letter, <=63 chars) before being applied — the raw keys (Veltrix:Customer,
424
+ CostCenter, ...) are invalid GCP label keys. Must include Veltrix:ManagedBy
425
+ (the OIDC/WIF apply identity is scoped by veltrix_managedby = veltrix).
426
+ EOT
427
+ type = map(string)
428
+ }
@@ -0,0 +1,27 @@
1
+ # =============================================================================
2
+ # GCP environment module — provider/version pinning (OpenTofu-compatible HCL).
3
+ #
4
+ # OpenTofu resolves providers from registry.opentofu.org by default but the
5
+ # canonical `hashicorp/google` source address is mirrored there, so this block is
6
+ # byte-for-byte compatible with both `tofu` and `terraform`. We pin OpenTofu
7
+ # >= 1.6.0 (the first stable OpenTofu line) rather than a Terraform version.
8
+ #
9
+ # Provider: google ~> 6.0 (current major line). Every resource used here is in
10
+ # the GA `google` provider (no google-beta needed). random is used for the
11
+ # object-storage bucket suffix, exactly as in the AWS reference module.
12
+ # =============================================================================
13
+
14
+ terraform {
15
+ required_version = ">= 1.6.0"
16
+
17
+ required_providers {
18
+ google = {
19
+ source = "hashicorp/google"
20
+ version = "~> 6.0"
21
+ }
22
+ random = {
23
+ source = "hashicorp/random"
24
+ version = "~> 3.6"
25
+ }
26
+ }
27
+ }