@emilia-protocol/gate 0.9.5 → 0.10.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 (74) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +190 -0
  3. package/README.md +75 -16
  4. package/action-control-manifest.js +26 -0
  5. package/adapters/_kit.js +47 -5
  6. package/adapters/aws.js +15 -4
  7. package/adapters/github-demo.mjs +25 -22
  8. package/adapters/github.js +1 -1
  9. package/adapters/jira.js +1 -0
  10. package/adapters/linear.js +1 -0
  11. package/adapters/salesforce.js +1 -0
  12. package/adapters/stripe.js +1 -1
  13. package/adapters/supabase.js +26 -4
  14. package/adapters/vercel.js +33 -4
  15. package/aec-execution.js +1 -3
  16. package/breakglass.js +285 -51
  17. package/control-plane.js +341 -0
  18. package/coverage.js +722 -0
  19. package/custody-demo.mjs +13 -3
  20. package/demo.mjs +9 -3
  21. package/deploy/helm/README.md +16 -8
  22. package/deploy/helm/emilia-gate/Chart.yaml +3 -1
  23. package/deploy/helm/emilia-gate/templates/NOTES.txt +3 -2
  24. package/deploy/helm/emilia-gate/templates/deployment.yaml +55 -1
  25. package/deploy/helm/emilia-gate/values.yaml +18 -2
  26. package/deploy/helm/emilia-gate-service/Chart.yaml +11 -0
  27. package/deploy/helm/emilia-gate-service/README.md +81 -0
  28. package/deploy/helm/emilia-gate-service/templates/NOTES.txt +12 -0
  29. package/deploy/helm/emilia-gate-service/templates/_helpers.tpl +83 -0
  30. package/deploy/helm/emilia-gate-service/templates/deployment.yaml +153 -0
  31. package/deploy/helm/emilia-gate-service/templates/migration-job.yaml +73 -0
  32. package/deploy/helm/emilia-gate-service/templates/networkpolicy.yaml +113 -0
  33. package/deploy/helm/emilia-gate-service/templates/pdb.yaml +14 -0
  34. package/deploy/helm/emilia-gate-service/templates/service.yaml +20 -0
  35. package/deploy/helm/emilia-gate-service/templates/serviceaccount.yaml +8 -0
  36. package/deploy/helm/emilia-gate-service/tests/fixtures/001_gate.sql +26 -0
  37. package/deploy/helm/emilia-gate-service/tests/fixtures/002_runtime_access.sql +32 -0
  38. package/deploy/helm/emilia-gate-service/tests/fixtures/gate.config.mjs +29 -0
  39. package/deploy/helm/emilia-gate-service/tests/render-check.sh +145 -0
  40. package/deploy/helm/emilia-gate-service/values.schema.json +145 -0
  41. package/deploy/helm/emilia-gate-service/values.yaml +166 -0
  42. package/deploy/sql/001-runtime.sql +707 -0
  43. package/deploy/terraform/README.md +24 -14
  44. package/deploy/terraform/main.tf +59 -0
  45. package/deploy/terraform/service/README.md +81 -0
  46. package/deploy/terraform/service/main.tf +718 -0
  47. package/deploy/terraform/service/outputs.tf +19 -0
  48. package/deploy/terraform/service/tests/validate.sh +24 -0
  49. package/deploy/terraform/service/tests/validation.tftest.hcl +168 -0
  50. package/deploy/terraform/service/variables.tf +459 -0
  51. package/deploy/terraform/service/versions.tf +10 -0
  52. package/deploy/terraform/variables.tf +95 -2
  53. package/deployment-attestation.js +248 -0
  54. package/eg1-conformance.js +46 -12
  55. package/enterprise.js +6 -2
  56. package/ep-assure.mjs +3 -2
  57. package/evidence-postgres.js +357 -0
  58. package/evidence.js +10 -3
  59. package/execution-binding.js +141 -26
  60. package/index.js +556 -115
  61. package/key-registry.js +51 -19
  62. package/mcp.js +4 -2
  63. package/network-witness.js +500 -0
  64. package/package.json +25 -5
  65. package/reliance-kernel.js +5 -6
  66. package/reliance-packet.js +43 -10
  67. package/reports/assurance-package.js +45 -19
  68. package/reports/reperform.js +78 -18
  69. package/reports/underwriter.js +1 -1
  70. package/settlement.js +300 -0
  71. package/store-postgres.js +14 -0
  72. package/store.js +26 -5
  73. package/strict-json.js +86 -0
  74. package/witness-postgres.js +97 -0
@@ -0,0 +1,19 @@
1
+ output "service_name" {
2
+ description = "ClusterIP Service name."
3
+ value = kubernetes_service_v1.gate.metadata[0].name
4
+ }
5
+
6
+ output "service_dns" {
7
+ description = "In-cluster DNS name for the Gate service."
8
+ value = "${kubernetes_service_v1.gate.metadata[0].name}.${var.namespace}.svc"
9
+ }
10
+
11
+ output "deployment_name" {
12
+ description = "Gate Deployment name."
13
+ value = kubernetes_deployment_v1.gate.metadata[0].name
14
+ }
15
+
16
+ output "migration_job_name" {
17
+ description = "Migration Job name, or null when migrations are disabled."
18
+ value = var.migration_enabled ? kubernetes_job_v1.migration[0].metadata[0].name : null
19
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ module_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ terraform_bin="${TERRAFORM_BIN:-terraform}"
6
+
7
+ if ! command -v "$terraform_bin" >/dev/null 2>&1; then
8
+ echo "terraform is required (or set TERRAFORM_BIN)" >&2
9
+ exit 127
10
+ fi
11
+
12
+ "$terraform_bin" fmt -check -recursive "$module_dir"
13
+
14
+ tmp_dir="$(mktemp -d)"
15
+ trap 'rm -rf "$tmp_dir"' EXIT
16
+ cp "$module_dir"/*.tf "$tmp_dir"/
17
+ cp -R "$module_dir/tests" "$tmp_dir/tests"
18
+
19
+ TF_DATA_DIR="$tmp_dir/.terraform" "$terraform_bin" -chdir="$tmp_dir" init \
20
+ -backend=false -input=false >/dev/null
21
+ TF_DATA_DIR="$tmp_dir/.terraform" "$terraform_bin" -chdir="$tmp_dir" validate
22
+ TF_DATA_DIR="$tmp_dir/.terraform" "$terraform_bin" -chdir="$tmp_dir" test
23
+
24
+ echo "Terraform formatting, provider-schema, and validation-contract tests passed"
@@ -0,0 +1,168 @@
1
+ mock_provider "kubernetes" {}
2
+
3
+ run "valid_distinct_migration_and_secret_env" {
4
+ command = plan
5
+
6
+ variables {
7
+ namespace = "gate-system"
8
+ image = "registry.example.test/security/emilia-gate-service@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
9
+ configuration_secret_name = "gate-configuration"
10
+ postgres_secret_name = "gate-postgres"
11
+ migration_postgres_secret_name = "gate-postgres-migrate"
12
+ migration_postgres_secret_key = "database-url"
13
+ api_token_secret_name = "gate-api-token"
14
+ issuer_roots_secret_name = "gate-issuer-roots"
15
+ github_token_secret_name = "gate-github"
16
+ github_token_secret_key = "token"
17
+ secret_env = {
18
+ SIEM_TOKEN = {
19
+ secret_name = "gate-siem"
20
+ secret_key = "token"
21
+ }
22
+ }
23
+ }
24
+
25
+ assert {
26
+ condition = anytrue([
27
+ for item in kubernetes_deployment_v1.gate.spec[0].template[0].spec[0].container[0].env :
28
+ item.name == "GITHUB_TOKEN"
29
+ && try(item.value_from[0].secret_key_ref[0].name == "gate-github", false)
30
+ ])
31
+ error_message = "GitHub token must render only as a secretKeyRef."
32
+ }
33
+
34
+ assert {
35
+ condition = anytrue([
36
+ for item in kubernetes_deployment_v1.gate.spec[0].template[0].spec[0].container[0].env :
37
+ item.name == "SIEM_TOKEN"
38
+ && try(item.value_from[0].secret_key_ref[0].name == "gate-siem", false)
39
+ ])
40
+ error_message = "Generic secret environment input must render as a secretKeyRef."
41
+ }
42
+
43
+ assert {
44
+ condition = anytrue([
45
+ for item in kubernetes_job_v1.migration[0].spec[0].template[0].spec[0].container[0].env :
46
+ item.name == "DATABASE_URL"
47
+ && try(item.value_from[0].secret_key_ref[0].name == "gate-postgres-migrate", false)
48
+ ])
49
+ error_message = "Migration Job must use the distinct migration Secret."
50
+ }
51
+
52
+ assert {
53
+ condition = (
54
+ kubernetes_deployment_v1.gate.spec[0].template[0].spec[0].container[0].startup_probe[0].http_get[0].path == "/v1/live"
55
+ && kubernetes_deployment_v1.gate.spec[0].template[0].spec[0].container[0].liveness_probe[0].http_get[0].path == "/v1/live"
56
+ && kubernetes_deployment_v1.gate.spec[0].template[0].spec[0].container[0].readiness_probe[0].http_get[0].path == "/v1/ready"
57
+ )
58
+ error_message = "Gate probes must use the service live and ready routes."
59
+ }
60
+ }
61
+
62
+ run "reject_explicit_latest_image" {
63
+ command = plan
64
+
65
+ variables {
66
+ namespace = "gate-system"
67
+ image = "registry.example.test/security/emilia-gate-service:latest"
68
+ configuration_secret_name = "gate-configuration"
69
+ postgres_secret_name = "gate-postgres"
70
+ migration_postgres_secret_name = "gate-postgres-migrate"
71
+ migration_postgres_secret_key = "database-url"
72
+ api_token_secret_name = "gate-api-token"
73
+ issuer_roots_secret_name = "gate-issuer-roots"
74
+ }
75
+
76
+ expect_failures = [var.image]
77
+ }
78
+
79
+ run "reject_implicit_latest_image" {
80
+ command = plan
81
+
82
+ variables {
83
+ namespace = "gate-system"
84
+ image = "registry.example.test/security/emilia-gate-service"
85
+ configuration_secret_name = "gate-configuration"
86
+ postgres_secret_name = "gate-postgres"
87
+ migration_postgres_secret_name = "gate-postgres-migrate"
88
+ migration_postgres_secret_key = "database-url"
89
+ api_token_secret_name = "gate-api-token"
90
+ issuer_roots_secret_name = "gate-issuer-roots"
91
+ }
92
+
93
+ expect_failures = [var.image]
94
+ }
95
+
96
+ run "reject_missing_migration_secret" {
97
+ command = plan
98
+
99
+ variables {
100
+ namespace = "gate-system"
101
+ image = "registry.example.test/security/emilia-gate-service@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
102
+ configuration_secret_name = "gate-configuration"
103
+ postgres_secret_name = "gate-postgres"
104
+ api_token_secret_name = "gate-api-token"
105
+ issuer_roots_secret_name = "gate-issuer-roots"
106
+ }
107
+
108
+ expect_failures = [kubernetes_job_v1.migration]
109
+ }
110
+
111
+ run "reject_equal_migration_secret" {
112
+ command = plan
113
+
114
+ variables {
115
+ namespace = "gate-system"
116
+ image = "registry.example.test/security/emilia-gate-service@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
117
+ configuration_secret_name = "gate-configuration"
118
+ postgres_secret_name = "gate-postgres"
119
+ migration_postgres_secret_name = "gate-postgres"
120
+ migration_postgres_secret_key = "database-url"
121
+ api_token_secret_name = "gate-api-token"
122
+ issuer_roots_secret_name = "gate-issuer-roots"
123
+ }
124
+
125
+ expect_failures = [kubernetes_job_v1.migration]
126
+ }
127
+
128
+ run "allow_disabled_migration_without_secret" {
129
+ command = plan
130
+
131
+ variables {
132
+ namespace = "gate-system"
133
+ image = "registry.example.test/security/emilia-gate-service@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
134
+ configuration_secret_name = "gate-configuration"
135
+ postgres_secret_name = "gate-postgres"
136
+ api_token_secret_name = "gate-api-token"
137
+ issuer_roots_secret_name = "gate-issuer-roots"
138
+ migration_enabled = false
139
+ }
140
+
141
+ assert {
142
+ condition = length(kubernetes_job_v1.migration) == 0
143
+ error_message = "Disabled migrations must not create a Job."
144
+ }
145
+ }
146
+
147
+ run "reject_secret_env_override" {
148
+ command = plan
149
+
150
+ variables {
151
+ namespace = "gate-system"
152
+ image = "registry.example.test/security/emilia-gate-service@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
153
+ configuration_secret_name = "gate-configuration"
154
+ postgres_secret_name = "gate-postgres"
155
+ migration_postgres_secret_name = "gate-postgres-migrate"
156
+ migration_postgres_secret_key = "database-url"
157
+ api_token_secret_name = "gate-api-token"
158
+ issuer_roots_secret_name = "gate-issuer-roots"
159
+ secret_env = {
160
+ DATABASE_URL = {
161
+ secret_name = "attacker-secret"
162
+ secret_key = "database-url"
163
+ }
164
+ }
165
+ }
166
+
167
+ expect_failures = [kubernetes_deployment_v1.gate]
168
+ }
@@ -0,0 +1,459 @@
1
+ variable "name" {
2
+ description = "Kubernetes resource name prefix."
3
+ type = string
4
+ default = "emilia-gate-service"
5
+
6
+ validation {
7
+ condition = can(regex("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", var.name)) && length(var.name) <= 52
8
+ error_message = "name must be a DNS label no longer than 52 characters."
9
+ }
10
+ }
11
+
12
+ variable "namespace" {
13
+ description = "Existing Kubernetes namespace in which to deploy."
14
+ type = string
15
+
16
+ validation {
17
+ condition = can(regex("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", var.namespace))
18
+ error_message = "namespace must be a Kubernetes DNS label."
19
+ }
20
+ }
21
+
22
+ variable "image" {
23
+ description = "Complete BYOC image reference built from Dockerfile.gate. No official public image is implied. Use a digest in production."
24
+ type = string
25
+
26
+ validation {
27
+ condition = (
28
+ trimspace(var.image) == var.image
29
+ && (
30
+ can(regex("@sha256:[a-f0-9]{64}$", var.image))
31
+ || (
32
+ can(regex(":[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$", element(reverse(split("/", var.image)), 0)))
33
+ && !endswith(lower(var.image), ":latest")
34
+ )
35
+ )
36
+ )
37
+ error_message = "image must use an explicit non-latest tag or sha256 digest; untagged and implicit-latest references are refused."
38
+ }
39
+ }
40
+
41
+ variable "image_pull_policy" {
42
+ description = "Kubernetes image pull policy."
43
+ type = string
44
+ default = "IfNotPresent"
45
+
46
+ validation {
47
+ condition = contains(["Always", "IfNotPresent", "Never"], var.image_pull_policy)
48
+ error_message = "image_pull_policy must be Always, IfNotPresent, or Never."
49
+ }
50
+ }
51
+
52
+ variable "image_pull_secrets" {
53
+ description = "Names of existing image pull Secrets."
54
+ type = list(string)
55
+ default = []
56
+ }
57
+
58
+ variable "replicas" {
59
+ description = "Gate service replicas. Two is the production-shaped default."
60
+ type = number
61
+ default = 2
62
+
63
+ validation {
64
+ condition = var.replicas >= 1 && floor(var.replicas) == var.replicas
65
+ error_message = "replicas must be a positive integer."
66
+ }
67
+ }
68
+
69
+ variable "port" {
70
+ description = "Gate service container and ClusterIP port."
71
+ type = number
72
+ default = 8080
73
+
74
+ validation {
75
+ condition = var.port >= 1 && var.port <= 65535
76
+ error_message = "port must be between 1 and 65535."
77
+ }
78
+ }
79
+
80
+ variable "log_level" {
81
+ description = "EP_GATE_LOG_LEVEL passed to the service."
82
+ type = string
83
+ default = "info"
84
+
85
+ validation {
86
+ condition = contains(["debug", "info", "warn", "error"], var.log_level)
87
+ error_message = "log_level must be debug, info, warn, or error."
88
+ }
89
+ }
90
+
91
+ variable "configuration_secret_name" {
92
+ description = "Name of an existing Secret containing the operator-owned gate.config.mjs and migrate.mjs modules."
93
+ type = string
94
+
95
+ validation {
96
+ condition = trimspace(var.configuration_secret_name) != ""
97
+ error_message = "configuration_secret_name is required."
98
+ }
99
+ }
100
+
101
+ variable "configuration_secret_key" {
102
+ description = "Key in configuration_secret_name containing the apps/gate-service ESM config module."
103
+ type = string
104
+ default = "gate.config.mjs"
105
+ }
106
+
107
+ variable "migration_script_secret_key" {
108
+ description = "Key in configuration_secret_name containing the idempotent migration module."
109
+ type = string
110
+ default = "migrate.mjs"
111
+ }
112
+
113
+ variable "configuration_mount_path" {
114
+ description = "Read-only directory where operator configuration modules are mounted."
115
+ type = string
116
+ default = "/app/config"
117
+ }
118
+
119
+ variable "postgres_secret_name" {
120
+ description = "Name of an existing Secret containing the Postgres connection URL. Only the reference is stored by Terraform."
121
+ type = string
122
+
123
+ validation {
124
+ condition = trimspace(var.postgres_secret_name) != ""
125
+ error_message = "postgres_secret_name is required."
126
+ }
127
+ }
128
+
129
+ variable "postgres_secret_key" {
130
+ description = "Key in postgres_secret_name containing the connection URL."
131
+ type = string
132
+ default = "database-url"
133
+ }
134
+
135
+ variable "postgres_env_name" {
136
+ description = "Runtime environment variable populated from the Postgres Secret."
137
+ type = string
138
+ default = "DATABASE_URL"
139
+ }
140
+
141
+ variable "migration_postgres_secret_name" {
142
+ description = "Existing Secret for a DDL-capable migration login. Required when migration_enabled is true and must differ from postgres_secret_name."
143
+ type = string
144
+ default = null
145
+ nullable = true
146
+ }
147
+
148
+ variable "migration_postgres_secret_key" {
149
+ description = "Key in migration_postgres_secret_name containing the migration URL. Required when migration_enabled is true."
150
+ type = string
151
+ default = null
152
+ nullable = true
153
+ }
154
+
155
+ variable "api_token_secret_name" {
156
+ description = "Name of an existing Secret containing the Gate action-API bearer token. Only the reference is stored by Terraform."
157
+ type = string
158
+
159
+ validation {
160
+ condition = trimspace(var.api_token_secret_name) != ""
161
+ error_message = "api_token_secret_name is required."
162
+ }
163
+ }
164
+
165
+ variable "api_token_secret_key" {
166
+ description = "Key in api_token_secret_name containing the Gate API token."
167
+ type = string
168
+ default = "api-token"
169
+ }
170
+
171
+ variable "api_token_env_name" {
172
+ description = "Runtime environment variable populated from the Gate API-token Secret."
173
+ type = string
174
+ default = "EMILIA_GATE_API_TOKEN"
175
+ }
176
+
177
+ variable "kms_secret_name" {
178
+ description = "Optional existing Secret containing KMS configuration used by an operator extension. The Gate service itself does not mint or require a signing key."
179
+ type = string
180
+ default = null
181
+ nullable = true
182
+
183
+ validation {
184
+ condition = var.kms_secret_name == null ? true : trimspace(var.kms_secret_name) != ""
185
+ error_message = "kms_secret_name must be null or non-empty."
186
+ }
187
+ }
188
+
189
+ variable "kms_secret_key" {
190
+ description = "Key in kms_secret_name containing the KMS key identifier/config."
191
+ type = string
192
+ default = "kms-key-id"
193
+ }
194
+
195
+ variable "kms_env_name" {
196
+ description = "Runtime environment variable populated from the KMS Secret."
197
+ type = string
198
+ default = "EP_KMS_KEY_ID"
199
+ }
200
+
201
+ variable "issuer_roots_secret_name" {
202
+ description = "Name of an existing Secret containing the pinned issuer-root set. Only the reference is stored by Terraform."
203
+ type = string
204
+
205
+ validation {
206
+ condition = trimspace(var.issuer_roots_secret_name) != ""
207
+ error_message = "issuer_roots_secret_name is required."
208
+ }
209
+ }
210
+
211
+ variable "issuer_roots_secret_key" {
212
+ description = "Key in issuer_roots_secret_name containing the pinned issuer-root set."
213
+ type = string
214
+ default = "issuer-roots.json"
215
+ }
216
+
217
+ variable "issuer_roots_env_name" {
218
+ description = "Runtime environment variable populated from the issuer-roots Secret."
219
+ type = string
220
+ default = "EP_GATE_ISSUER_ROOTS"
221
+ }
222
+
223
+ variable "github_token_secret_name" {
224
+ description = "Optional existing Secret containing a GitHub token. Only the Secret reference enters Terraform state."
225
+ type = string
226
+ default = null
227
+ nullable = true
228
+
229
+ validation {
230
+ condition = var.github_token_secret_name == null ? true : can(regex("^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$", var.github_token_secret_name))
231
+ error_message = "github_token_secret_name must be null or a Kubernetes Secret name."
232
+ }
233
+ }
234
+
235
+ variable "github_token_secret_key" {
236
+ description = "Key in github_token_secret_name containing the GitHub token."
237
+ type = string
238
+ default = "token"
239
+
240
+ validation {
241
+ condition = trimspace(var.github_token_secret_key) != ""
242
+ error_message = "github_token_secret_key must be non-empty."
243
+ }
244
+ }
245
+
246
+ variable "github_token_env_name" {
247
+ description = "Runtime environment variable populated from github_token_secret_name."
248
+ type = string
249
+ default = "GITHUB_TOKEN"
250
+
251
+ validation {
252
+ condition = can(regex("^[A-Za-z_][A-Za-z0-9_]*$", var.github_token_env_name))
253
+ error_message = "github_token_env_name must be a valid environment variable name."
254
+ }
255
+ }
256
+
257
+ variable "secret_env" {
258
+ description = "Additional runtime environment variables populated from existing Kubernetes Secrets. Values are references only; secret bytes never enter this module."
259
+ type = map(object({
260
+ secret_name = string
261
+ secret_key = string
262
+ }))
263
+ default = {}
264
+
265
+ validation {
266
+ condition = alltrue([
267
+ for env_name, reference in var.secret_env :
268
+ can(regex("^[A-Za-z_][A-Za-z0-9_]*$", env_name))
269
+ && trimspace(reference.secret_name) != ""
270
+ && trimspace(reference.secret_key) != ""
271
+ ])
272
+ error_message = "secret_env keys must be environment variable names and each Secret name/key must be non-empty."
273
+ }
274
+ }
275
+
276
+ variable "extra_env" {
277
+ description = "Additional non-secret literal environment variables. Use external Secret wiring for sensitive values."
278
+ type = map(string)
279
+ default = {}
280
+ }
281
+
282
+ variable "migration_enabled" {
283
+ description = "Run the database migration Job before the Deployment. Keep enabled outside controlled recovery operations."
284
+ type = bool
285
+ default = true
286
+ }
287
+
288
+ variable "migration_command" {
289
+ description = "Command run by the migration Job."
290
+ type = list(string)
291
+ default = ["node", "/app/config/migrate.mjs"]
292
+
293
+ validation {
294
+ condition = length(var.migration_command) > 0
295
+ error_message = "migration_command must contain at least one element."
296
+ }
297
+ }
298
+
299
+ variable "migration_revision" {
300
+ description = "Bump when migration inputs change without changing the image; it is included in the immutable Job name."
301
+ type = string
302
+ default = "schema-v1"
303
+ }
304
+
305
+ variable "migration_active_deadline_seconds" {
306
+ description = "Maximum migration Job runtime."
307
+ type = number
308
+ default = 600
309
+ }
310
+
311
+ variable "migration_backoff_limit" {
312
+ description = "Migration Job retry limit."
313
+ type = number
314
+ default = 3
315
+ }
316
+
317
+ variable "resources" {
318
+ description = "Service container requests and limits."
319
+ type = object({
320
+ requests = map(string)
321
+ limits = map(string)
322
+ })
323
+ default = {
324
+ requests = {
325
+ cpu = "100m"
326
+ memory = "128Mi"
327
+ }
328
+ limits = {
329
+ cpu = "1"
330
+ memory = "512Mi"
331
+ }
332
+ }
333
+ }
334
+
335
+ variable "migration_resources" {
336
+ description = "Migration Job requests and limits."
337
+ type = object({
338
+ requests = map(string)
339
+ limits = map(string)
340
+ })
341
+ default = {
342
+ requests = {
343
+ cpu = "50m"
344
+ memory = "64Mi"
345
+ }
346
+ limits = {
347
+ cpu = "500m"
348
+ memory = "256Mi"
349
+ }
350
+ }
351
+ }
352
+
353
+ variable "pdb_enabled" {
354
+ description = "Create a PodDisruptionBudget for service pods."
355
+ type = bool
356
+ default = true
357
+ }
358
+
359
+ variable "pdb_min_available" {
360
+ description = "Minimum service pods available during voluntary disruption."
361
+ type = string
362
+ default = "1"
363
+ }
364
+
365
+ variable "network_policy_enabled" {
366
+ description = "Create default-deny and explicit allow NetworkPolicies for gate and migration pods."
367
+ type = bool
368
+ default = true
369
+ }
370
+
371
+ variable "allow_same_namespace_ingress" {
372
+ description = "Allow pods in the deployment namespace to reach the gate service port."
373
+ type = bool
374
+ default = true
375
+ }
376
+
377
+ variable "dns_namespace_labels" {
378
+ description = "Namespace selector labels for cluster DNS."
379
+ type = map(string)
380
+ default = {
381
+ "kubernetes.io/metadata.name" = "kube-system"
382
+ }
383
+ }
384
+
385
+ variable "dns_pod_labels" {
386
+ description = "Pod selector labels for cluster DNS."
387
+ type = map(string)
388
+ default = {
389
+ "k8s-app" = "kube-dns"
390
+ }
391
+ }
392
+
393
+ variable "postgres_port" {
394
+ description = "Postgres egress port."
395
+ type = number
396
+ default = 5432
397
+ }
398
+
399
+ variable "postgres_pod_labels" {
400
+ description = "Same-namespace Postgres pod selector. Set to an empty map when using only managed-Postgres CIDRs."
401
+ type = map(string)
402
+ default = {
403
+ "app.kubernetes.io/name" = "postgresql"
404
+ }
405
+ }
406
+
407
+ variable "postgres_egress_cidrs" {
408
+ description = "Stable managed-Postgres or egress-proxy CIDRs allowed on postgres_port."
409
+ type = list(string)
410
+ default = []
411
+ }
412
+
413
+ variable "kms_egress_cidrs" {
414
+ description = "Stable KMS proxy/NAT CIDRs allowed on TCP 443. Standard NetworkPolicy cannot select KMS FQDNs."
415
+ type = list(string)
416
+ default = []
417
+ }
418
+
419
+ variable "github_egress_cidrs" {
420
+ description = "Stable GitHub egress-proxy CIDRs allowed on TCP 443. Prefer an FQDN-aware CNI policy over public IP snapshots."
421
+ type = list(string)
422
+ default = []
423
+ }
424
+
425
+ variable "siem_egress_cidrs" {
426
+ description = "Stable SIEM egress-proxy CIDRs allowed on TCP 443. Prefer an FQDN-aware CNI policy for SaaS SIEM endpoints."
427
+ type = list(string)
428
+ default = []
429
+ }
430
+
431
+ variable "pod_annotations" {
432
+ description = "Additional service pod annotations."
433
+ type = map(string)
434
+ default = {}
435
+ }
436
+
437
+ variable "labels" {
438
+ description = "Additional labels added to resources. Do not override selector labels."
439
+ type = map(string)
440
+ default = {}
441
+ }
442
+
443
+ variable "node_selector" {
444
+ description = "Service pod node selector."
445
+ type = map(string)
446
+ default = {}
447
+ }
448
+
449
+ variable "tolerations" {
450
+ description = "Service pod tolerations in Kubernetes provider shape."
451
+ type = list(object({
452
+ key = optional(string)
453
+ operator = optional(string)
454
+ value = optional(string)
455
+ effect = optional(string)
456
+ toleration_seconds = optional(number)
457
+ }))
458
+ default = []
459
+ }
@@ -0,0 +1,10 @@
1
+ terraform {
2
+ required_version = ">= 1.6.0"
3
+
4
+ required_providers {
5
+ kubernetes = {
6
+ source = "hashicorp/kubernetes"
7
+ version = ">= 2.31.0, < 3.0.0"
8
+ }
9
+ }
10
+ }