@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,72 @@
1
+ # =============================================================================
2
+ # Azure environment module — outputs.
3
+ #
4
+ # `resource_refs` is THE contract with the status-back path: a map of
5
+ # plan_key -> external cloud ref. The CI apply reads this (via `tofu output
6
+ # -json`) and/or the parsed apply stream and emits `resource.status
7
+ # {planKey, status, externalRef}` per key, which the app's onEvent hook maps
8
+ # onto the matching BYOL resource row. Names are IDENTICAL to the AWS module.
9
+ # =============================================================================
10
+
11
+ locals {
12
+ # Foundation-tier refs, included only when the tier is in the plan. Mirrors the
13
+ # AWS infra_refs map key-for-key (Azure resource ids in place of AWS arns).
14
+ infra_refs = merge(
15
+ { "foundation/network" = local.network_id },
16
+ local.has_storage ? { "foundation/storage" = azurerm_storage_account.objstore[0].id } : {},
17
+ local.has_secrets ? { "foundation/secrets" = azurerm_key_vault.secrets[0].id } : {},
18
+ local.has_license_file ? { "foundation/license-file" = azurerm_key_vault_secret.license[0].id } : {},
19
+ # TLS: Azure has no in-module cert issuance, so the ref is the caller-supplied
20
+ # Key Vault cert id (var.certificate_arn) when a tls tier is planned.
21
+ (local.has_tls && var.certificate_arn != "") ? { "foundation/tls" = var.certificate_arn } : {},
22
+ local.has_lb_spec ? { "foundation/load-balancer" = azurerm_application_gateway.env[0].id } : {},
23
+ (local.dns_managed && local.has_dns && local.has_lb_spec) ? { "foundation/dns" = azurerm_dns_a_record.env[0].fqdn } : {},
24
+ # HEC maps to the front-door public IP when present. When there is no LB it is
25
+ # a post-config step (reported via deployment.step, not a discrete resource).
26
+ (local.has_hec && local.has_lb_spec) ? { "ingest/hec" = azurerm_public_ip.appgw[0].ip_address } : {},
27
+ )
28
+ }
29
+
30
+ output "resource_refs" {
31
+ description = "Map of plan_key -> external cloud reference (VM id / resource id / fqdn). Drives per-resource status-back."
32
+ value = merge(
33
+ { for k, vm in azurerm_linux_virtual_machine.node : k => vm.id },
34
+ local.infra_refs,
35
+ )
36
+ }
37
+
38
+ output "subnet_id" {
39
+ description = "Primary compute subnet id (the allocated subnet for shared/existing; the private subnet for dedicated)."
40
+ value = local.compute_subnet_ids[0]
41
+ }
42
+
43
+ output "security_group_id" {
44
+ description = "Id of the compute-node network security group (NSG)."
45
+ value = azurerm_network_security_group.node.id
46
+ }
47
+
48
+ output "network_id" {
49
+ description = "Id of the network (VNet) this stack runs in — the created VNet for dedicated, the looked-up VNet for shared/existing."
50
+ value = local.network_id
51
+ }
52
+
53
+ output "instance_ids" {
54
+ description = "plan_key -> VM id for compute nodes."
55
+ value = { for k, vm in azurerm_linux_virtual_machine.node : k => vm.id }
56
+ }
57
+
58
+ output "instance_private_ips" {
59
+ description = "plan_key -> private IP for compute nodes."
60
+ value = { for k, nic in azurerm_network_interface.node : k => nic.private_ip_address }
61
+ }
62
+
63
+ output "node_fqdns" {
64
+ description = <<-EOT
65
+ plan_key -> function FQDN (e.g. idx1.<dns_domain>, sh1.<dns_domain>) for each
66
+ compute node. The bring-up layer uses these to build its inventory (cluster
67
+ peer resolution). Populated whenever dns_domain is set; the matching A records
68
+ are only created when a private zone is present (create_private_zone or
69
+ private_zone_id). Empty when dns_domain is unset.
70
+ EOT
71
+ value = local.node_fqdns
72
+ }
@@ -0,0 +1,498 @@
1
+ # =============================================================================
2
+ # Azure environment module — input variables.
3
+ #
4
+ # One BYOL environment (a "stack") = one dedicated subnet carved from a Veltrix
5
+ # or customer VNet, plus one compute VM per plan item and the storage / secrets /
6
+ # TLS / LB / DNS a tool tier needs. The shared VNet is looked up (data source),
7
+ # NEVER created here; a dedicated (BYOC) VNet IS created.
8
+ #
9
+ # The `plan` list is the SAME topology the app persists as its BYOL resource
10
+ # rows. Keying compute by `plan_key` is the contract that lets the CI apply
11
+ # report `resource.status` back per row (see outputs.tf).
12
+ #
13
+ # This file mirrors sdk/opentofu/modules/aws/variables.tf 1:1. The SPEC-DERIVED
14
+ # block (foundation_kinds, compute_kinds, security_rules, load_balancer,
15
+ # dns_prefixes, waf_enabled, alb_auth) is byte-identical across all clouds — it
16
+ # is what `renderInfraVars` emits. The DEPLOYMENT vars keep the same names as AWS
17
+ # and adapt only the Azure specifics (AMI -> image, instance types -> vm sizes,
18
+ # route53 -> Azure DNS zone, ACM arn -> Key Vault cert id).
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 + tag 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 Azure). One of aws|azure|gcp|hetzner."
40
+ type = string
41
+ default = "azure"
42
+ }
43
+
44
+ variable "region" {
45
+ description = "Azure location to deploy into, e.g. eastus. (Named `region` for cross-cloud contract parity.)"
46
+ type = string
47
+ }
48
+
49
+ # --- Network (mode: hosted-shared vs BYOC dedicated/existing) --------------
50
+ # network_mode is a DEPLOYMENT-TARGET var set by the worker per environment — it
51
+ # is NOT part of the app's InfraSpec (which describes only the tool). One app
52
+ # spec deploys hosted OR into a customer's own account, any cloud.
53
+
54
+ variable "network_mode" {
55
+ description = <<-EOT
56
+ How the environment's network is sourced:
57
+ shared — Veltrix-hosted: data-source the shared VNet (network_ref in
58
+ network_resource_group) and create the allocated per-stack
59
+ subnet (subnet_cidr).
60
+ dedicated — BYOC: CREATE a fresh VNet (vpc_cidr) + an App Gateway subnet +
61
+ a private compute subnet + a NAT gateway for private egress,
62
+ isolated per env.
63
+ existing — BYOC: data-source a customer-designated VNet (network_ref in
64
+ network_resource_group) and create subnets inside it.
65
+ EOT
66
+ type = string
67
+ default = "shared"
68
+
69
+ validation {
70
+ condition = contains(["shared", "dedicated", "existing"], var.network_mode)
71
+ error_message = "network_mode must be one of: shared, dedicated, existing."
72
+ }
73
+ }
74
+
75
+ variable "network_ref" {
76
+ description = <<-EOT
77
+ Name of the VNet to deploy into, for network_mode = shared|existing (a
78
+ Veltrix-managed name such as `vnet-veltrix-eastus-shared`, or a customer VNet
79
+ name). Resolved together with network_resource_group. Ignored for
80
+ network_mode = dedicated (the VNet is created).
81
+ EOT
82
+ type = string
83
+ default = ""
84
+ }
85
+
86
+ variable "network_lookup_by" {
87
+ description = <<-EOT
88
+ Contract parity with the other clouds. Azure resolves the VNet by NAME
89
+ (network_ref) + resource group (network_resource_group) via the
90
+ azurerm_virtual_network data source — there is no by-id VNet data source — so
91
+ "id" is accepted but treated the same as "name". Kept for uniform tfvars.
92
+ EOT
93
+ type = string
94
+ default = "name"
95
+
96
+ validation {
97
+ condition = contains(["name", "id", "tag"], var.network_lookup_by)
98
+ error_message = "network_lookup_by must be one of: name, id, tag (Azure resolves by name+resource group regardless)."
99
+ }
100
+ }
101
+
102
+ variable "network_resource_group" {
103
+ description = "Resource group holding the existing VNet (network_ref), for network_mode = shared|existing. The per-stack subnet is created into this RG (subnets belong to their VNet's RG). Ignored for dedicated."
104
+ type = string
105
+ default = ""
106
+ }
107
+
108
+ variable "subnet_cidr" {
109
+ description = <<-EOT
110
+ Per-stack compute subnet CIDR for network_mode = shared|existing
111
+ (IPAM-allocated for hosted). Ignored for dedicated (subnets are carved from
112
+ vpc_cidr). Empty is allowed only when dedicated.
113
+ EOT
114
+ type = string
115
+ default = ""
116
+
117
+ validation {
118
+ condition = var.subnet_cidr == "" || can(cidrhost(var.subnet_cidr, 0))
119
+ error_message = "subnet_cidr must be empty or a valid IPv4 CIDR, e.g. 10.20.16.0/24."
120
+ }
121
+ }
122
+
123
+ variable "vpc_cidr" {
124
+ description = <<-EOT
125
+ Address space for the VNet CREATED when network_mode = dedicated (BYOC). An
126
+ App Gateway /20 (index 0) and a private compute /20 (index 1) are carved from
127
+ it. Named `vpc_cidr` for cross-cloud contract parity. Ignored otherwise.
128
+ EOT
129
+ type = string
130
+ default = "10.60.0.0/16"
131
+
132
+ validation {
133
+ condition = can(cidrhost(var.vpc_cidr, 0))
134
+ error_message = "vpc_cidr must be a valid IPv4 CIDR, e.g. 10.60.0.0/16."
135
+ }
136
+ }
137
+
138
+ variable "appgw_subnet_cidr" {
139
+ description = <<-EOT
140
+ Dedicated subnet CIDR for the Application Gateway when network_mode =
141
+ shared|existing (an Azure Application Gateway REQUIRES its own empty subnet —
142
+ it cannot share the compute subnet). This is the Azure analogue of the AWS
143
+ module's extra_lb_subnet_ids. Required for a load-balancer in shared|existing;
144
+ ignored for dedicated (the module carves the App Gateway subnet from vpc_cidr).
145
+ EOT
146
+ type = string
147
+ default = ""
148
+
149
+ validation {
150
+ condition = var.appgw_subnet_cidr == "" || can(cidrhost(var.appgw_subnet_cidr, 0))
151
+ error_message = "appgw_subnet_cidr must be empty or a valid IPv4 CIDR, e.g. 10.20.17.0/26."
152
+ }
153
+ }
154
+
155
+ variable "admin_cidr" {
156
+ description = "CIDR allowed to reach management ports + the web UI when there is no load balancer. Typically the Veltrix control-plane / customer bastion range."
157
+ type = string
158
+ default = "10.0.0.0/8"
159
+ }
160
+
161
+ # --- The plan (topology) --------------------------------------------------
162
+
163
+ variable "plan" {
164
+ description = <<-EOT
165
+ Ordered resource plan from the app topology. One object per resource the
166
+ environment needs. `plan_key` is the stable key that maps 1:1 to a BYOL
167
+ resource row; the CI apply emits resource.status per plan_key.
168
+ EOT
169
+ type = list(object({
170
+ plan_key = string
171
+ tier = string # foundation | control-plane | data | search | ingest
172
+ kind = string # network | storage | indexer | search-head | management-node | ...
173
+ name = optional(string, "")
174
+ role = optional(string, "")
175
+ region = optional(string, null)
176
+ # Multi-AZ placement: the Azure availability zone ("1"|"2"|"3") this node is
177
+ # pinned to (null = non-zonal). Azure subnets are regional; only the VM zone varies.
178
+ zone = optional(string, null)
179
+ roles = optional(list(string), [])
180
+ }))
181
+
182
+ validation {
183
+ condition = length(var.plan) > 0
184
+ error_message = "plan must contain at least one item."
185
+ }
186
+ }
187
+
188
+ # --- Per-tier compute sizing (Azure VM sizes; mirrors AWS instance_types) ---
189
+
190
+ variable "vm_sizes" {
191
+ description = <<-EOT
192
+ Per-tier Azure VM size override, keyed by tier
193
+ (foundation|control-plane|data|search|ingest). Missing tiers fall back to
194
+ vm_size. Kind-level overrides go in vm_sizes_by_kind. (Mirrors AWS
195
+ instance_types.)
196
+ EOT
197
+ type = map(string)
198
+ default = {}
199
+ }
200
+
201
+ variable "vm_sizes_by_kind" {
202
+ description = "Per-kind Azure VM size override (wins over vm_sizes). e.g. { indexer = \"Standard_D8s_v5\" }. (Mirrors AWS instance_types_by_kind.)"
203
+ type = map(string)
204
+ default = {}
205
+ }
206
+
207
+ variable "vm_size" {
208
+ description = "Fallback Azure VM size for any compute plan item with no tier/kind override. (Mirrors AWS default_instance_type.)"
209
+ type = string
210
+ default = "Standard_B2s"
211
+ }
212
+
213
+ variable "os_disk_gb" {
214
+ description = "OS managed-disk size (GiB) for each compute node. (Mirrors AWS root_volume_gb.)"
215
+ type = number
216
+ default = 100
217
+ }
218
+
219
+ # --- Machine image / access -----------------------------------------------
220
+
221
+ variable "image_ref" {
222
+ description = <<-EOT
223
+ Managed image / Shared Image Gallery version resource id for compute nodes.
224
+ If empty, the module falls back to the latest Ubuntu 22.04 LTS marketplace
225
+ image (scaffold only) — production MUST supply a hardened, tool-preinstalled
226
+ image id. (Mirrors AWS ami_id.)
227
+ EOT
228
+ type = string
229
+ default = ""
230
+ }
231
+
232
+ variable "admin_username" {
233
+ description = "Admin username created on each compute VM. Azure disallows reserved names (admin, root, ...)."
234
+ type = string
235
+ default = "veltrixadmin"
236
+ }
237
+
238
+ variable "admin_ssh_public_key" {
239
+ description = <<-EOT
240
+ Optional OpenSSH public key for break-glass SSH access. When set, VMs use
241
+ key-only auth (password auth disabled). When empty, the module generates a
242
+ complex random password per apply and enables password auth (so a minimal
243
+ plan still applies). (Mirrors AWS key_name.)
244
+ EOT
245
+ type = string
246
+ default = ""
247
+ }
248
+
249
+ # --- Foundation options ----------------------------------------------------
250
+
251
+ variable "dns_domain" {
252
+ 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."
253
+ type = string
254
+ default = ""
255
+ }
256
+
257
+ variable "dns_mode" {
258
+ description = <<-EOT
259
+ How the PUBLIC (analyst-facing) DNS name + TLS cert are handled. The PRIVATE
260
+ intra-cluster zone is always created in the deploy account regardless.
261
+ managed — the module creates the public A record in public_dns_zone_name
262
+ (works for hosted on the Veltrix zone AND a BYOC customer-owned
263
+ zone — both live in the deploy subscription). See certificate_arn
264
+ for the TLS handling (Azure has no in-module public CA issuance).
265
+ delegated — BYOC cross-account: the WORKER writes the public record into
266
+ Veltrix's zone; the module makes NO public record and uses
267
+ certificate_arn on the App Gateway listener instead.
268
+ private-only — no public DNS; reached via the customer network (ZTNA/VPN).
269
+ EOT
270
+ type = string
271
+ default = "managed"
272
+
273
+ validation {
274
+ condition = contains(["managed", "delegated", "private-only"], var.dns_mode)
275
+ error_message = "dns_mode must be one of: managed, delegated, private-only."
276
+ }
277
+ }
278
+
279
+ variable "public_dns_zone_name" {
280
+ description = <<-EOT
281
+ Azure Public DNS zone NAME for the public record, when dns_mode = managed
282
+ (e.g. `veltrixsecops.com`). The zone must live in the DEPLOY subscription
283
+ (Veltrix's for hosted, the customer's for BYOC customer-owned). This holds the
284
+ ROLE of AWS's route53_zone_id.
285
+ EOT
286
+ type = string
287
+ default = ""
288
+ }
289
+
290
+ variable "public_dns_rg" {
291
+ description = "Resource group of public_dns_zone_name. Required when dns_mode = managed and the plan carries a dns item."
292
+ type = string
293
+ default = ""
294
+ }
295
+
296
+ variable "certificate_arn" {
297
+ description = <<-EOT
298
+ Key Vault certificate/secret id for the App Gateway HTTPS listener
299
+ (secret-identifier form, e.g.
300
+ https://<vault>.vault.azure.net/secrets/<name>/<version>). Named
301
+ `certificate_arn` for cross-cloud contract parity; on Azure it holds a Key
302
+ Vault cert id, NOT an ACM ARN.
303
+
304
+ Unlike AWS (which auto-issues a DNS-validated ACM cert in dns_mode = managed),
305
+ Azure has NO in-module public CA issuance — the App Gateway consumes a Key
306
+ Vault certificate that must be provisioned out-of-band. Therefore BOTH managed
307
+ and delegated modes reference this var for the HTTPS listener. Pair it with
308
+ appgw_identity_id (a user-assigned identity with GET access to that Key Vault).
309
+ Empty => the App Gateway serves HTTP only (no HTTPS listener).
310
+ EOT
311
+ type = string
312
+ default = ""
313
+ }
314
+
315
+ variable "appgw_identity_id" {
316
+ description = <<-EOT
317
+ Resource id of a user-assigned managed identity that has GET access to the Key
318
+ Vault holding certificate_arn. Required for the HTTPS listener (App Gateway
319
+ reads the KV cert through this identity). Provisioning the identity + KV access
320
+ is a worker/caller responsibility (analogous to AWS's cross-account ACM
321
+ validation in delegated mode). Empty => no HTTPS listener.
322
+ EOT
323
+ type = string
324
+ default = ""
325
+ }
326
+
327
+ variable "web_ingress_cidr" {
328
+ description = <<-EOT
329
+ CIDR allowed to reach the PUBLIC App Gateway on 443/80. Declared for
330
+ cross-cloud contract parity. NOTE: Azure Application Gateway v2 subnets require
331
+ permissive GatewayManager/AzureLoadBalancer NSG rules, so source-IP restriction
332
+ is best expressed as a WAF custom rule rather than a subnet NSG — that is a
333
+ follow-on and is NOT wired here (the module does not narrow ingress by this var).
334
+ EOT
335
+ type = string
336
+ default = "0.0.0.0/0"
337
+ }
338
+
339
+ variable "alb_auth" {
340
+ description = <<-EOT
341
+ Optional OIDC/Cognito-style MFA at the front door, in front of the tool's web
342
+ UI. Shape kept byte-identical to AWS for uniform tfvars. NOTE: Azure
343
+ Application Gateway has NO direct equivalent of AWS ALB authenticate-cognito;
344
+ the Azure follow-on is Azure AD Application Proxy / Front Door + Entra ID. This
345
+ var is ACCEPTED but NO-OP'd on Azure (documented gap) — see main.tf.
346
+ EOT
347
+ type = object({
348
+ enabled = optional(bool, false)
349
+ user_pool_arn = optional(string, "")
350
+ user_pool_client_id = optional(string, "")
351
+ user_pool_domain = optional(string, "")
352
+ })
353
+ default = {}
354
+
355
+ validation {
356
+ condition = !var.alb_auth.enabled || (
357
+ var.alb_auth.user_pool_arn != "" &&
358
+ var.alb_auth.user_pool_client_id != "" &&
359
+ var.alb_auth.user_pool_domain != ""
360
+ )
361
+ error_message = "When alb_auth.enabled is true, user_pool_arn, user_pool_client_id and user_pool_domain are all required."
362
+ }
363
+ }
364
+
365
+ variable "create_private_zone" {
366
+ description = <<-EOT
367
+ Create an Azure Private DNS zone for dns_domain, linked to the deploy VNet, to
368
+ hold per-node function FQDNs (idx1.<domain>, sh1.<domain>, ...). Ignored when
369
+ dns_domain is empty. Mutually complementary with private_zone_id: set
370
+ private_zone_id instead to reuse an existing zone.
371
+ EOT
372
+ type = bool
373
+ default = false
374
+ }
375
+
376
+ variable "private_zone_id" {
377
+ description = <<-EOT
378
+ Set non-empty to REUSE an existing Private DNS zone (named dns_domain) instead
379
+ of creating one; takes precedence over create_private_zone. Kept as
380
+ `private_zone_id` for cross-cloud parity — on Azure the zone is addressed by
381
+ NAME (dns_domain) + private_zone_resource_group, so this value is only used as
382
+ the "reuse an existing zone" flag. Leave empty (and set create_private_zone) to
383
+ have the module create the zone.
384
+ EOT
385
+ type = string
386
+ default = ""
387
+ }
388
+
389
+ variable "private_zone_resource_group" {
390
+ description = "Resource group of the existing Private DNS zone to reuse (when private_zone_id is set). Ignored when the module creates the zone (records go into the per-stack RG)."
391
+ type = string
392
+ default = ""
393
+ }
394
+
395
+ # --- Declarative infra spec (rendered from the app's InfraSpec) -----------
396
+ # These are what make the module tool-agnostic. The app declares its ports /
397
+ # front-door / DNS as DATA (sdk/src/opentofu/spec.ts InfraSpec) and the SDK
398
+ # renders them here. NOTHING below is app-specific. This block is BYTE-IDENTICAL
399
+ # across the aws/azure/gcp/hetzner modules — it is exactly what renderInfraVars emits.
400
+
401
+ variable "foundation_kinds" {
402
+ description = <<-EOT
403
+ Plan `kind`s the module realizes as shared FOUNDATION infra (not compute).
404
+ Any plan item whose kind is NOT in this set (and not named by compute_kinds)
405
+ is a compute node. Kept in sync with FOUNDATION_KINDS in spec.ts.
406
+ EOT
407
+ type = list(string)
408
+ default = [
409
+ "network", "storage", "secrets", "tls",
410
+ "load-balancer", "dns", "license-file", "hec",
411
+ ]
412
+ }
413
+
414
+ variable "compute_kinds" {
415
+ description = <<-EOT
416
+ Optional explicit allow-list of compute kinds. When non-empty, ONLY these
417
+ kinds become compute nodes. When empty (default), compute = any plan item
418
+ whose kind is not in foundation_kinds (so an app's roles are compute
419
+ automatically).
420
+ EOT
421
+ type = list(string)
422
+ default = []
423
+ }
424
+
425
+ variable "security_rules" {
426
+ description = <<-EOT
427
+ Ingress rules from the app's InfraSpec. Each rule opens `port` to one or
428
+ more `sources`: "self" (peer nodes — the node ASG referencing itself),
429
+ "alb" (the front-door / App Gateway subnet; ignored when there is no load
430
+ balancer), or "admin" (var.admin_cidr). Replaces any hardcoded, tool-specific
431
+ port list.
432
+ EOT
433
+ type = list(object({
434
+ port = number
435
+ protocol = optional(string, "tcp")
436
+ sources = list(string)
437
+ description = optional(string, "")
438
+ }))
439
+ default = []
440
+
441
+ validation {
442
+ condition = alltrue([
443
+ for r in var.security_rules : alltrue([
444
+ for s in r.sources : contains(["self", "alb", "admin"], s)
445
+ ])
446
+ ])
447
+ error_message = "Every security_rules[*].sources entry must be one of: self, alb, admin."
448
+ }
449
+ }
450
+
451
+ variable "load_balancer" {
452
+ description = <<-EOT
453
+ Front-door spec from the app's InfraSpec. Null for headless / forwarder-only
454
+ tools. When set (and the plan carries a load-balancer item), the module builds
455
+ the App Gateway backend pool + health probe + listeners from this.
456
+ `target_kinds` are the compute kinds that sit behind the front door.
457
+ EOT
458
+ type = object({
459
+ target_port = number
460
+ target_protocol = optional(string, "HTTP")
461
+ health_check_path = string
462
+ health_check_matcher = optional(string, "200-399")
463
+ health_check_protocol = optional(string, "")
464
+ target_kinds = list(string)
465
+ listener_port = optional(number, 443)
466
+ })
467
+ default = null
468
+ }
469
+
470
+ variable "dns_prefixes" {
471
+ description = <<-EOT
472
+ kind -> DNS label prefix for per-node function FQDNs (e.g.
473
+ { indexer = "idx", search-head = "sh", cluster-manager = "mgmt" }). A compute
474
+ kind absent from the map falls back to the kind string itself.
475
+ EOT
476
+ type = map(string)
477
+ default = {}
478
+ }
479
+
480
+ variable "waf_enabled" {
481
+ description = "Attach a WAF policy (OWASP managed rules) to the App Gateway (WAF_v2 SKU). Ignored when there is no load balancer."
482
+ type = bool
483
+ default = true
484
+ }
485
+
486
+ # --- Tags (threaded onto every taggable resource) --------------------------
487
+
488
+ variable "tags" {
489
+ description = <<-EOT
490
+ Canonical Veltrix tag set applied to every taggable resource
491
+ (Veltrix:Customer, Veltrix:Environment, Veltrix:App, Veltrix:ManagedBy,
492
+ CostCenter, Owner, ...). The apply identity is scoped by
493
+ Veltrix:ManagedBy = Veltrix, so this map MUST include it. NOTE: Azure subnets,
494
+ NSG rules, storage containers, and NIC associations are not taggable and carry
495
+ no tags.
496
+ EOT
497
+ type = map(string)
498
+ }
@@ -0,0 +1,27 @@
1
+ # =============================================================================
2
+ # Azure environment module — provider/version pinning (OpenTofu-compatible HCL).
3
+ #
4
+ # OpenTofu resolves providers from registry.opentofu.org by default but the
5
+ # canonical `hashicorp/azurerm` source address is mirrored there, so this block
6
+ # is 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
+ # NOTE: this module declares NO `provider "azurerm"` block — the platform
10
+ # render+apply worker configures the provider (subscription_id, features {}, and
11
+ # the customer credential for BYOC) at the root, and the module inherits it.
12
+ # =============================================================================
13
+
14
+ terraform {
15
+ required_version = ">= 1.6.0"
16
+
17
+ required_providers {
18
+ azurerm = {
19
+ source = "hashicorp/azurerm"
20
+ version = "~> 4.0"
21
+ }
22
+ random = {
23
+ source = "hashicorp/random"
24
+ version = "~> 3.6"
25
+ }
26
+ }
27
+ }