@veltrixsecops/app-sdk 3.2.0 → 3.5.0

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 +1 -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,90 @@
1
+ # =============================================================================
2
+ # AWS 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.
9
+ # =============================================================================
10
+
11
+ locals {
12
+ # Foundation-tier refs, included only when the tier is in the plan.
13
+ infra_refs = merge(
14
+ { "foundation/network" = local.network_id },
15
+ local.has_storage ? { "foundation/storage" = aws_s3_bucket.objstore[0].arn } : {},
16
+ local.has_secrets ? { "foundation/secrets" = aws_secretsmanager_secret.env[0].arn } : {},
17
+ local.has_license_file ? { "foundation/license-file" = aws_secretsmanager_secret.license[0].arn } : {},
18
+ (local.dns_managed && local.has_tls && var.dns_domain != "") ? { "foundation/tls" = aws_acm_certificate.env[0].arn } : {},
19
+ local.has_lb ? { "foundation/load-balancer" = aws_lb.env[0].arn } : {},
20
+ local.dns_managed && local.has_dns && local.has_lb ? { "foundation/dns" = aws_route53_record.env[0].fqdn } : {},
21
+ # HEC is realized as an endpoint on the LB (external) or on the standalone
22
+ # instance; it maps to the LB ref when present. When there is no LB it is a
23
+ # post-config step (reported via deployment.step, not a discrete resource).
24
+ local.has_hec && local.has_lb ? { "ingest/hec" = aws_lb.env[0].dns_name } : {},
25
+ )
26
+ }
27
+
28
+ output "resource_refs" {
29
+ description = "Map of plan_key -> external cloud reference (instance id / arn / fqdn). Drives per-resource status-back."
30
+ value = merge(
31
+ { for k, inst in aws_instance.node : k => inst.id },
32
+ local.infra_refs,
33
+ )
34
+ }
35
+
36
+ output "subnet_id" {
37
+ description = "Primary compute subnet id (the allocated subnet for shared/existing; the first private subnet for dedicated)."
38
+ value = local.compute_subnet_ids[0]
39
+ }
40
+
41
+ output "security_group_id" {
42
+ description = "Id of the compute-node security group."
43
+ value = aws_security_group.splunk.id
44
+ }
45
+
46
+ output "network_id" {
47
+ description = "Id of the network (VPC) this stack runs in — the created VPC for dedicated, the looked-up VPC for shared/existing."
48
+ value = local.network_id
49
+ }
50
+
51
+ output "vpc_id" {
52
+ description = "Alias of network_id (AWS VPC id) for back-compat."
53
+ value = local.network_id
54
+ }
55
+
56
+ output "instance_ids" {
57
+ description = "plan_key -> EC2 instance id for compute nodes."
58
+ value = { for k, inst in aws_instance.node : k => inst.id }
59
+ }
60
+
61
+ output "instance_private_ips" {
62
+ description = "plan_key -> private IP for compute nodes."
63
+ value = { for k, inst in aws_instance.node : k => inst.private_ip }
64
+ }
65
+
66
+ output "node_fqdns" {
67
+ description = <<-EOT
68
+ plan_key -> function FQDN (e.g. idx1.<dns_domain>, sh1.<dns_domain>) for each
69
+ compute node. The bring-up layer uses these to build its cluster inventory
70
+ (cluster peer resolution). Populated whenever dns_domain is set; the
71
+ matching A records are only created when a private zone is present
72
+ (create_private_zone or private_zone_id). Empty when dns_domain is unset.
73
+ EOT
74
+ value = local.node_fqdns
75
+ }
76
+
77
+ output "vpc_cidr" {
78
+ description = "CIDR of the VPC this stack runs in — the created VPC (dedicated) or the allocated subnet block otherwise. Used by the root to wire cross-region peering routes + peer security-group rules."
79
+ value = local.is_dedicated ? var.vpc_cidr : var.subnet_cidr
80
+ }
81
+
82
+ output "private_route_table_id" {
83
+ description = "Id of the private (compute) route table for dedicated fabrics; empty otherwise. The root adds cross-region VPC-peering routes to it for multi-region satellites."
84
+ value = local.is_dedicated ? aws_route_table.private[0].id : ""
85
+ }
86
+
87
+ output "private_zone_id" {
88
+ description = "Id of the private DNS zone (when dns_domain is set + a zone is created/supplied); empty otherwise. The root associates it with multi-region satellite VPCs so their nodes resolve the main region's function FQDNs."
89
+ value = local.want_private_dns ? local.private_zone_id : ""
90
+ }
@@ -0,0 +1,437 @@
1
+ # =============================================================================
2
+ # AWS environment module — input variables.
3
+ #
4
+ # One BYOL environment (a "stack") = one dedicated /24 subnet carved from the
5
+ # shared Veltrix Network (VPC), plus one compute resource per plan item and the
6
+ # storage / secrets / TLS / LB / DNS an app tier needs. The shared VPC is
7
+ # Veltrix-owned and is looked up (data source), NEVER created here.
8
+ #
9
+ # The `plan` list is the SAME topology the app persists as its BYOL-resource
10
+ # rows (see the app's byolTopology). Keying compute by
11
+ # `plan_key` is the contract that lets the CI apply report `resource.status`
12
+ # back per row (see outputs.tf + ci/emit-status.mjs).
13
+ # =============================================================================
14
+
15
+ # --- Identity / naming / tenancy ------------------------------------------
16
+
17
+ variable "app_id" {
18
+ description = "Owning app id, e.g. my-app. Used for naming + state key."
19
+ type = string
20
+ }
21
+
22
+ variable "customer_id" {
23
+ description = "Tenant (customer) id. Used for naming + tag Veltrix:Customer."
24
+ type = string
25
+ }
26
+
27
+ variable "infrastructure_id" {
28
+ description = "BYOL infrastructure (environment/stack) id. Unique per stack."
29
+ type = string
30
+ }
31
+
32
+ variable "provider_code" {
33
+ description = "Cloud provider code (informational; this module is AWS). One of aws|azure|gcp|hetzner."
34
+ type = string
35
+ default = "aws"
36
+ }
37
+
38
+ variable "region" {
39
+ description = "AWS region to deploy into, e.g. us-east-1."
40
+ type = string
41
+ }
42
+
43
+ # --- Network (mode: hosted-shared vs BYOC dedicated/existing) --------------
44
+ # network_mode is a DEPLOYMENT-TARGET var set by the worker per environment — it
45
+ # is NOT part of the app's InfraSpec (which describes only the tool). One app
46
+ # spec deploys hosted OR into a customer's own account, any cloud.
47
+
48
+ variable "network_mode" {
49
+ description = <<-EOT
50
+ How the environment's network is sourced:
51
+ shared — Veltrix-hosted: data-source the shared VPC (network_ref) and
52
+ create the IPAM-allocated per-stack subnet (subnet_cidr).
53
+ dedicated — BYOC: CREATE a fresh VPC (vpc_cidr) + public/private subnets
54
+ across 2 AZs + internet gateway + NAT, isolated per env.
55
+ existing — BYOC: data-source a customer-designated VPC (network_ref) and
56
+ create subnets inside it (subnet_cidr).
57
+ EOT
58
+ type = string
59
+ default = "shared"
60
+
61
+ validation {
62
+ condition = contains(["shared", "dedicated", "existing"], var.network_mode)
63
+ error_message = "network_mode must be one of: shared, dedicated, existing."
64
+ }
65
+ }
66
+
67
+ variable "network_ref" {
68
+ description = <<-EOT
69
+ Reference to the VPC to deploy into, for network_mode = shared|existing.
70
+ Matched against the VPC's `Name` tag by default (a Veltrix-managed name such
71
+ as `vpc-veltrix-use1-shared`, or a customer VPC name); pass a vpc-id and set
72
+ network_lookup_by = "id" to resolve by id. Ignored for network_mode =
73
+ dedicated (the VPC is created).
74
+ EOT
75
+ type = string
76
+ default = ""
77
+ }
78
+
79
+ variable "network_lookup_by" {
80
+ description = "How to resolve network_ref: `tag` (Name tag) or `id` (vpc-xxxx). Used for shared|existing."
81
+ type = string
82
+ default = "tag"
83
+
84
+ validation {
85
+ condition = contains(["tag", "id"], var.network_lookup_by)
86
+ error_message = "network_lookup_by must be either \"tag\" or \"id\"."
87
+ }
88
+ }
89
+
90
+ variable "subnet_cidr" {
91
+ description = <<-EOT
92
+ Per-stack subnet CIDR for network_mode = shared|existing (IPAM-allocated for
93
+ hosted). Ignored for dedicated (subnets are carved from vpc_cidr). Empty is
94
+ allowed only when dedicated.
95
+ EOT
96
+ type = string
97
+ default = ""
98
+
99
+ validation {
100
+ condition = var.subnet_cidr == "" || can(cidrhost(var.subnet_cidr, 0))
101
+ error_message = "subnet_cidr must be empty or a valid IPv4 CIDR, e.g. 10.20.16.0/24."
102
+ }
103
+ }
104
+
105
+ variable "vpc_cidr" {
106
+ description = <<-EOT
107
+ CIDR for the VPC CREATED when network_mode = dedicated (BYOC). Public and
108
+ private /20 subnets are carved from it across 2 AZs. Ignored otherwise.
109
+ EOT
110
+ type = string
111
+ default = "10.60.0.0/16"
112
+
113
+ validation {
114
+ condition = can(cidrhost(var.vpc_cidr, 0))
115
+ error_message = "vpc_cidr must be a valid IPv4 CIDR, e.g. 10.60.0.0/16."
116
+ }
117
+ }
118
+
119
+ variable "admin_cidr" {
120
+ description = "CIDR allowed to reach management ports + the web UI when there is no ALB. Typically the Veltrix control-plane / customer bastion range."
121
+ type = string
122
+ default = "10.0.0.0/8"
123
+ }
124
+
125
+ variable "peer_cidrs" {
126
+ description = <<-EOT
127
+ CIDRs of PEERED VPCs whose nodes are part of this cluster (multi-region
128
+ indexer/search-head satellites). Each is allowed on the same intra-cluster
129
+ ("self") ports so cross-region replication/management works over VPC peering.
130
+ Empty (the single-region default) adds no rules.
131
+ EOT
132
+ type = list(string)
133
+ default = []
134
+ }
135
+
136
+ # --- The plan (topology) --------------------------------------------------
137
+
138
+ variable "plan" {
139
+ description = <<-EOT
140
+ Ordered resource plan from the app topology. One object per resource the
141
+ environment needs. `plan_key` is the stable key that maps 1:1 to a
142
+ BYOL-resource row; the CI apply emits resource.status per plan_key.
143
+ EOT
144
+ type = list(object({
145
+ plan_key = string
146
+ tier = string # foundation | control-plane | data | search | ingest
147
+ kind = string # network | storage | indexer | search-head | management-node | ...
148
+ name = optional(string, "")
149
+ role = optional(string, "")
150
+ region = optional(string, null)
151
+ # Multi-AZ placement: the availability zone this node is pinned to (null = default).
152
+ zone = optional(string, null)
153
+ # Consolidated control-plane roles this instance runs (drives bring-up config).
154
+ roles = optional(list(string), [])
155
+ }))
156
+
157
+ validation {
158
+ condition = length(var.plan) > 0
159
+ error_message = "plan must contain at least one item."
160
+ }
161
+ }
162
+
163
+ # --- Per-tier compute sizing ----------------------------------------------
164
+
165
+ variable "instance_types" {
166
+ description = <<-EOT
167
+ Per-tier EC2 instance type override, keyed by tier
168
+ (foundation|control-plane|data|search|ingest). Missing tiers fall back to
169
+ default_instance_type. Kind-level overrides go in instance_types_by_kind.
170
+ EOT
171
+ type = map(string)
172
+ default = {}
173
+ }
174
+
175
+ variable "instance_types_by_kind" {
176
+ description = "Per-kind EC2 instance type override (wins over instance_types). e.g. { indexer = \"m6i.2xlarge\" }."
177
+ type = map(string)
178
+ default = {}
179
+ }
180
+
181
+ variable "default_instance_type" {
182
+ description = "Fallback EC2 instance type for any compute plan item with no tier/kind override."
183
+ type = string
184
+ default = "t2.medium"
185
+ }
186
+
187
+ variable "root_volume_gb" {
188
+ description = "Root EBS volume size (GiB) for each compute node."
189
+ type = number
190
+ default = 100
191
+ }
192
+
193
+ # --- Machine image / access -----------------------------------------------
194
+
195
+ variable "ami_id" {
196
+ description = <<-EOT
197
+ AMI for app compute nodes. If empty, the module falls back to the latest
198
+ Amazon Linux 2023 image (scaffold only) — production MUST supply a hardened,
199
+ app-preinstalled (custom) AMI id per region.
200
+ EOT
201
+ type = string
202
+ default = ""
203
+ }
204
+
205
+ variable "key_name" {
206
+ description = "Optional EC2 key pair name for SSH break-glass access. Empty = none."
207
+ type = string
208
+ default = ""
209
+ }
210
+
211
+ # --- Foundation options ----------------------------------------------------
212
+
213
+ variable "dns_domain" {
214
+ 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."
215
+ type = string
216
+ default = ""
217
+ }
218
+
219
+ variable "private_dns_domain" {
220
+ description = "Domain for the INTERNAL (intra-cluster) private zone + node FQDNs, e.g. my-app.internal. Kept separate from dns_domain so a dedicated fabric with no public domain gets cm1/idx1/sh1 FQDNs without triggering public DNS/ACM. Empty falls back to dns_domain."
221
+ type = string
222
+ default = ""
223
+ }
224
+
225
+ variable "dns_mode" {
226
+ description = <<-EOT
227
+ How the PUBLIC (analyst-facing) DNS name + TLS cert are handled. The PRIVATE
228
+ intra-cluster zone is always created in the deploy account's VPC regardless.
229
+ managed — the module creates the public A/alias record + ACM cert in
230
+ route53_zone_id (works for hosted on the Veltrix zone AND
231
+ BYOC customer-owned zone — both live in the deploy account).
232
+ delegated — BYOC cross-account: the WORKER writes the public record +
233
+ ACM-validation record into Veltrix's zone; the module makes
234
+ NO public record/cert and uses certificate_arn on the
235
+ listener instead.
236
+ private-only — no public DNS; reached via the customer network (ZTNA/VPN).
237
+ EOT
238
+ type = string
239
+ default = "managed"
240
+
241
+ validation {
242
+ condition = contains(["managed", "delegated", "private-only"], var.dns_mode)
243
+ error_message = "dns_mode must be one of: managed, delegated, private-only."
244
+ }
245
+ }
246
+
247
+ variable "route53_zone_id" {
248
+ description = "Route53 hosted zone id for the PUBLIC record, when dns_mode = managed. The zone must be in the DEPLOY account (Veltrix's for hosted, the customer's for BYOC customer-owned)."
249
+ type = string
250
+ default = ""
251
+ }
252
+
253
+ variable "certificate_arn" {
254
+ description = <<-EOT
255
+ Pre-validated ACM certificate ARN for the HTTPS listener, used when
256
+ dns_mode = delegated (the worker provisions + cross-account-validates the
257
+ cert). Empty otherwise (the module issues its own ACM cert in dns_mode =
258
+ managed).
259
+ EOT
260
+ type = string
261
+ default = ""
262
+ }
263
+
264
+ variable "extra_lb_subnet_ids" {
265
+ description = <<-EOT
266
+ Additional subnet ids (in OTHER AZs) for the ALB, which needs >= 2 subnets
267
+ across >= 2 AZs. Used for network_mode = shared|existing where the per-stack
268
+ subnet is single-AZ. Ignored for dedicated (the module creates multi-AZ
269
+ public subnets itself). Required for a load-balancer in shared|existing.
270
+ EOT
271
+ type = list(string)
272
+ default = []
273
+ }
274
+
275
+ variable "web_ingress_cidr" {
276
+ description = <<-EOT
277
+ CIDR allowed to reach the PUBLIC ALB on 443/80 (the app's web UI). Defaults to
278
+ 0.0.0.0/0 because the v1 posture is a public ALB fronted by WAF + optional
279
+ Cognito MFA. Narrow this to an office/VPN range to restrict access.
280
+ EOT
281
+ type = string
282
+ default = "0.0.0.0/0"
283
+ }
284
+
285
+ variable "alb_auth" {
286
+ description = <<-EOT
287
+ Optional OIDC/Cognito MFA enforced at the ALB, in front of the app's web UI. When
288
+ enabled, the HTTPS listener authenticates against the given Cognito user pool
289
+ BEFORE forwarding to the search target group. Leave disabled (default) for a
290
+ v1 public ALB + WAF posture without ALB-level auth. When enabled, all three
291
+ user_pool_* fields are required.
292
+ EOT
293
+ type = object({
294
+ enabled = optional(bool, false)
295
+ user_pool_arn = optional(string, "")
296
+ user_pool_client_id = optional(string, "")
297
+ user_pool_domain = optional(string, "")
298
+ })
299
+ default = {}
300
+
301
+ validation {
302
+ condition = !var.alb_auth.enabled || (
303
+ var.alb_auth.user_pool_arn != "" &&
304
+ var.alb_auth.user_pool_client_id != "" &&
305
+ var.alb_auth.user_pool_domain != ""
306
+ )
307
+ error_message = "When alb_auth.enabled is true, user_pool_arn, user_pool_client_id and user_pool_domain are all required."
308
+ }
309
+ }
310
+
311
+ variable "create_private_zone" {
312
+ description = <<-EOT
313
+ Create a Route53 PRIVATE hosted zone for dns_domain, associated with the
314
+ shared VPC, to hold per-node function FQDNs (idx1.<domain>, sh1.<domain>, ...).
315
+ Ignored when dns_domain is empty. Mutually complementary with
316
+ private_zone_id: set private_zone_id instead to reuse an existing zone.
317
+ EOT
318
+ type = bool
319
+ default = false
320
+ }
321
+
322
+ variable "private_zone_id" {
323
+ description = <<-EOT
324
+ Existing Route53 private hosted zone id to publish per-node function FQDNs
325
+ into. Takes precedence over create_private_zone when non-empty. Leave empty
326
+ (and set create_private_zone) to have the module create the zone.
327
+ EOT
328
+ type = string
329
+ default = ""
330
+ }
331
+
332
+ # --- Declarative infra spec (rendered from the app's InfraSpec) -----------
333
+ # These are what make the module tool-agnostic. The app declares its ports /
334
+ # front-door / DNS as DATA (sdk/src/opentofu/spec.ts InfraSpec) and the SDK
335
+ # renders them here. NOTHING below is app-specific.
336
+
337
+ variable "foundation_kinds" {
338
+ description = <<-EOT
339
+ Plan `kind`s the module realizes as shared FOUNDATION infra (not compute).
340
+ Any plan item whose kind is NOT in this set (and not named by compute_kinds)
341
+ is a compute node. Kept in sync with FOUNDATION_KINDS in spec.ts.
342
+ EOT
343
+ type = list(string)
344
+ default = [
345
+ "network", "storage", "secrets", "tls",
346
+ "load-balancer", "dns", "license-file", "hec",
347
+ ]
348
+ }
349
+
350
+ variable "compute_kinds" {
351
+ description = <<-EOT
352
+ Optional explicit allow-list of compute kinds. When non-empty, ONLY these
353
+ kinds become compute nodes. When empty (default), compute = any plan item
354
+ whose kind is not in foundation_kinds (so an app's roles are compute
355
+ automatically).
356
+ EOT
357
+ type = list(string)
358
+ default = []
359
+ }
360
+
361
+ variable "security_rules" {
362
+ description = <<-EOT
363
+ SG ingress rules from the app's InfraSpec. Each rule opens `port` to one or
364
+ more `sources`: "self" (peer nodes — the node SG referencing itself),
365
+ "alb" (the public ALB SG; ignored when there is no load balancer), or
366
+ "admin" (var.admin_cidr). Replaces any hardcoded, tool-specific port list.
367
+ EOT
368
+ type = list(object({
369
+ port = number
370
+ protocol = optional(string, "tcp")
371
+ sources = list(string)
372
+ description = optional(string, "")
373
+ }))
374
+ default = []
375
+
376
+ validation {
377
+ condition = alltrue([
378
+ for r in var.security_rules : alltrue([
379
+ for s in r.sources : contains(["self", "alb", "admin"], s)
380
+ ])
381
+ ])
382
+ error_message = "Every security_rules[*].sources entry must be one of: self, alb, admin."
383
+ }
384
+ }
385
+
386
+ variable "load_balancer" {
387
+ description = <<-EOT
388
+ Front-door ALB spec from the app's InfraSpec. Null for headless / forwarder-
389
+ only tools. When set (and the plan carries a load-balancer item), the module
390
+ builds the target group + health check + listeners from this. `target_kinds`
391
+ are the compute kinds that sit behind the ALB.
392
+ EOT
393
+ type = object({
394
+ target_port = number
395
+ target_protocol = optional(string, "HTTP")
396
+ health_check_path = string
397
+ health_check_matcher = optional(string, "200-399")
398
+ health_check_protocol = optional(string, "")
399
+ target_kinds = list(string)
400
+ listener_port = optional(number, 443)
401
+ })
402
+ default = null
403
+ }
404
+
405
+ variable "dns_prefixes" {
406
+ description = <<-EOT
407
+ kind -> DNS label prefix for per-node function FQDNs (e.g.
408
+ { indexer = "idx", search-head = "sh", cluster-manager = "mgmt" }). A compute
409
+ kind absent from the map falls back to the kind string itself.
410
+ EOT
411
+ type = map(string)
412
+ default = {}
413
+ }
414
+
415
+ variable "waf_enabled" {
416
+ description = "Attach a WAFv2 web ACL (managed rules + IP rate limit) to the ALB. Ignored when there is no load balancer."
417
+ type = bool
418
+ default = true
419
+ }
420
+
421
+ variable "artifacts_bucket" {
422
+ description = "S3 bucket holding the app's staged install artifacts the bring-up layer installs from. Empty ('') disables the artifacts IAM grant on the node role."
423
+ type = string
424
+ default = ""
425
+ }
426
+
427
+ # --- Tags (threaded onto every resource) ----------------------------------
428
+
429
+ variable "tags" {
430
+ description = <<-EOT
431
+ Canonical Veltrix tag set applied to every resource
432
+ (Veltrix:Customer, Veltrix:Environment, Veltrix:App, Veltrix:ManagedBy,
433
+ CostCenter, Owner, ...). The OIDC apply role is scoped by
434
+ Veltrix:ManagedBy = Veltrix, so this map MUST include it.
435
+ EOT
436
+ type = map(string)
437
+ }
@@ -0,0 +1,23 @@
1
+ # =============================================================================
2
+ # AWS environment module — provider/version pinning (OpenTofu-compatible HCL).
3
+ #
4
+ # OpenTofu resolves providers from registry.opentofu.org by default but the
5
+ # canonical `hashicorp/aws` 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
+
10
+ terraform {
11
+ required_version = ">= 1.6.0"
12
+
13
+ required_providers {
14
+ aws = {
15
+ source = "hashicorp/aws"
16
+ version = "~> 5.40"
17
+ }
18
+ random = {
19
+ source = "hashicorp/random"
20
+ version = "~> 3.6"
21
+ }
22
+ }
23
+ }