iec-builder 0.1.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 (337) hide show
  1. package/.claude/settings.local.json +111 -0
  2. package/.iec.yaml +5 -0
  3. package/CLAUDE.md +174 -0
  4. package/Dockerfile +34 -0
  5. package/README.md +84 -0
  6. package/catalog-info.yaml +11 -0
  7. package/dist/config/env.d.ts +219 -0
  8. package/dist/config/env.d.ts.map +1 -0
  9. package/dist/config/env.js +89 -0
  10. package/dist/config/env.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +148 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/middleware/auth.d.ts +43 -0
  16. package/dist/middleware/auth.d.ts.map +1 -0
  17. package/dist/middleware/auth.js +217 -0
  18. package/dist/middleware/auth.js.map +1 -0
  19. package/dist/middleware/org-access.d.ts +28 -0
  20. package/dist/middleware/org-access.d.ts.map +1 -0
  21. package/dist/middleware/org-access.js +102 -0
  22. package/dist/middleware/org-access.js.map +1 -0
  23. package/dist/models/types.d.ts +254 -0
  24. package/dist/models/types.d.ts.map +1 -0
  25. package/dist/models/types.js +2 -0
  26. package/dist/models/types.js.map +1 -0
  27. package/dist/routes/ai.d.ts +2 -0
  28. package/dist/routes/ai.d.ts.map +1 -0
  29. package/dist/routes/ai.js +77 -0
  30. package/dist/routes/ai.js.map +1 -0
  31. package/dist/routes/audit.d.ts +2 -0
  32. package/dist/routes/audit.d.ts.map +1 -0
  33. package/dist/routes/audit.js +102 -0
  34. package/dist/routes/audit.js.map +1 -0
  35. package/dist/routes/builds.d.ts +2 -0
  36. package/dist/routes/builds.d.ts.map +1 -0
  37. package/dist/routes/builds.js +262 -0
  38. package/dist/routes/builds.js.map +1 -0
  39. package/dist/routes/cluster.d.ts +2 -0
  40. package/dist/routes/cluster.d.ts.map +1 -0
  41. package/dist/routes/cluster.js +181 -0
  42. package/dist/routes/cluster.js.map +1 -0
  43. package/dist/routes/config.d.ts +2 -0
  44. package/dist/routes/config.d.ts.map +1 -0
  45. package/dist/routes/config.js +291 -0
  46. package/dist/routes/config.js.map +1 -0
  47. package/dist/routes/databases.d.ts +2 -0
  48. package/dist/routes/databases.d.ts.map +1 -0
  49. package/dist/routes/databases.js +161 -0
  50. package/dist/routes/databases.js.map +1 -0
  51. package/dist/routes/db-whitelist.d.ts +2 -0
  52. package/dist/routes/db-whitelist.d.ts.map +1 -0
  53. package/dist/routes/db-whitelist.js +148 -0
  54. package/dist/routes/db-whitelist.js.map +1 -0
  55. package/dist/routes/domains.d.ts +2 -0
  56. package/dist/routes/domains.d.ts.map +1 -0
  57. package/dist/routes/domains.js +449 -0
  58. package/dist/routes/domains.js.map +1 -0
  59. package/dist/routes/oauth.d.ts +2 -0
  60. package/dist/routes/oauth.d.ts.map +1 -0
  61. package/dist/routes/oauth.js +180 -0
  62. package/dist/routes/oauth.js.map +1 -0
  63. package/dist/routes/observability.d.ts +2 -0
  64. package/dist/routes/observability.d.ts.map +1 -0
  65. package/dist/routes/observability.js +167 -0
  66. package/dist/routes/observability.js.map +1 -0
  67. package/dist/routes/orgs.d.ts +2 -0
  68. package/dist/routes/orgs.d.ts.map +1 -0
  69. package/dist/routes/orgs.js +270 -0
  70. package/dist/routes/orgs.js.map +1 -0
  71. package/dist/routes/platform.d.ts +2 -0
  72. package/dist/routes/platform.d.ts.map +1 -0
  73. package/dist/routes/platform.js +107 -0
  74. package/dist/routes/platform.js.map +1 -0
  75. package/dist/routes/push.d.ts +2 -0
  76. package/dist/routes/push.d.ts.map +1 -0
  77. package/dist/routes/push.js +233 -0
  78. package/dist/routes/push.js.map +1 -0
  79. package/dist/routes/rotation.d.ts +3 -0
  80. package/dist/routes/rotation.d.ts.map +1 -0
  81. package/dist/routes/rotation.js +154 -0
  82. package/dist/routes/rotation.js.map +1 -0
  83. package/dist/routes/services.d.ts +2 -0
  84. package/dist/routes/services.d.ts.map +1 -0
  85. package/dist/routes/services.js +246 -0
  86. package/dist/routes/services.js.map +1 -0
  87. package/dist/routes/storage.d.ts +2 -0
  88. package/dist/routes/storage.d.ts.map +1 -0
  89. package/dist/routes/storage.js +118 -0
  90. package/dist/routes/storage.js.map +1 -0
  91. package/dist/routes/users.d.ts +2 -0
  92. package/dist/routes/users.d.ts.map +1 -0
  93. package/dist/routes/users.js +183 -0
  94. package/dist/routes/users.js.map +1 -0
  95. package/dist/routes/versions.d.ts +2 -0
  96. package/dist/routes/versions.d.ts.map +1 -0
  97. package/dist/routes/versions.js +195 -0
  98. package/dist/routes/versions.js.map +1 -0
  99. package/dist/routes/webhooks.d.ts +2 -0
  100. package/dist/routes/webhooks.d.ts.map +1 -0
  101. package/dist/routes/webhooks.js +334 -0
  102. package/dist/routes/webhooks.js.map +1 -0
  103. package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts +2 -0
  104. package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts.map +1 -0
  105. package/dist/services/__tests__/deploy-pipeline.integration.test.js +482 -0
  106. package/dist/services/__tests__/deploy-pipeline.integration.test.js.map +1 -0
  107. package/dist/services/bio-client.d.ts +68 -0
  108. package/dist/services/bio-client.d.ts.map +1 -0
  109. package/dist/services/bio-client.js +110 -0
  110. package/dist/services/bio-client.js.map +1 -0
  111. package/dist/services/build-queue.d.ts +7 -0
  112. package/dist/services/build-queue.d.ts.map +1 -0
  113. package/dist/services/build-queue.js +114 -0
  114. package/dist/services/build-queue.js.map +1 -0
  115. package/dist/services/builder.d.ts +7 -0
  116. package/dist/services/builder.d.ts.map +1 -0
  117. package/dist/services/builder.js +1384 -0
  118. package/dist/services/builder.js.map +1 -0
  119. package/dist/services/catalog.d.ts +177 -0
  120. package/dist/services/catalog.d.ts.map +1 -0
  121. package/dist/services/catalog.js +805 -0
  122. package/dist/services/catalog.js.map +1 -0
  123. package/dist/services/catalog.test.d.ts +2 -0
  124. package/dist/services/catalog.test.d.ts.map +1 -0
  125. package/dist/services/catalog.test.js +467 -0
  126. package/dist/services/catalog.test.js.map +1 -0
  127. package/dist/services/cloudflare.d.ts +43 -0
  128. package/dist/services/cloudflare.d.ts.map +1 -0
  129. package/dist/services/cloudflare.js +182 -0
  130. package/dist/services/cloudflare.js.map +1 -0
  131. package/dist/services/config-validator.d.ts +28 -0
  132. package/dist/services/config-validator.d.ts.map +1 -0
  133. package/dist/services/config-validator.js +68 -0
  134. package/dist/services/config-validator.js.map +1 -0
  135. package/dist/services/config-validator.test.d.ts +2 -0
  136. package/dist/services/config-validator.test.d.ts.map +1 -0
  137. package/dist/services/config-validator.test.js +151 -0
  138. package/dist/services/config-validator.test.js.map +1 -0
  139. package/dist/services/crypto.d.ts +19 -0
  140. package/dist/services/crypto.d.ts.map +1 -0
  141. package/dist/services/crypto.js +63 -0
  142. package/dist/services/crypto.js.map +1 -0
  143. package/dist/services/database.d.ts +26 -0
  144. package/dist/services/database.d.ts.map +1 -0
  145. package/dist/services/database.js +100 -0
  146. package/dist/services/database.js.map +1 -0
  147. package/dist/services/db-credential-manager.d.ts +73 -0
  148. package/dist/services/db-credential-manager.d.ts.map +1 -0
  149. package/dist/services/db-credential-manager.js +342 -0
  150. package/dist/services/db-credential-manager.js.map +1 -0
  151. package/dist/services/db-provisioner.d.ts +57 -0
  152. package/dist/services/db-provisioner.d.ts.map +1 -0
  153. package/dist/services/db-provisioner.js +400 -0
  154. package/dist/services/db-provisioner.js.map +1 -0
  155. package/dist/services/db-provisioner.test.d.ts +2 -0
  156. package/dist/services/db-provisioner.test.d.ts.map +1 -0
  157. package/dist/services/db-provisioner.test.js +141 -0
  158. package/dist/services/db-provisioner.test.js.map +1 -0
  159. package/dist/services/db-whitelist.d.ts +58 -0
  160. package/dist/services/db-whitelist.d.ts.map +1 -0
  161. package/dist/services/db-whitelist.js +379 -0
  162. package/dist/services/db-whitelist.js.map +1 -0
  163. package/dist/services/dependency-resolver.d.ts +58 -0
  164. package/dist/services/dependency-resolver.d.ts.map +1 -0
  165. package/dist/services/dependency-resolver.js +180 -0
  166. package/dist/services/dependency-resolver.js.map +1 -0
  167. package/dist/services/dependency-resolver.test.d.ts +2 -0
  168. package/dist/services/dependency-resolver.test.d.ts.map +1 -0
  169. package/dist/services/dependency-resolver.test.js +195 -0
  170. package/dist/services/dependency-resolver.test.js.map +1 -0
  171. package/dist/services/deploy-gate.d.ts +19 -0
  172. package/dist/services/deploy-gate.d.ts.map +1 -0
  173. package/dist/services/deploy-gate.js +56 -0
  174. package/dist/services/deploy-gate.js.map +1 -0
  175. package/dist/services/deploy-gate.test.d.ts +2 -0
  176. package/dist/services/deploy-gate.test.d.ts.map +1 -0
  177. package/dist/services/deploy-gate.test.js +199 -0
  178. package/dist/services/deploy-gate.test.js.map +1 -0
  179. package/dist/services/dockerfile-generator.d.ts +31 -0
  180. package/dist/services/dockerfile-generator.d.ts.map +1 -0
  181. package/dist/services/dockerfile-generator.js +544 -0
  182. package/dist/services/dockerfile-generator.js.map +1 -0
  183. package/dist/services/dockerfile-generator.test.d.ts +2 -0
  184. package/dist/services/dockerfile-generator.test.d.ts.map +1 -0
  185. package/dist/services/dockerfile-generator.test.js +144 -0
  186. package/dist/services/dockerfile-generator.test.js.map +1 -0
  187. package/dist/services/forgejo.d.ts +58 -0
  188. package/dist/services/forgejo.d.ts.map +1 -0
  189. package/dist/services/forgejo.js +131 -0
  190. package/dist/services/forgejo.js.map +1 -0
  191. package/dist/services/koko.d.ts +153 -0
  192. package/dist/services/koko.d.ts.map +1 -0
  193. package/dist/services/koko.js +260 -0
  194. package/dist/services/koko.js.map +1 -0
  195. package/dist/services/kubernetes.d.ts +16 -0
  196. package/dist/services/kubernetes.d.ts.map +1 -0
  197. package/dist/services/kubernetes.js +102 -0
  198. package/dist/services/kubernetes.js.map +1 -0
  199. package/dist/services/oauth-provisioner.d.ts +30 -0
  200. package/dist/services/oauth-provisioner.d.ts.map +1 -0
  201. package/dist/services/oauth-provisioner.js +182 -0
  202. package/dist/services/oauth-provisioner.js.map +1 -0
  203. package/dist/services/oauth-provisioner.test.d.ts +2 -0
  204. package/dist/services/oauth-provisioner.test.d.ts.map +1 -0
  205. package/dist/services/oauth-provisioner.test.js +349 -0
  206. package/dist/services/oauth-provisioner.test.js.map +1 -0
  207. package/dist/services/pod-diagnostics.d.ts +11 -0
  208. package/dist/services/pod-diagnostics.d.ts.map +1 -0
  209. package/dist/services/pod-diagnostics.js +201 -0
  210. package/dist/services/pod-diagnostics.js.map +1 -0
  211. package/dist/services/rotation-scheduler.d.ts +2 -0
  212. package/dist/services/rotation-scheduler.d.ts.map +1 -0
  213. package/dist/services/rotation-scheduler.js +215 -0
  214. package/dist/services/rotation-scheduler.js.map +1 -0
  215. package/dist/services/storage-credential-manager.d.ts +43 -0
  216. package/dist/services/storage-credential-manager.d.ts.map +1 -0
  217. package/dist/services/storage-credential-manager.js +159 -0
  218. package/dist/services/storage-credential-manager.js.map +1 -0
  219. package/dist/services/storage-provisioner.d.ts +32 -0
  220. package/dist/services/storage-provisioner.d.ts.map +1 -0
  221. package/dist/services/storage-provisioner.js +136 -0
  222. package/dist/services/storage-provisioner.js.map +1 -0
  223. package/dist/services/storage.d.ts +65 -0
  224. package/dist/services/storage.d.ts.map +1 -0
  225. package/dist/services/storage.js +204 -0
  226. package/dist/services/storage.js.map +1 -0
  227. package/dist/services/troubleshooter.d.ts +22 -0
  228. package/dist/services/troubleshooter.d.ts.map +1 -0
  229. package/dist/services/troubleshooter.js +168 -0
  230. package/dist/services/troubleshooter.js.map +1 -0
  231. package/dist/services/vault-client.d.ts +114 -0
  232. package/dist/services/vault-client.d.ts.map +1 -0
  233. package/dist/services/vault-client.js +411 -0
  234. package/dist/services/vault-client.js.map +1 -0
  235. package/dist/utils/logger.d.ts +2 -0
  236. package/dist/utils/logger.d.ts.map +1 -0
  237. package/dist/utils/logger.js +6 -0
  238. package/dist/utils/logger.js.map +1 -0
  239. package/dist/utils/response.d.ts +13 -0
  240. package/dist/utils/response.d.ts.map +1 -0
  241. package/dist/utils/response.js +12 -0
  242. package/dist/utils/response.js.map +1 -0
  243. package/docs/registry-migration.md +301 -0
  244. package/docs/registry-quickstart.md +169 -0
  245. package/ecosystem.config.cjs +14 -0
  246. package/findings.md +168 -0
  247. package/helm/default-service/Chart.yaml +6 -0
  248. package/helm/default-service/templates/deployment.yaml +97 -0
  249. package/helm/default-service/templates/ingress.yaml +43 -0
  250. package/helm/default-service/templates/service.yaml +17 -0
  251. package/helm/default-service/values.yaml +82 -0
  252. package/helm/services/iec-builder/Chart.yaml +6 -0
  253. package/helm/services/iec-builder/templates/_helpers.tpl +61 -0
  254. package/helm/services/iec-builder/templates/deployment.yaml +73 -0
  255. package/helm/services/iec-builder/templates/service.yaml +15 -0
  256. package/helm/services/iec-builder/templates/serviceaccount.yaml +12 -0
  257. package/helm/services/iec-builder/values.yaml +56 -0
  258. package/helm/vault-values.yaml +127 -0
  259. package/package.json +45 -0
  260. package/progress.md +156 -0
  261. package/scripts/.vault-init-keys.json +23 -0
  262. package/scripts/backfill-ownership.ts +113 -0
  263. package/scripts/finalize-mongo-auth.sh +212 -0
  264. package/scripts/setup-ipset.sh +107 -0
  265. package/scripts/setup-mongo-auth.sh +163 -0
  266. package/scripts/setup-neo4j-auth.sh +62 -0
  267. package/scripts/setup-redis-auth.sh +55 -0
  268. package/scripts/setup-registry-secret.sh +71 -0
  269. package/scripts/setup-vault.sh +308 -0
  270. package/src/config/env.ts +117 -0
  271. package/src/index.ts +153 -0
  272. package/src/middleware/auth.ts +294 -0
  273. package/src/middleware/org-access.ts +126 -0
  274. package/src/models/types.ts +288 -0
  275. package/src/routes/ai.ts +115 -0
  276. package/src/routes/audit.ts +121 -0
  277. package/src/routes/builds.ts +320 -0
  278. package/src/routes/cluster.ts +235 -0
  279. package/src/routes/config.ts +369 -0
  280. package/src/routes/databases.ts +201 -0
  281. package/src/routes/db-whitelist.ts +204 -0
  282. package/src/routes/domains.ts +547 -0
  283. package/src/routes/oauth.ts +195 -0
  284. package/src/routes/observability.ts +205 -0
  285. package/src/routes/orgs.ts +330 -0
  286. package/src/routes/platform.ts +134 -0
  287. package/src/routes/rotation.ts +191 -0
  288. package/src/routes/services.ts +290 -0
  289. package/src/routes/storage.ts +153 -0
  290. package/src/routes/users.ts +235 -0
  291. package/src/routes/webhooks.ts +384 -0
  292. package/src/services/__tests__/catalog-storage.test.ts +186 -0
  293. package/src/services/__tests__/deploy-pipeline.integration.test.ts +624 -0
  294. package/src/services/__tests__/pod-diagnostics.test.ts +332 -0
  295. package/src/services/__tests__/storage-credential-manager.test.ts +129 -0
  296. package/src/services/__tests__/storage-provisioner.test.ts +166 -0
  297. package/src/services/__tests__/troubleshooter.test.ts +329 -0
  298. package/src/services/bio-client.ts +189 -0
  299. package/src/services/build-queue.ts +137 -0
  300. package/src/services/builder.ts +1800 -0
  301. package/src/services/catalog.test.ts +1389 -0
  302. package/src/services/catalog.ts +1187 -0
  303. package/src/services/cloudflare.ts +259 -0
  304. package/src/services/config-validator.test.ts +190 -0
  305. package/src/services/config-validator.ts +108 -0
  306. package/src/services/crypto.ts +78 -0
  307. package/src/services/database.ts +122 -0
  308. package/src/services/db-credential-manager.test.ts +101 -0
  309. package/src/services/db-credential-manager.ts +447 -0
  310. package/src/services/db-provisioner.test.ts +602 -0
  311. package/src/services/db-provisioner.ts +589 -0
  312. package/src/services/db-whitelist.test.ts +671 -0
  313. package/src/services/db-whitelist.ts +496 -0
  314. package/src/services/dependency-resolver.test.ts +677 -0
  315. package/src/services/dependency-resolver.ts +319 -0
  316. package/src/services/deploy-gate.test.ts +247 -0
  317. package/src/services/deploy-gate.ts +75 -0
  318. package/src/services/dockerfile-generator.test.ts +401 -0
  319. package/src/services/dockerfile-generator.ts +606 -0
  320. package/src/services/forgejo.ts +212 -0
  321. package/src/services/koko.ts +492 -0
  322. package/src/services/kubernetes.ts +141 -0
  323. package/src/services/oauth-provisioner.test.ts +477 -0
  324. package/src/services/oauth-provisioner.ts +286 -0
  325. package/src/services/pod-diagnostics.ts +261 -0
  326. package/src/services/rotation-scheduler.ts +293 -0
  327. package/src/services/storage-credential-manager.ts +223 -0
  328. package/src/services/storage-provisioner.ts +216 -0
  329. package/src/services/storage.ts +274 -0
  330. package/src/services/troubleshooter.ts +208 -0
  331. package/src/services/vault-client.test.ts +272 -0
  332. package/src/services/vault-client.ts +587 -0
  333. package/src/utils/logger.ts +6 -0
  334. package/src/utils/response.ts +23 -0
  335. package/task_plan.md +171 -0
  336. package/tsconfig.json +20 -0
  337. package/vitest.config.ts +19 -0
package/progress.md ADDED
@@ -0,0 +1,156 @@
1
+ # Progress Log — Phase 2: Vault Dynamic Credentials
2
+
3
+ ## Session: 2026-02-17 (Planning)
4
+
5
+ ### Planning & Architecture Design
6
+ - **Status:** complete
7
+ - **Started:** 2026-02-17
8
+ - Actions taken:
9
+ - Explored current infrastructure: Helm charts, db-provisioner, credential manager, builder deploy flow
10
+ - Identified Helm chart gaps: missing `podAnnotations` and `serviceAccountName`
11
+ - Designed Vault Agent sidecar + entrypoint wrapper pattern (no app code changes)
12
+ - Chose AWS KMS for auto-unseal, Raft for storage, standalone mode
13
+ - Scoped all three DB types: MongoDB dynamic, Redis ACL, Neo4j (evaluate plugin)
14
+ - Documented architecture, annotations pattern, entrypoint wrapper
15
+ - Created task_plan.md, findings.md, progress.md
16
+ - Files created/modified:
17
+ - task_plan.md (created)
18
+ - findings.md (created)
19
+ - progress.md (created)
20
+ - .claude/plans/pure-snuggling-sketch.md (created — detailed architecture plan)
21
+
22
+ ---
23
+
24
+ ## Phase 2a: Vault Infrastructure (Days 1-3)
25
+ - **Status:** complete
26
+ - Actions taken:
27
+ - Created AWS KMS key `00d61cf0-a84f-49ef-a54d-62465dc09931` (alias: tawa-vault-unseal, us-east-1)
28
+ - Created IAM user `tawa-vault-unseal` with scoped Encrypt/Decrypt policy
29
+ - Created K8s secret `vault-kms-creds` in vault namespace
30
+ - Deployed Vault OSS 1.21.2 via Helm (standalone, Raft storage, AWS KMS auto-unseal)
31
+ - Initialized Vault, stored recovery keys to `~/vault-init-keys.json`
32
+ - Enabled: K8s auth, database secrets engine, KV v2, file audit device
33
+ - Configured MongoDB connection (`mongodb-tawa`) in database engine
34
+ - Created 12 Vault database roles (7 prod + 4 sandbox + 1 shared)
35
+ - Created 11 policies (least-privilege per service)
36
+ - Created 11 K8s auth role bindings (ServiceAccount → Vault policy)
37
+ - Exposed Vault via NodePort 30820 for builder access
38
+ - Added `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_ENABLED=false` to builder `.env`
39
+ - Verified dynamic credential generation: end-to-end test with koko-prod role
40
+ - Files created/modified:
41
+ - `scripts/setup-vault.sh` (created — deployment + init script)
42
+ - `helm/vault-values.yaml` (created — Vault Helm chart values)
43
+ - `src/services/vault-client.ts` (created — Vault API client)
44
+ - `src/services/vault-client.test.ts` (created — 21 tests)
45
+ - `src/config/env.ts` (modified — added VAULT_ADDR, VAULT_TOKEN, VAULT_ENABLED)
46
+ - `helm/default-service/templates/deployment.yaml` (modified — podAnnotations + serviceAccountName)
47
+ - `helm/default-service/values.yaml` (modified — added defaults)
48
+ - `src/services/dockerfile-generator.ts` (modified — Vault entrypoint wrapper)
49
+
50
+ ## Phase 2b: Wire Vault into Deploy Pipeline (Days 3-7)
51
+ - **Status:** complete
52
+ - Actions taken:
53
+ - Added `provisionDatabasesVault()` helper to builder.ts (creates Vault roles, policies, K8s auth, returns annotations)
54
+ - Modified `executeBuild()` with feature-flagged Vault branch: `isVaultEnabled() && isVaultHealthy()`
55
+ - Vault branch creates Vault database roles + K8s auth, skips K8s secret creation (Vault Agent handles it)
56
+ - Fallback: if Vault provisioning throws, catches error and falls back to Phase 1 `provisionDatabases()`
57
+ - Modified `deployToKubernetes()` to accept `vaultAnnotations` parameter
58
+ - Added Vault annotation injection via `--set-json podAnnotations` + `--set serviceAccountName`
59
+ - Updated Dockerfile entrypoint to source all files in `/vault/secrets/*` (one per DB type)
60
+ - Fixed vault-client.ts connection names: `mongodb-tawa`, `redis-tawa`, `neo4j-tawa`
61
+ - Added 8 integration tests for Vault pipeline (annotations, role names, fallback, all DB types)
62
+ - All 287 tests passing, TypeScript build clean
63
+ - Files created/modified:
64
+ - `src/services/builder.ts` (modified — Vault branch in executeBuild, annotation injection in deployToKubernetes)
65
+ - `src/services/vault-client.ts` (modified — fixed connection names)
66
+ - `src/services/dockerfile-generator.ts` (modified — entrypoint sources /vault/secrets/*)
67
+ - `src/services/__tests__/deploy-pipeline.integration.test.ts` (modified — 8 new Vault tests)
68
+
69
+ ## Phase 2b.1: Production Rollout — Bio-ID (2026-02-17)
70
+ - **Status:** complete
71
+ - Actions taken:
72
+ - Enabled VAULT_ENABLED=true on builder
73
+ - Bio-ID crashed: custom Dockerfile missing Vault entrypoint wrapper
74
+ - Added entrypoint wrapper to bio's custom Dockerfile (sources /vault/secrets/*)
75
+ - Removed bio's unused custom Helm chart (builder uses default-service chart)
76
+ - Fixed Vault template to use VPC IP (10.124.0.3) instead of public IP (64.23.181.20)
77
+ - Added new K8s node (137.184.92.199) to MongoDB iptables + persisted rules
78
+ - Fixed liveness probe: added httpGet:null to prevent dual handler conflict
79
+ - Cleared stale helmChart:'helm' from bio's service DB record
80
+ - Added runAsUser:1001 + fsGroup:1001 to default-service chart values
81
+ - Changed bio Dockerfile to use numeric UID (USER 1001) instead of USER nextjs
82
+ - Bio-ID running with Vault dynamic credentials, 0 restarts, VPC connectivity
83
+ - Files created/modified:
84
+ - `iec-bio/apps/bio-id/Dockerfile` (modified — Vault entrypoint + numeric UID)
85
+ - `iec-bio/apps/bio-id/.iec.yaml` (modified — restored dockerfile: Dockerfile)
86
+ - `iec-bio/apps/bio-id/helm/` (deleted — unused custom chart)
87
+ - `src/services/vault-client.ts` (modified — use DB_MONGODB_HOST for Vault template)
88
+ - `src/services/builder.ts` (modified — httpGet:null in liveness probe override)
89
+ - `helm/default-service/values.yaml` (modified — runAsUser:1001, fsGroup:1001)
90
+ - Builder .env: DB_MONGODB_HOST=10.124.0.3, DB_REDIS_HOST=10.124.0.3
91
+ - Errors encountered:
92
+ - Custom Dockerfile missing Vault entrypoint → bio fell back to localhost:27017/iec_bio
93
+ - Generated Dockerfile can't handle turborepo → must use custom Dockerfile
94
+ - New K8s node IP not in iptables → MongoDB connection timeout from pods
95
+ - Stale helmChart in DB → Helm tried to use deleted directory
96
+ - Non-numeric USER in Dockerfile → K8s runAsNonRoot rejection
97
+
98
+ ## Phase 2c: Redis ACLs via Vault (Days 7-9)
99
+ - **Status:** pending
100
+ - Actions taken:
101
+ -
102
+ - Files created/modified:
103
+ -
104
+
105
+ ## Phase 2d: Neo4j Auth via Vault (Days 9-11)
106
+ - **Status:** pending
107
+ - Actions taken:
108
+ -
109
+ - Files created/modified:
110
+ -
111
+
112
+ ## Phase 2e: Migration & Hardening (Days 11-14)
113
+ - **Status:** pending
114
+ - Actions taken:
115
+ -
116
+ - Files created/modified:
117
+ -
118
+
119
+ ## Test Results
120
+ | Test | Input | Expected | Actual | Status |
121
+ |------|-------|----------|--------|--------|
122
+ | Vault health | `vault status` | Initialized + unsealed | Initialized=true, Sealed=false, v1.21.2 | pass |
123
+ | K8s auth | `vault auth list` | kubernetes/ method listed | kubernetes/ enabled | pass |
124
+ | MongoDB dynamic creds | `vault read database/creds/svc-koko-prod-mongodb` | Short-lived user + password | lease_duration=1h, renewable=true | pass |
125
+ | Auto-unseal | Kill vault pod, wait | Pod recovers, unsealed | | pending |
126
+ | MongoDB dynamic creds | Deploy sandbox svc | Pod has sidecar, MONGODB_URI valid | | pending |
127
+ | Lease renewal | Wait 1hr | No disruption, lease renewed | | pending |
128
+ | Phase 1 fallback | Set VAULT_ENABLED=false, redeploy | Static creds work | | pending |
129
+ | Redis ACL | Deploy sandbox svc | REDIS_URL with credentials | | pending |
130
+ | Redis key isolation | Write to other service prefix | Permission denied | | pending |
131
+ | Neo4j auth | Deploy sandbox svc | NEO4J_URI + credentials valid | | pending |
132
+ | Full migration | Run migrate-to-vault.ts | All services have Vault roles | | pending |
133
+ | All health checks | After migration | All services healthy | | pending |
134
+
135
+ ## Error Log
136
+ | Timestamp | Error | Attempt | Resolution |
137
+ |-----------|-------|---------|------------|
138
+ | | | | |
139
+
140
+ ## Blockers
141
+ - [x] ~~Phase 1 extension (Redis ACL + Neo4j static auth) must complete first~~ — Phase 1 COMPLETE
142
+ - [x] ~~AWS account needed for KMS key~~ — KMS key created
143
+ - [ ] Redis version must be confirmed >= 6.0
144
+ - [ ] Neo4j Vault plugin maturity must be evaluated
145
+
146
+ ## 5-Question Reboot Check
147
+ | Question | Answer |
148
+ |----------|--------|
149
+ | Where am I? | Phase 2a + 2b COMPLETE. Ready for testing with a sandbox deploy |
150
+ | Where am I going? | Test with sandbox service, then Phase 2c (Redis ACLs) or 2e (migration) |
151
+ | What's the goal? | Replace static DB passwords with Vault dynamic credentials |
152
+ | What have I learned? | Vault wired into builder, feature-flagged, graceful fallback works |
153
+ | What have I done? | Vault infra deployed, builder code branching on VAULT_ENABLED, 287 tests green |
154
+
155
+ ---
156
+ *Update after completing each phase or encountering errors*
@@ -0,0 +1,23 @@
1
+ {
2
+ "unseal_keys_b64": [],
3
+ "unseal_keys_hex": [],
4
+ "unseal_shares": 1,
5
+ "unseal_threshold": 1,
6
+ "recovery_keys_b64": [
7
+ "ewoWGuci8aYmg/mEbyjIX4YHUOKfFzTiqQZDHnPiJADz",
8
+ "0KFRyVBV7VDJRPq+JBtCcVTT3KIh9mf0aRq2azrr8bYG",
9
+ "7PK+sZSLN9xwW75Kxe0pTRm48bM+DmMXp/Adn0ABRNkP",
10
+ "Hzj7Ar4+Pk+8lwkWJR0FnJX39sANDj8Q1I0d5CUofQ6k",
11
+ "Q+0y35dkqh1n44C/aefvQ3e/nYkRoB1Wm4Fp8qX5DCNV"
12
+ ],
13
+ "recovery_keys_hex": [
14
+ "7b0a161ae722f1a62683f9846f28c85f860750e29f1734e2a906431e73e22400f3",
15
+ "d0a151c95055ed50c944fabe241b427154d3dca221f667f4691ab66b3aebf1b606",
16
+ "ecf2beb1948b37dc705bbe4ac5ed294d19b8f1b33e0e6317a7f01d9f400144d90f",
17
+ "1f38fb02be3e3e4fbc970916251d059c95f7f6c00d0e3f10d48d1de425287d0ea4",
18
+ "43ed32df9764aa1d67e380bf69e7ef4377bf9d8911a01d569b8169f2a5f90c2355"
19
+ ],
20
+ "recovery_keys_shares": 5,
21
+ "recovery_keys_threshold": 3,
22
+ "root_token": "hvs.MS9jJK9L8FoTIPJesT1emjWE"
23
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One-time script to backfill org and createdBy on existing services.
3
+ *
4
+ * For each service missing org/createdBy:
5
+ * 1. Finds the oldest build with org/requestedBy set
6
+ * 2. Falls back to extracting org from repoUrl
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/backfill-ownership.ts [--dry-run]
10
+ */
11
+
12
+ import { MongoClient } from 'mongodb'
13
+ import { config } from 'dotenv'
14
+ import { dirname, resolve } from 'path'
15
+ import { fileURLToPath } from 'url'
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url))
18
+ config({ path: resolve(__dirname, '../.env') })
19
+
20
+ const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://64.23.181.20:27017/builder'
21
+ const DRY_RUN = process.argv.includes('--dry-run')
22
+
23
+ function extractRepoOrg(repoUrl: string): string | undefined {
24
+ try {
25
+ const url = new URL(repoUrl)
26
+ const parts = url.pathname.split('/').filter(Boolean)
27
+ if (parts.length >= 2) return parts[0]
28
+ } catch {
29
+ // Not a valid URL — try SSH format
30
+ }
31
+
32
+ const sshMatch = repoUrl.match(/^[^@]+@[^:]+:([^/]+)\//)
33
+ if (sshMatch) return sshMatch[1]
34
+
35
+ return undefined
36
+ }
37
+
38
+ async function main() {
39
+ console.log(`Backfill service ownership${DRY_RUN ? ' (DRY RUN)' : ''}`)
40
+ console.log(`MongoDB: ${MONGODB_URI.replace(/\/\/[^@]+@/, '//<redacted>@')}`)
41
+
42
+ const client = new MongoClient(MONGODB_URI)
43
+ await client.connect()
44
+ const db = client.db()
45
+
46
+ const services = db.collection('services')
47
+ const builds = db.collection('builds')
48
+
49
+ const needsBackfill = await services
50
+ .find({ $or: [{ org: { $exists: false } }, { createdBy: { $exists: false } }] })
51
+ .toArray()
52
+
53
+ console.log(`Found ${needsBackfill.length} services needing backfill\n`)
54
+
55
+ let updated = 0
56
+ let skipped = 0
57
+
58
+ for (const service of needsBackfill) {
59
+ const updates: Record<string, string> = {}
60
+
61
+ // Find oldest build for this service with org or requestedBy
62
+ const oldestBuild = await builds.findOne(
63
+ {
64
+ serviceId: service.id,
65
+ $or: [
66
+ { org: { $exists: true, $ne: null } },
67
+ { requestedBy: { $exists: true, $ne: null } },
68
+ ],
69
+ },
70
+ { sort: { createdAt: 1 } }
71
+ )
72
+
73
+ if (!service.org) {
74
+ if (oldestBuild?.org) {
75
+ updates.org = oldestBuild.org
76
+ } else {
77
+ const repoOrg = extractRepoOrg(service.repoUrl)
78
+ if (repoOrg) {
79
+ updates.org = repoOrg
80
+ }
81
+ }
82
+ }
83
+
84
+ if (!service.createdBy && oldestBuild?.requestedBy) {
85
+ updates.createdBy = oldestBuild.requestedBy
86
+ }
87
+
88
+ if (Object.keys(updates).length === 0) {
89
+ console.log(` SKIP ${service.name} — no data to backfill`)
90
+ skipped++
91
+ continue
92
+ }
93
+
94
+ console.log(` ${DRY_RUN ? 'WOULD UPDATE' : 'UPDATE'} ${service.name} → ${JSON.stringify(updates)}`)
95
+
96
+ if (!DRY_RUN) {
97
+ await services.updateOne(
98
+ { id: service.id },
99
+ { $set: { ...updates, updatedAt: new Date().toISOString() } }
100
+ )
101
+ }
102
+
103
+ updated++
104
+ }
105
+
106
+ console.log(`\nDone. Updated: ${updated}, Skipped: ${skipped}`)
107
+ await client.close()
108
+ }
109
+
110
+ main().catch((err) => {
111
+ console.error('Backfill failed:', err)
112
+ process.exit(1)
113
+ })
@@ -0,0 +1,212 @@
1
+ #!/bin/bash
2
+ # Finalize MongoDB auth: enable authorization on standalone MongoDB.
3
+ # Run ONLY after all services have been redeployed with credentials.
4
+ #
5
+ # Usage:
6
+ # Check status: ssh tawa 'bash -s' < scripts/finalize-mongo-auth.sh --check
7
+ # Enable auth: ssh tawa 'bash -s' < scripts/finalize-mongo-auth.sh
8
+ # Rollback auth: ssh tawa 'bash -s' < scripts/finalize-mongo-auth.sh --rollback
9
+
10
+ set -euo pipefail
11
+
12
+ MONGOD_CONF="/etc/mongod.conf"
13
+ BUILDER_ENV="/opt/iec-builder/.env"
14
+
15
+ # Parse args
16
+ ACTION="${1:-finalize}"
17
+
18
+ # ----- Helper: check credential status -----
19
+
20
+ check_credentials() {
21
+ if ! grep -q "^DB_MONGODB_ADMIN_URI=" "$BUILDER_ENV" 2>/dev/null; then
22
+ echo "ERROR: DB_MONGODB_ADMIN_URI not set in builder .env"
23
+ echo "Run setup-mongo-auth.sh first."
24
+ return 1
25
+ fi
26
+
27
+ ADMIN_URI=$(grep "^DB_MONGODB_ADMIN_URI=" "$BUILDER_ENV" | cut -d'=' -f2-)
28
+
29
+ echo "Checking service credentials..."
30
+ echo ""
31
+
32
+ mongosh --quiet "$ADMIN_URI" --eval '
33
+ const svcs = db.getSiblingDB("builder").services.find(
34
+ {},
35
+ { name: 1, databaseCredentials: 1, "catalog.databases": 1 }
36
+ ).toArray()
37
+
38
+ const withMongo = svcs.filter(s =>
39
+ s.catalog && s.catalog.databases &&
40
+ s.catalog.databases.some(d => d.type === "mongodb")
41
+ )
42
+
43
+ const withCreds = withMongo.filter(s =>
44
+ s.databaseCredentials && s.databaseCredentials.length > 0
45
+ )
46
+ const withoutCreds = withMongo.filter(s =>
47
+ !s.databaseCredentials || s.databaseCredentials.length === 0
48
+ )
49
+
50
+ print("Services with MongoDB databases: " + withMongo.length)
51
+ print("")
52
+
53
+ if (withCreds.length > 0) {
54
+ print(" With credentials (" + withCreds.length + "):")
55
+ withCreds.forEach(s => {
56
+ const creds = s.databaseCredentials.map(c => c.username + " (" + c.environment + ")")
57
+ print(" " + s.name + ": " + creds.join(", "))
58
+ })
59
+ }
60
+
61
+ print("")
62
+
63
+ if (withoutCreds.length > 0) {
64
+ print(" WITHOUT credentials (" + withoutCreds.length + "):")
65
+ withoutCreds.forEach(s => print(" " + s.name))
66
+ print("")
67
+ print("WARNING: These services will lose database access when auth is enabled.")
68
+ print("Redeploy them first: tawa deploy <service>")
69
+ } else {
70
+ print(" All services have credentials!")
71
+ print("")
72
+ print("Safe to finalize: ssh tawa '\''bash -s'\'' < scripts/finalize-mongo-auth.sh")
73
+ }
74
+ ' 2>/dev/null || echo "Could not query builder database"
75
+ }
76
+
77
+ # ----- --check: just show status -----
78
+
79
+ if [ "$ACTION" = "--check" ]; then
80
+ echo "=== MongoDB Auth Migration Status ==="
81
+ echo ""
82
+
83
+ if grep -q "authorization: enabled" "$MONGOD_CONF" 2>/dev/null; then
84
+ echo "Auth status: ENABLED (full enforcement)"
85
+ else
86
+ echo "Auth status: DISABLED (migration in progress)"
87
+ fi
88
+ echo ""
89
+
90
+ check_credentials
91
+ exit 0
92
+ fi
93
+
94
+ # ----- --rollback: disable auth -----
95
+
96
+ if [ "$ACTION" = "--rollback" ]; then
97
+ echo "=== Rolling Back MongoDB Auth ==="
98
+ echo ""
99
+
100
+ if ! grep -q "authorization: enabled" "$MONGOD_CONF" 2>/dev/null; then
101
+ echo "Auth is not enabled. Nothing to roll back."
102
+ exit 0
103
+ fi
104
+
105
+ sudo cp "$MONGOD_CONF" "${MONGOD_CONF}.bak.$(date +%Y%m%d%H%M%S)"
106
+
107
+ # Remove the entire security section
108
+ sudo python3 -c "
109
+ import re
110
+ with open('$MONGOD_CONF', 'r') as f:
111
+ content = f.read()
112
+ # Remove security section (last one in file)
113
+ content = re.sub(r'\nsecurity:\n authorization: enabled\n?', '\n', content)
114
+ with open('$MONGOD_CONF', 'w') as f:
115
+ f.write(content)
116
+ "
117
+
118
+ echo "Restarting MongoDB without auth..."
119
+ sudo systemctl restart mongod
120
+ sleep 3
121
+
122
+ if ! systemctl is-active --quiet mongod; then
123
+ echo "ERROR: MongoDB failed to restart! Rolling back config..."
124
+ LATEST_BACKUP=$(ls -t "${MONGOD_CONF}.bak."* 2>/dev/null | head -1)
125
+ if [ -n "$LATEST_BACKUP" ]; then
126
+ sudo cp "$LATEST_BACKUP" "$MONGOD_CONF"
127
+ sudo systemctl restart mongod
128
+ fi
129
+ exit 1
130
+ fi
131
+
132
+ echo "MongoDB auth disabled. All connections accepted."
133
+ exit 0
134
+ fi
135
+
136
+ # ----- Finalize: enable auth -----
137
+
138
+ echo "=== Finalize MongoDB Auth ==="
139
+ echo ""
140
+
141
+ if grep -q "authorization: enabled" "$MONGOD_CONF" 2>/dev/null; then
142
+ echo "Auth is already enabled."
143
+ exit 0
144
+ fi
145
+
146
+ # Check admin URI is set
147
+ if ! grep -q "^DB_MONGODB_ADMIN_URI=" "$BUILDER_ENV" 2>/dev/null; then
148
+ echo "ERROR: DB_MONGODB_ADMIN_URI not set in builder .env"
149
+ echo "Run setup-mongo-auth.sh first."
150
+ exit 1
151
+ fi
152
+
153
+ ADMIN_URI=$(grep "^DB_MONGODB_ADMIN_URI=" "$BUILDER_ENV" | cut -d'=' -f2-)
154
+
155
+ # Show credential status
156
+ check_credentials
157
+ echo ""
158
+
159
+ read -p "Proceed with full auth enforcement? Services without credentials will lose DB access. (yes/no): " CONFIRM
160
+ if [ "$CONFIRM" != "yes" ]; then
161
+ echo "Aborted."
162
+ exit 0
163
+ fi
164
+
165
+ # ----- Enable auth -----
166
+
167
+ echo "Updating $MONGOD_CONF..."
168
+ sudo cp "$MONGOD_CONF" "${MONGOD_CONF}.bak.$(date +%Y%m%d%H%M%S)"
169
+
170
+ # Add security section at end of file
171
+ sudo tee -a "$MONGOD_CONF" > /dev/null <<'MONGOCFG'
172
+
173
+ security:
174
+ authorization: enabled
175
+ MONGOCFG
176
+
177
+ echo "Restarting MongoDB..."
178
+ sudo systemctl restart mongod
179
+ sleep 3
180
+
181
+ if ! systemctl is-active --quiet mongod; then
182
+ echo "ERROR: MongoDB failed to restart! Rolling back..."
183
+ LATEST_BACKUP=$(ls -t "${MONGOD_CONF}.bak."* 2>/dev/null | head -1)
184
+ if [ -n "$LATEST_BACKUP" ]; then
185
+ sudo cp "$LATEST_BACKUP" "$MONGOD_CONF"
186
+ sudo systemctl restart mongod
187
+ fi
188
+ exit 1
189
+ fi
190
+
191
+ # Verify auth is enforced
192
+ UNAUTH_TEST=$(mongosh --quiet --eval 'db.getSiblingDB("builder").services.countDocuments()' 2>&1 || echo "DENIED")
193
+ if echo "$UNAUTH_TEST" | grep -qi "auth\|denied\|unauthorized"; then
194
+ echo "Unauthenticated data access: BLOCKED (correct)"
195
+ else
196
+ echo "WARNING: Unauthenticated data access may still work"
197
+ fi
198
+
199
+ AUTH_TEST=$(mongosh --quiet "$ADMIN_URI" --eval 'db.adminCommand({ping:1}).ok' 2>/dev/null || echo "FAIL")
200
+ if [ "$AUTH_TEST" = "1" ]; then
201
+ echo "Admin auth connection: OK"
202
+ else
203
+ echo "ERROR: Admin auth connection failed!"
204
+ fi
205
+
206
+ echo ""
207
+ echo "=== MongoDB Auth Finalized ==="
208
+ echo "All unauthenticated connections are now rejected."
209
+ echo "Services must have credentials to connect."
210
+ echo ""
211
+ echo "To roll back in an emergency:"
212
+ echo " ssh tawa 'bash -s' < scripts/finalize-mongo-auth.sh --rollback"
@@ -0,0 +1,107 @@
1
+ #!/bin/bash
2
+ # Setup ipset-based MongoDB access control on tawa-builder server.
3
+ # Run once: ssh tawa 'bash -s' < scripts/setup-ipset.sh
4
+ #
5
+ # Creates a named IP set "mongodb-access" and a single iptables rule
6
+ # that allows port 27017 from IPs in the set. Each entry supports
7
+ # per-IP TTL (auto-expiry), managed by the builder API.
8
+
9
+ set -euo pipefail
10
+
11
+ IPSET_NAME="mongodb-access"
12
+ MONGO_PORT=27017
13
+
14
+ echo "=== MongoDB IP Whitelist Setup ==="
15
+ echo ""
16
+
17
+ # 1. Install ipset if not present
18
+ if ! command -v ipset &>/dev/null; then
19
+ echo "Installing ipset..."
20
+ sudo apt-get update -qq && sudo apt-get install -y -qq ipset
21
+ fi
22
+
23
+ echo "ipset version: $(ipset --version | head -1)"
24
+
25
+ # 2. Create the hash:ip set with timeout support (idempotent)
26
+ if ipset list "$IPSET_NAME" &>/dev/null; then
27
+ echo "ipset '$IPSET_NAME' already exists"
28
+ else
29
+ sudo ipset create "$IPSET_NAME" hash:ip timeout 0 maxelem 65536
30
+ echo "Created ipset '$IPSET_NAME'"
31
+ fi
32
+
33
+ # 3. Add iptables rules (idempotent)
34
+ # Rule order: ipset ACCEPT → localhost ACCEPT → private ACCEPT → DROP all other 27017
35
+ if sudo iptables -C INPUT -p tcp --dport "$MONGO_PORT" -m set --match-set "$IPSET_NAME" src -j ACCEPT 2>/dev/null; then
36
+ echo "iptables ipset rule already exists"
37
+ else
38
+ sudo iptables -I INPUT -p tcp --dport "$MONGO_PORT" -m set --match-set "$IPSET_NAME" src -j ACCEPT
39
+ echo "Added iptables rule: ACCEPT tcp/$MONGO_PORT from ipset '$IPSET_NAME'"
40
+ fi
41
+
42
+ # Allow localhost (builder connects locally)
43
+ if sudo iptables -C INPUT -p tcp --dport "$MONGO_PORT" -s 127.0.0.1 -j ACCEPT 2>/dev/null; then
44
+ echo "iptables localhost rule already exists"
45
+ else
46
+ sudo iptables -I INPUT 2 -p tcp --dport "$MONGO_PORT" -s 127.0.0.1 -j ACCEPT
47
+ echo "Added iptables rule: ACCEPT tcp/$MONGO_PORT from localhost"
48
+ fi
49
+
50
+ # Allow private network (K8s nodes, internal services)
51
+ if sudo iptables -C INPUT -p tcp --dport "$MONGO_PORT" -s 10.0.0.0/8 -j ACCEPT 2>/dev/null; then
52
+ echo "iptables private network rule already exists"
53
+ else
54
+ sudo iptables -I INPUT 3 -p tcp --dport "$MONGO_PORT" -s 10.0.0.0/8 -j ACCEPT
55
+ echo "Added iptables rule: ACCEPT tcp/$MONGO_PORT from 10.0.0.0/8"
56
+ fi
57
+
58
+ # DROP all other 27017 traffic
59
+ if sudo iptables -C INPUT -p tcp --dport "$MONGO_PORT" -j DROP 2>/dev/null; then
60
+ echo "iptables DROP rule already exists"
61
+ else
62
+ sudo iptables -I INPUT 4 -p tcp --dport "$MONGO_PORT" -j DROP
63
+ echo "Added iptables rule: DROP tcp/$MONGO_PORT (all non-whitelisted)"
64
+ fi
65
+
66
+ # 4. Persist ipset and iptables across reboots
67
+ echo "Persisting ipset rules..."
68
+ sudo ipset save | sudo tee /etc/ipset.rules >/dev/null
69
+
70
+ # Create systemd service to restore ipset on boot (before iptables)
71
+ sudo tee /etc/systemd/system/ipset-restore.service >/dev/null <<'EOF'
72
+ [Unit]
73
+ Description=Restore ipset rules
74
+ Before=netfilter-persistent.service
75
+ DefaultDependencies=no
76
+
77
+ [Service]
78
+ Type=oneshot
79
+ ExecStart=/sbin/ipset restore -f /etc/ipset.rules
80
+ RemainAfterExit=yes
81
+
82
+ [Install]
83
+ WantedBy=multi-user.target
84
+ EOF
85
+
86
+ sudo systemctl daemon-reload
87
+ sudo systemctl enable ipset-restore.service
88
+
89
+ # Persist iptables
90
+ if command -v netfilter-persistent &>/dev/null; then
91
+ sudo netfilter-persistent save
92
+ echo "iptables rules persisted via netfilter-persistent"
93
+ else
94
+ echo "WARNING: netfilter-persistent not found. Install with: apt-get install iptables-persistent"
95
+ echo "iptables rule will not survive reboot without it."
96
+ fi
97
+
98
+ echo ""
99
+ echo "=== Setup Complete ==="
100
+ echo ""
101
+ echo "The builder can now manage access via:"
102
+ echo " ipset add $IPSET_NAME <IP> timeout <seconds>"
103
+ echo " ipset del $IPSET_NAME <IP>"
104
+ echo " ipset list $IPSET_NAME"
105
+ echo ""
106
+ echo "Current set members:"
107
+ sudo ipset list "$IPSET_NAME" | tail -n +8