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.
- package/.claude/settings.local.json +111 -0
- package/.iec.yaml +5 -0
- package/CLAUDE.md +174 -0
- package/Dockerfile +34 -0
- package/README.md +84 -0
- package/catalog-info.yaml +11 -0
- package/dist/config/env.d.ts +219 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +89 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +43 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +217 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/org-access.d.ts +28 -0
- package/dist/middleware/org-access.d.ts.map +1 -0
- package/dist/middleware/org-access.js +102 -0
- package/dist/middleware/org-access.js.map +1 -0
- package/dist/models/types.d.ts +254 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +2 -0
- package/dist/models/types.js.map +1 -0
- package/dist/routes/ai.d.ts +2 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +77 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/audit.d.ts +2 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +102 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/builds.d.ts +2 -0
- package/dist/routes/builds.d.ts.map +1 -0
- package/dist/routes/builds.js +262 -0
- package/dist/routes/builds.js.map +1 -0
- package/dist/routes/cluster.d.ts +2 -0
- package/dist/routes/cluster.d.ts.map +1 -0
- package/dist/routes/cluster.js +181 -0
- package/dist/routes/cluster.js.map +1 -0
- package/dist/routes/config.d.ts +2 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +291 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/databases.d.ts +2 -0
- package/dist/routes/databases.d.ts.map +1 -0
- package/dist/routes/databases.js +161 -0
- package/dist/routes/databases.js.map +1 -0
- package/dist/routes/db-whitelist.d.ts +2 -0
- package/dist/routes/db-whitelist.d.ts.map +1 -0
- package/dist/routes/db-whitelist.js +148 -0
- package/dist/routes/db-whitelist.js.map +1 -0
- package/dist/routes/domains.d.ts +2 -0
- package/dist/routes/domains.d.ts.map +1 -0
- package/dist/routes/domains.js +449 -0
- package/dist/routes/domains.js.map +1 -0
- package/dist/routes/oauth.d.ts +2 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +180 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/observability.d.ts +2 -0
- package/dist/routes/observability.d.ts.map +1 -0
- package/dist/routes/observability.js +167 -0
- package/dist/routes/observability.js.map +1 -0
- package/dist/routes/orgs.d.ts +2 -0
- package/dist/routes/orgs.d.ts.map +1 -0
- package/dist/routes/orgs.js +270 -0
- package/dist/routes/orgs.js.map +1 -0
- package/dist/routes/platform.d.ts +2 -0
- package/dist/routes/platform.d.ts.map +1 -0
- package/dist/routes/platform.js +107 -0
- package/dist/routes/platform.js.map +1 -0
- package/dist/routes/push.d.ts +2 -0
- package/dist/routes/push.d.ts.map +1 -0
- package/dist/routes/push.js +233 -0
- package/dist/routes/push.js.map +1 -0
- package/dist/routes/rotation.d.ts +3 -0
- package/dist/routes/rotation.d.ts.map +1 -0
- package/dist/routes/rotation.js +154 -0
- package/dist/routes/rotation.js.map +1 -0
- package/dist/routes/services.d.ts +2 -0
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +246 -0
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/storage.d.ts +2 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +118 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +183 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/versions.d.ts +2 -0
- package/dist/routes/versions.d.ts.map +1 -0
- package/dist/routes/versions.js +195 -0
- package/dist/routes/versions.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +334 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts +2 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js +482 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js.map +1 -0
- package/dist/services/bio-client.d.ts +68 -0
- package/dist/services/bio-client.d.ts.map +1 -0
- package/dist/services/bio-client.js +110 -0
- package/dist/services/bio-client.js.map +1 -0
- package/dist/services/build-queue.d.ts +7 -0
- package/dist/services/build-queue.d.ts.map +1 -0
- package/dist/services/build-queue.js +114 -0
- package/dist/services/build-queue.js.map +1 -0
- package/dist/services/builder.d.ts +7 -0
- package/dist/services/builder.d.ts.map +1 -0
- package/dist/services/builder.js +1384 -0
- package/dist/services/builder.js.map +1 -0
- package/dist/services/catalog.d.ts +177 -0
- package/dist/services/catalog.d.ts.map +1 -0
- package/dist/services/catalog.js +805 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/catalog.test.d.ts +2 -0
- package/dist/services/catalog.test.d.ts.map +1 -0
- package/dist/services/catalog.test.js +467 -0
- package/dist/services/catalog.test.js.map +1 -0
- package/dist/services/cloudflare.d.ts +43 -0
- package/dist/services/cloudflare.d.ts.map +1 -0
- package/dist/services/cloudflare.js +182 -0
- package/dist/services/cloudflare.js.map +1 -0
- package/dist/services/config-validator.d.ts +28 -0
- package/dist/services/config-validator.d.ts.map +1 -0
- package/dist/services/config-validator.js +68 -0
- package/dist/services/config-validator.js.map +1 -0
- package/dist/services/config-validator.test.d.ts +2 -0
- package/dist/services/config-validator.test.d.ts.map +1 -0
- package/dist/services/config-validator.test.js +151 -0
- package/dist/services/config-validator.test.js.map +1 -0
- package/dist/services/crypto.d.ts +19 -0
- package/dist/services/crypto.d.ts.map +1 -0
- package/dist/services/crypto.js +63 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/database.d.ts +26 -0
- package/dist/services/database.d.ts.map +1 -0
- package/dist/services/database.js +100 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/db-credential-manager.d.ts +73 -0
- package/dist/services/db-credential-manager.d.ts.map +1 -0
- package/dist/services/db-credential-manager.js +342 -0
- package/dist/services/db-credential-manager.js.map +1 -0
- package/dist/services/db-provisioner.d.ts +57 -0
- package/dist/services/db-provisioner.d.ts.map +1 -0
- package/dist/services/db-provisioner.js +400 -0
- package/dist/services/db-provisioner.js.map +1 -0
- package/dist/services/db-provisioner.test.d.ts +2 -0
- package/dist/services/db-provisioner.test.d.ts.map +1 -0
- package/dist/services/db-provisioner.test.js +141 -0
- package/dist/services/db-provisioner.test.js.map +1 -0
- package/dist/services/db-whitelist.d.ts +58 -0
- package/dist/services/db-whitelist.d.ts.map +1 -0
- package/dist/services/db-whitelist.js +379 -0
- package/dist/services/db-whitelist.js.map +1 -0
- package/dist/services/dependency-resolver.d.ts +58 -0
- package/dist/services/dependency-resolver.d.ts.map +1 -0
- package/dist/services/dependency-resolver.js +180 -0
- package/dist/services/dependency-resolver.js.map +1 -0
- package/dist/services/dependency-resolver.test.d.ts +2 -0
- package/dist/services/dependency-resolver.test.d.ts.map +1 -0
- package/dist/services/dependency-resolver.test.js +195 -0
- package/dist/services/dependency-resolver.test.js.map +1 -0
- package/dist/services/deploy-gate.d.ts +19 -0
- package/dist/services/deploy-gate.d.ts.map +1 -0
- package/dist/services/deploy-gate.js +56 -0
- package/dist/services/deploy-gate.js.map +1 -0
- package/dist/services/deploy-gate.test.d.ts +2 -0
- package/dist/services/deploy-gate.test.d.ts.map +1 -0
- package/dist/services/deploy-gate.test.js +199 -0
- package/dist/services/deploy-gate.test.js.map +1 -0
- package/dist/services/dockerfile-generator.d.ts +31 -0
- package/dist/services/dockerfile-generator.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.js +544 -0
- package/dist/services/dockerfile-generator.js.map +1 -0
- package/dist/services/dockerfile-generator.test.d.ts +2 -0
- package/dist/services/dockerfile-generator.test.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.test.js +144 -0
- package/dist/services/dockerfile-generator.test.js.map +1 -0
- package/dist/services/forgejo.d.ts +58 -0
- package/dist/services/forgejo.d.ts.map +1 -0
- package/dist/services/forgejo.js +131 -0
- package/dist/services/forgejo.js.map +1 -0
- package/dist/services/koko.d.ts +153 -0
- package/dist/services/koko.d.ts.map +1 -0
- package/dist/services/koko.js +260 -0
- package/dist/services/koko.js.map +1 -0
- package/dist/services/kubernetes.d.ts +16 -0
- package/dist/services/kubernetes.d.ts.map +1 -0
- package/dist/services/kubernetes.js +102 -0
- package/dist/services/kubernetes.js.map +1 -0
- package/dist/services/oauth-provisioner.d.ts +30 -0
- package/dist/services/oauth-provisioner.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.js +182 -0
- package/dist/services/oauth-provisioner.js.map +1 -0
- package/dist/services/oauth-provisioner.test.d.ts +2 -0
- package/dist/services/oauth-provisioner.test.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.test.js +349 -0
- package/dist/services/oauth-provisioner.test.js.map +1 -0
- package/dist/services/pod-diagnostics.d.ts +11 -0
- package/dist/services/pod-diagnostics.d.ts.map +1 -0
- package/dist/services/pod-diagnostics.js +201 -0
- package/dist/services/pod-diagnostics.js.map +1 -0
- package/dist/services/rotation-scheduler.d.ts +2 -0
- package/dist/services/rotation-scheduler.d.ts.map +1 -0
- package/dist/services/rotation-scheduler.js +215 -0
- package/dist/services/rotation-scheduler.js.map +1 -0
- package/dist/services/storage-credential-manager.d.ts +43 -0
- package/dist/services/storage-credential-manager.d.ts.map +1 -0
- package/dist/services/storage-credential-manager.js +159 -0
- package/dist/services/storage-credential-manager.js.map +1 -0
- package/dist/services/storage-provisioner.d.ts +32 -0
- package/dist/services/storage-provisioner.d.ts.map +1 -0
- package/dist/services/storage-provisioner.js +136 -0
- package/dist/services/storage-provisioner.js.map +1 -0
- package/dist/services/storage.d.ts +65 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +204 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/troubleshooter.d.ts +22 -0
- package/dist/services/troubleshooter.d.ts.map +1 -0
- package/dist/services/troubleshooter.js +168 -0
- package/dist/services/troubleshooter.js.map +1 -0
- package/dist/services/vault-client.d.ts +114 -0
- package/dist/services/vault-client.d.ts.map +1 -0
- package/dist/services/vault-client.js +411 -0
- package/dist/services/vault-client.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +6 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +13 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +12 -0
- package/dist/utils/response.js.map +1 -0
- package/docs/registry-migration.md +301 -0
- package/docs/registry-quickstart.md +169 -0
- package/ecosystem.config.cjs +14 -0
- package/findings.md +168 -0
- package/helm/default-service/Chart.yaml +6 -0
- package/helm/default-service/templates/deployment.yaml +97 -0
- package/helm/default-service/templates/ingress.yaml +43 -0
- package/helm/default-service/templates/service.yaml +17 -0
- package/helm/default-service/values.yaml +82 -0
- package/helm/services/iec-builder/Chart.yaml +6 -0
- package/helm/services/iec-builder/templates/_helpers.tpl +61 -0
- package/helm/services/iec-builder/templates/deployment.yaml +73 -0
- package/helm/services/iec-builder/templates/service.yaml +15 -0
- package/helm/services/iec-builder/templates/serviceaccount.yaml +12 -0
- package/helm/services/iec-builder/values.yaml +56 -0
- package/helm/vault-values.yaml +127 -0
- package/package.json +45 -0
- package/progress.md +156 -0
- package/scripts/.vault-init-keys.json +23 -0
- package/scripts/backfill-ownership.ts +113 -0
- package/scripts/finalize-mongo-auth.sh +212 -0
- package/scripts/setup-ipset.sh +107 -0
- package/scripts/setup-mongo-auth.sh +163 -0
- package/scripts/setup-neo4j-auth.sh +62 -0
- package/scripts/setup-redis-auth.sh +55 -0
- package/scripts/setup-registry-secret.sh +71 -0
- package/scripts/setup-vault.sh +308 -0
- package/src/config/env.ts +117 -0
- package/src/index.ts +153 -0
- package/src/middleware/auth.ts +294 -0
- package/src/middleware/org-access.ts +126 -0
- package/src/models/types.ts +288 -0
- package/src/routes/ai.ts +115 -0
- package/src/routes/audit.ts +121 -0
- package/src/routes/builds.ts +320 -0
- package/src/routes/cluster.ts +235 -0
- package/src/routes/config.ts +369 -0
- package/src/routes/databases.ts +201 -0
- package/src/routes/db-whitelist.ts +204 -0
- package/src/routes/domains.ts +547 -0
- package/src/routes/oauth.ts +195 -0
- package/src/routes/observability.ts +205 -0
- package/src/routes/orgs.ts +330 -0
- package/src/routes/platform.ts +134 -0
- package/src/routes/rotation.ts +191 -0
- package/src/routes/services.ts +290 -0
- package/src/routes/storage.ts +153 -0
- package/src/routes/users.ts +235 -0
- package/src/routes/webhooks.ts +384 -0
- package/src/services/__tests__/catalog-storage.test.ts +186 -0
- package/src/services/__tests__/deploy-pipeline.integration.test.ts +624 -0
- package/src/services/__tests__/pod-diagnostics.test.ts +332 -0
- package/src/services/__tests__/storage-credential-manager.test.ts +129 -0
- package/src/services/__tests__/storage-provisioner.test.ts +166 -0
- package/src/services/__tests__/troubleshooter.test.ts +329 -0
- package/src/services/bio-client.ts +189 -0
- package/src/services/build-queue.ts +137 -0
- package/src/services/builder.ts +1800 -0
- package/src/services/catalog.test.ts +1389 -0
- package/src/services/catalog.ts +1187 -0
- package/src/services/cloudflare.ts +259 -0
- package/src/services/config-validator.test.ts +190 -0
- package/src/services/config-validator.ts +108 -0
- package/src/services/crypto.ts +78 -0
- package/src/services/database.ts +122 -0
- package/src/services/db-credential-manager.test.ts +101 -0
- package/src/services/db-credential-manager.ts +447 -0
- package/src/services/db-provisioner.test.ts +602 -0
- package/src/services/db-provisioner.ts +589 -0
- package/src/services/db-whitelist.test.ts +671 -0
- package/src/services/db-whitelist.ts +496 -0
- package/src/services/dependency-resolver.test.ts +677 -0
- package/src/services/dependency-resolver.ts +319 -0
- package/src/services/deploy-gate.test.ts +247 -0
- package/src/services/deploy-gate.ts +75 -0
- package/src/services/dockerfile-generator.test.ts +401 -0
- package/src/services/dockerfile-generator.ts +606 -0
- package/src/services/forgejo.ts +212 -0
- package/src/services/koko.ts +492 -0
- package/src/services/kubernetes.ts +141 -0
- package/src/services/oauth-provisioner.test.ts +477 -0
- package/src/services/oauth-provisioner.ts +286 -0
- package/src/services/pod-diagnostics.ts +261 -0
- package/src/services/rotation-scheduler.ts +293 -0
- package/src/services/storage-credential-manager.ts +223 -0
- package/src/services/storage-provisioner.ts +216 -0
- package/src/services/storage.ts +274 -0
- package/src/services/troubleshooter.ts +208 -0
- package/src/services/vault-client.test.ts +272 -0
- package/src/services/vault-client.ts +587 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/response.ts +23 -0
- package/task_plan.md +171 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import { env } from '../config/env.js'
|
|
3
|
+
import { logger } from '../utils/logger.js'
|
|
4
|
+
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// Types
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export interface VaultDatabaseRoleConfig {
|
|
10
|
+
readonly serviceName: string
|
|
11
|
+
readonly environment: string
|
|
12
|
+
readonly databaseName: string
|
|
13
|
+
readonly role: 'readWrite' | 'read'
|
|
14
|
+
readonly dbType: 'mongodb' | 'redis' | 'neo4j'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface VaultAnnotations {
|
|
18
|
+
readonly [key: string]: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface VaultHealthResponse {
|
|
22
|
+
readonly initialized: boolean
|
|
23
|
+
readonly sealed: boolean
|
|
24
|
+
readonly version: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Health & Feature Flag
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if Vault integration is enabled via feature flag.
|
|
33
|
+
*/
|
|
34
|
+
export function isVaultEnabled(): boolean {
|
|
35
|
+
return env.VAULT_ENABLED === 'true' && env.VAULT_ADDR !== ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if Vault is healthy (initialized, unsealed, reachable).
|
|
40
|
+
* Returns false on any error — builder falls back to Phase 1 static credentials.
|
|
41
|
+
*/
|
|
42
|
+
export async function isVaultHealthy(): Promise<boolean> {
|
|
43
|
+
if (!isVaultEnabled()) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(`${env.VAULT_ADDR}/v1/sys/health`, {
|
|
49
|
+
signal: AbortSignal.timeout(5000),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
// 429 = standby, 472 = DR secondary, 473 = performance standby — all "healthy enough"
|
|
54
|
+
// 501 = not initialized, 503 = sealed — not healthy
|
|
55
|
+
const unhealthy = [501, 503]
|
|
56
|
+
if (unhealthy.includes(response.status)) {
|
|
57
|
+
logger.warn({ status: response.status }, 'Vault is not healthy')
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const data = await response.json() as VaultHealthResponse
|
|
63
|
+
return data.initialized && !data.sealed
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
66
|
+
logger.warn({ error: message }, 'Vault health check failed — falling back to static credentials')
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
// Vault API helpers
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
async function vaultRequest(
|
|
76
|
+
method: string,
|
|
77
|
+
path: string,
|
|
78
|
+
body?: Record<string, unknown>
|
|
79
|
+
): Promise<Response> {
|
|
80
|
+
const url = `${env.VAULT_ADDR}/v1/${path}`
|
|
81
|
+
const headers: Record<string, string> = {
|
|
82
|
+
'X-Vault-Token': env.VAULT_TOKEN,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (body) {
|
|
86
|
+
headers['Content-Type'] = 'application/json'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
method,
|
|
91
|
+
headers,
|
|
92
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
93
|
+
signal: AbortSignal.timeout(10000),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return response
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function vaultWrite(path: string, data: Record<string, unknown>): Promise<void> {
|
|
100
|
+
const response = await vaultRequest('POST', path, data)
|
|
101
|
+
|
|
102
|
+
if (!response.ok && response.status !== 204) {
|
|
103
|
+
const errorBody = await response.text()
|
|
104
|
+
throw new Error(`Vault write to ${path} failed (${response.status}): ${errorBody}`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function vaultDelete(path: string): Promise<void> {
|
|
109
|
+
const response = await vaultRequest('DELETE', path)
|
|
110
|
+
|
|
111
|
+
if (!response.ok && response.status !== 204 && response.status !== 404) {
|
|
112
|
+
const errorBody = await response.text()
|
|
113
|
+
throw new Error(`Vault delete at ${path} failed (${response.status}): ${errorBody}`)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function vaultRead(path: string): Promise<Record<string, unknown> | null> {
|
|
118
|
+
const response = await vaultRequest('GET', path)
|
|
119
|
+
|
|
120
|
+
if (response.status === 404) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const errorBody = await response.text()
|
|
126
|
+
throw new Error(`Vault read from ${path} failed (${response.status}): ${errorBody}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const json = await response.json() as { data: Record<string, unknown> }
|
|
130
|
+
return json.data
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
+
// Vault Role Names
|
|
135
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build the Vault database role name for a service's own database.
|
|
139
|
+
*/
|
|
140
|
+
export function buildVaultRoleName(serviceName: string, environment: string, dbType: string): string {
|
|
141
|
+
return `svc-${serviceName}-${environment}-${dbType}`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build the Vault database role name for a shared/consumed database.
|
|
146
|
+
*/
|
|
147
|
+
export function buildSharedVaultRoleName(
|
|
148
|
+
consumerName: string,
|
|
149
|
+
ownerName: string,
|
|
150
|
+
environment: string,
|
|
151
|
+
dbType: string
|
|
152
|
+
): string {
|
|
153
|
+
return `svc-${consumerName}-on-${ownerName}-${environment}-${dbType}`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build the Vault K8s auth role name for a service.
|
|
158
|
+
*/
|
|
159
|
+
function buildKubeAuthRoleName(serviceName: string, environment: string): string {
|
|
160
|
+
return `svc-${serviceName}-${environment}`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Build the Vault policy name for a service.
|
|
165
|
+
*/
|
|
166
|
+
function buildPolicyName(serviceName: string, environment: string): string {
|
|
167
|
+
return `svc-${serviceName}-${environment}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
// Database Secrets Engine — Role Management
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Ensure a MongoDB database connection is configured in Vault's database engine.
|
|
176
|
+
* Idempotent — updates if exists.
|
|
177
|
+
*/
|
|
178
|
+
export async function ensureMongoDbConnection(connectionName: string, mongoUri: string): Promise<void> {
|
|
179
|
+
logger.info({ connectionName }, 'Configuring Vault MongoDB connection')
|
|
180
|
+
|
|
181
|
+
await vaultWrite(`database/config/${connectionName}`, {
|
|
182
|
+
plugin_name: 'mongodb-database-plugin',
|
|
183
|
+
allowed_roles: 'svc-*',
|
|
184
|
+
connection_url: mongoUri,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Ensure a Vault database role exists for a service's MongoDB database.
|
|
190
|
+
* Idempotent — updates if exists.
|
|
191
|
+
*/
|
|
192
|
+
export async function ensureVaultMongoRole(config: VaultDatabaseRoleConfig): Promise<void> {
|
|
193
|
+
const roleName = buildVaultRoleName(config.serviceName, config.environment, 'mongodb')
|
|
194
|
+
const connectionName = 'mongodb-tawa' // Single MongoDB instance — matches database/config/mongodb-tawa
|
|
195
|
+
|
|
196
|
+
logger.info(
|
|
197
|
+
{ roleName, databaseName: config.databaseName, role: config.role },
|
|
198
|
+
'Ensuring Vault MongoDB role'
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const creationStatements = JSON.stringify({
|
|
202
|
+
db: config.databaseName,
|
|
203
|
+
roles: [{ role: config.role, db: config.databaseName }],
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await vaultWrite(`database/roles/${roleName}`, {
|
|
207
|
+
db_name: connectionName,
|
|
208
|
+
creation_statements: [creationStatements],
|
|
209
|
+
default_ttl: '1h',
|
|
210
|
+
max_ttl: '720h',
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Ensure a Vault database role exists for a service's Redis ACL.
|
|
216
|
+
* Idempotent — updates if exists.
|
|
217
|
+
*/
|
|
218
|
+
export async function ensureVaultRedisRole(config: VaultDatabaseRoleConfig): Promise<void> {
|
|
219
|
+
const roleName = buildVaultRoleName(config.serviceName, config.environment, 'redis')
|
|
220
|
+
const connectionName = 'redis-tawa' // TODO Phase 2c: configure redis connection in Vault
|
|
221
|
+
|
|
222
|
+
logger.info(
|
|
223
|
+
{ roleName, role: config.role },
|
|
224
|
+
'Ensuring Vault Redis role'
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
// Redis ACL creation template — key prefix isolation
|
|
228
|
+
const keyPattern = `~${config.serviceName}-${config.environment}:*`
|
|
229
|
+
const aclRule = config.role === 'readWrite'
|
|
230
|
+
? `${keyPattern} +@all`
|
|
231
|
+
: `${keyPattern} +@read`
|
|
232
|
+
|
|
233
|
+
await vaultWrite(`database/roles/${roleName}`, {
|
|
234
|
+
db_name: connectionName,
|
|
235
|
+
creation_statements: [JSON.stringify([aclRule])],
|
|
236
|
+
default_ttl: '1h',
|
|
237
|
+
max_ttl: '720h',
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Ensure a Vault database role exists for Neo4j.
|
|
243
|
+
* Uses database plugin if available, otherwise KV v2 static rotation.
|
|
244
|
+
*/
|
|
245
|
+
export async function ensureVaultNeo4jRole(config: VaultDatabaseRoleConfig): Promise<void> {
|
|
246
|
+
const roleName = buildVaultRoleName(config.serviceName, config.environment, 'neo4j')
|
|
247
|
+
|
|
248
|
+
logger.info(
|
|
249
|
+
{ roleName, databaseName: config.databaseName, role: config.role },
|
|
250
|
+
'Ensuring Vault Neo4j role'
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
// Try database engine first (community plugin)
|
|
254
|
+
try {
|
|
255
|
+
const connectionName = 'neo4j-tawa' // TODO Phase 2d: configure neo4j connection in Vault
|
|
256
|
+
|
|
257
|
+
await vaultWrite(`database/roles/${roleName}`, {
|
|
258
|
+
db_name: connectionName,
|
|
259
|
+
creation_statements: [JSON.stringify({
|
|
260
|
+
roles: [{ role: config.role === 'readWrite' ? 'admin' : 'reader' }],
|
|
261
|
+
})],
|
|
262
|
+
default_ttl: '1h',
|
|
263
|
+
max_ttl: '720h',
|
|
264
|
+
})
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// Fallback: store in KV v2 for static rotation
|
|
267
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
268
|
+
logger.warn(
|
|
269
|
+
{ roleName, error: message },
|
|
270
|
+
'Neo4j database plugin not available, using KV v2 static rotation'
|
|
271
|
+
)
|
|
272
|
+
// KV v2 path — credentials stored as static secrets, rotated by external script
|
|
273
|
+
// The Vault Agent template reads from this path instead of database/creds/
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
278
|
+
// Kubernetes Auth — Role + Policy Management
|
|
279
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create a Vault policy scoped to a service's database credentials.
|
|
283
|
+
* Least privilege — service can only read its own credentials.
|
|
284
|
+
*/
|
|
285
|
+
export async function ensureVaultPolicy(
|
|
286
|
+
serviceName: string,
|
|
287
|
+
environment: string,
|
|
288
|
+
dbTypes: ReadonlyArray<string>
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
const policyName = buildPolicyName(serviceName, environment)
|
|
291
|
+
|
|
292
|
+
// Build policy rules for each database type
|
|
293
|
+
const rules = dbTypes.map((dbType) => {
|
|
294
|
+
const roleName = buildVaultRoleName(serviceName, environment, dbType)
|
|
295
|
+
return `path "database/creds/${roleName}" {\n capabilities = ["read"]\n}`
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// Add KV v2 access for Neo4j fallback
|
|
299
|
+
if (dbTypes.includes('neo4j')) {
|
|
300
|
+
rules.push(
|
|
301
|
+
`path "secret/data/neo4j/svc-${serviceName}-${environment}" {\n capabilities = ["read"]\n}`
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const policy = rules.join('\n\n')
|
|
306
|
+
|
|
307
|
+
logger.info({ policyName, dbTypes }, 'Ensuring Vault policy')
|
|
308
|
+
|
|
309
|
+
await vaultWrite(`sys/policy/${policyName}`, { policy })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Bind a K8s ServiceAccount to a Vault role via K8s auth method.
|
|
314
|
+
* The service's pod authenticates to Vault using its ServiceAccount token.
|
|
315
|
+
*/
|
|
316
|
+
export async function ensureKubeAuthRole(
|
|
317
|
+
serviceName: string,
|
|
318
|
+
environment: string,
|
|
319
|
+
namespace: string
|
|
320
|
+
): Promise<void> {
|
|
321
|
+
const roleName = buildKubeAuthRoleName(serviceName, environment)
|
|
322
|
+
const policyName = buildPolicyName(serviceName, environment)
|
|
323
|
+
|
|
324
|
+
logger.info(
|
|
325
|
+
{ roleName, namespace, serviceAccount: serviceName },
|
|
326
|
+
'Ensuring Vault K8s auth role'
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
await vaultWrite(`auth/kubernetes/role/${roleName}`, {
|
|
330
|
+
bound_service_account_names: [serviceName],
|
|
331
|
+
bound_service_account_namespaces: [namespace],
|
|
332
|
+
policies: [policyName],
|
|
333
|
+
ttl: '1h',
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Ensure a K8s ServiceAccount exists in the service namespace.
|
|
339
|
+
* Uses kubectl — idempotent via dry-run + apply.
|
|
340
|
+
*/
|
|
341
|
+
export function ensureServiceAccount(serviceName: string, namespace: string): void {
|
|
342
|
+
const command = [
|
|
343
|
+
`kubectl create serviceaccount ${serviceName}`,
|
|
344
|
+
`--namespace ${namespace}`,
|
|
345
|
+
'--dry-run=client -o yaml | kubectl apply -f -',
|
|
346
|
+
].join(' ')
|
|
347
|
+
|
|
348
|
+
execSync(command, {
|
|
349
|
+
encoding: 'utf8',
|
|
350
|
+
shell: '/bin/bash',
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
logger.info({ serviceName, namespace }, 'K8s ServiceAccount ensured')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
|
+
// MinIO — Vault Secrets Engine (custom plugin: vault-plugin-secrets-minio)
|
|
358
|
+
//
|
|
359
|
+
// Dynamic credentials via our custom Vault secrets engine.
|
|
360
|
+
// Plugin creates ephemeral MinIO users + scoped IAM policies on demand.
|
|
361
|
+
// Vault manages the full lifecycle: create on read, revoke on lease expiry.
|
|
362
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
export interface MinioAdminCreds {
|
|
365
|
+
readonly accessKey: string
|
|
366
|
+
readonly secretKey: string
|
|
367
|
+
readonly endpoint: string
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Build the Vault MinIO role name for a service's storage.
|
|
372
|
+
*/
|
|
373
|
+
export function buildMinioRoleName(serviceName: string, environment: string): string {
|
|
374
|
+
return `svc-${serviceName}-${environment}-storage`
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Build the Vault secrets engine creds path for a service.
|
|
379
|
+
* Pods read from this path via Vault Agent sidecar — Vault generates
|
|
380
|
+
* ephemeral MinIO credentials on each read.
|
|
381
|
+
*/
|
|
382
|
+
export function buildMinioCredsPath(serviceName: string, environment: string): string {
|
|
383
|
+
return `minio/creds/${buildMinioRoleName(serviceName, environment)}`
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Read MinIO admin credentials from Vault KV (secret/minio/admin).
|
|
388
|
+
* These are used by the builder to create buckets via MinIO SDK — never stored on disk.
|
|
389
|
+
* The custom plugin handles user/policy creation, but bucket creation still needs admin creds.
|
|
390
|
+
*/
|
|
391
|
+
export async function getMinioAdminCreds(): Promise<MinioAdminCreds> {
|
|
392
|
+
const data = await vaultRead('secret/data/minio/admin')
|
|
393
|
+
|
|
394
|
+
if (!data || !data.data) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
'MinIO admin credentials not found in Vault KV at secret/minio/admin. ' +
|
|
397
|
+
'Run: vault kv put secret/minio/admin access_key=... secret_key=... endpoint=...'
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const inner = data.data as Record<string, string>
|
|
402
|
+
|
|
403
|
+
if (!inner.access_key || !inner.secret_key) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
'MinIO admin credentials in Vault KV are missing access_key or secret_key'
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
accessKey: inner.access_key,
|
|
411
|
+
secretKey: inner.secret_key,
|
|
412
|
+
endpoint: inner.endpoint || `http://127.0.0.1:9000`,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Configure a Vault MinIO role for a service.
|
|
418
|
+
* The role defines the inline IAM policy (scoped to declared buckets).
|
|
419
|
+
* When pods read from `minio/creds/{role}`, Vault creates an ephemeral
|
|
420
|
+
* MinIO user with this policy attached.
|
|
421
|
+
*
|
|
422
|
+
* Idempotent — updates if role exists.
|
|
423
|
+
*/
|
|
424
|
+
export async function configureMinioVaultRole(
|
|
425
|
+
serviceName: string,
|
|
426
|
+
environment: string,
|
|
427
|
+
policyJson: string,
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
const roleName = buildMinioRoleName(serviceName, environment)
|
|
430
|
+
|
|
431
|
+
logger.info({ roleName, serviceName, environment }, 'Configuring Vault MinIO role')
|
|
432
|
+
|
|
433
|
+
await vaultWrite(`minio/roles/${roleName}`, {
|
|
434
|
+
policy: policyJson,
|
|
435
|
+
user_name_prefix: `svc-${serviceName}-${environment}-`,
|
|
436
|
+
default_ttl: '1h',
|
|
437
|
+
max_ttl: '720h',
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Revoke all active MinIO leases for a service (credential rotation).
|
|
443
|
+
* Forces Vault to delete the ephemeral MinIO users + canned policies.
|
|
444
|
+
* New credentials are generated on next pod startup.
|
|
445
|
+
*/
|
|
446
|
+
export async function revokeMinioLeases(
|
|
447
|
+
serviceName: string,
|
|
448
|
+
environment: string,
|
|
449
|
+
): Promise<void> {
|
|
450
|
+
const prefix = `minio/creds/${buildMinioRoleName(serviceName, environment)}`
|
|
451
|
+
|
|
452
|
+
logger.info({ serviceName, environment, prefix }, 'Revoking MinIO leases')
|
|
453
|
+
|
|
454
|
+
await vaultWrite(`sys/leases/revoke-prefix/${prefix}`, {})
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Add MinIO creds path to a service's Vault policy.
|
|
459
|
+
* Call this after ensureVaultPolicy to add storage access rules.
|
|
460
|
+
*/
|
|
461
|
+
export async function addMinioPolicyRules(
|
|
462
|
+
serviceName: string,
|
|
463
|
+
environment: string,
|
|
464
|
+
): Promise<void> {
|
|
465
|
+
const policyName = buildPolicyName(serviceName, environment)
|
|
466
|
+
const credsPath = buildMinioCredsPath(serviceName, environment)
|
|
467
|
+
|
|
468
|
+
// Read existing policy
|
|
469
|
+
const existing = await vaultRead(`sys/policy/${policyName}`)
|
|
470
|
+
const existingPolicy = existing?.rules as string || ''
|
|
471
|
+
|
|
472
|
+
// Only add if not already present
|
|
473
|
+
const minioRule = `path "${credsPath}" {\n capabilities = ["read"]\n}`
|
|
474
|
+
if (existingPolicy.includes(credsPath)) {
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const updatedPolicy = existingPolicy
|
|
479
|
+
? `${existingPolicy}\n\n${minioRule}`
|
|
480
|
+
: minioRule
|
|
481
|
+
|
|
482
|
+
await vaultWrite(`sys/policy/${policyName}`, { policy: updatedPolicy })
|
|
483
|
+
|
|
484
|
+
logger.info({ policyName, credsPath }, 'Added MinIO creds rules to Vault policy')
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
488
|
+
// Vault Agent Annotations
|
|
489
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
490
|
+
|
|
491
|
+
interface DatabaseAnnotationConfig {
|
|
492
|
+
readonly dbType: 'mongodb' | 'redis' | 'neo4j'
|
|
493
|
+
readonly roleName: string
|
|
494
|
+
readonly templateContent: string
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Build the Vault Agent template that renders a MongoDB connection string.
|
|
499
|
+
*/
|
|
500
|
+
function buildMongoTemplate(roleName: string, databaseName: string): string {
|
|
501
|
+
const host = env.DB_MONGODB_PUBLIC_HOST || env.DB_MONGODB_HOST
|
|
502
|
+
const port = env.DB_MONGODB_PORT
|
|
503
|
+
return [
|
|
504
|
+
`{{- with secret "database/creds/${roleName}" -}}`,
|
|
505
|
+
`MONGODB_URI=mongodb://{{ .Data.username }}:{{ .Data.password }}@${host}:${port}/${databaseName}?authSource=${databaseName}`,
|
|
506
|
+
'{{- end -}}',
|
|
507
|
+
].join('\n')
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build the Vault Agent template that renders a Redis URL.
|
|
512
|
+
*/
|
|
513
|
+
function buildRedisTemplate(roleName: string): string {
|
|
514
|
+
const host = env.DB_REDIS_HOST
|
|
515
|
+
const port = env.DB_REDIS_PORT
|
|
516
|
+
return [
|
|
517
|
+
`{{- with secret "database/creds/${roleName}" -}}`,
|
|
518
|
+
`REDIS_URL=redis://{{ .Data.username }}:{{ .Data.password }}@${host}:${port}/0`,
|
|
519
|
+
'{{- end -}}',
|
|
520
|
+
].join('\n')
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Build the Vault Agent template that renders Neo4j credentials.
|
|
525
|
+
*/
|
|
526
|
+
function buildNeo4jTemplate(roleName: string): string {
|
|
527
|
+
const host = env.DB_NEO4J_HOST
|
|
528
|
+
const port = env.DB_NEO4J_PORT
|
|
529
|
+
return [
|
|
530
|
+
`{{- with secret "database/creds/${roleName}" -}}`,
|
|
531
|
+
`NEO4J_URI=bolt://${host}:${port}`,
|
|
532
|
+
'NEO4J_USERNAME={{ .Data.username }}',
|
|
533
|
+
'NEO4J_PASSWORD={{ .Data.password }}',
|
|
534
|
+
'{{- end -}}',
|
|
535
|
+
].join('\n')
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Generate Vault Agent pod annotations for a service.
|
|
540
|
+
*
|
|
541
|
+
* These annotations are injected via `--set-json podAnnotations='{...}'` in the Helm command.
|
|
542
|
+
* The Vault Agent Injector webhook reads them and injects init + sidecar containers.
|
|
543
|
+
*/
|
|
544
|
+
export function getVaultAgentAnnotations(
|
|
545
|
+
serviceName: string,
|
|
546
|
+
environment: string,
|
|
547
|
+
databases: ReadonlyArray<{ type: 'mongodb' | 'redis' | 'neo4j'; name?: string }>
|
|
548
|
+
): VaultAnnotations {
|
|
549
|
+
if (databases.length === 0) {
|
|
550
|
+
return {}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const kubeRoleName = buildKubeAuthRoleName(serviceName, environment)
|
|
554
|
+
|
|
555
|
+
const annotations: Record<string, string> = {
|
|
556
|
+
'vault.hashicorp.com/agent-inject': 'true',
|
|
557
|
+
'vault.hashicorp.com/role': kubeRoleName,
|
|
558
|
+
'vault.hashicorp.com/agent-pre-populate-only': 'false',
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Build per-database annotations
|
|
562
|
+
const dbConfigs: DatabaseAnnotationConfig[] = databases.map((db) => {
|
|
563
|
+
const databaseName = db.name || `${serviceName}-${environment}`
|
|
564
|
+
const roleName = buildVaultRoleName(serviceName, environment, db.type)
|
|
565
|
+
|
|
566
|
+
switch (db.type) {
|
|
567
|
+
case 'mongodb':
|
|
568
|
+
return { dbType: db.type, roleName, templateContent: buildMongoTemplate(roleName, databaseName) }
|
|
569
|
+
case 'redis':
|
|
570
|
+
return { dbType: db.type, roleName, templateContent: buildRedisTemplate(roleName) }
|
|
571
|
+
case 'neo4j':
|
|
572
|
+
return { dbType: db.type, roleName, templateContent: buildNeo4jTemplate(roleName) }
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
// Each database type gets its own secret file, all rendered to /vault/secrets/
|
|
577
|
+
// The command annotation triggers when credentials rotate at max_ttl —
|
|
578
|
+
// it removes the liveness signal file so K8s restarts the container with fresh creds.
|
|
579
|
+
for (const config of dbConfigs) {
|
|
580
|
+
const suffix = config.dbType
|
|
581
|
+
annotations[`vault.hashicorp.com/agent-inject-secret-${suffix}`] = `database/creds/${config.roleName}`
|
|
582
|
+
annotations[`vault.hashicorp.com/agent-inject-template-${suffix}`] = config.templateContent
|
|
583
|
+
annotations[`vault.hashicorp.com/agent-inject-command-${suffix}`] = 'rm -f /vault/signals/alive'
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return annotations
|
|
587
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ApiResponse<T> {
|
|
2
|
+
success: boolean
|
|
3
|
+
data?: T
|
|
4
|
+
error?: {
|
|
5
|
+
code: string
|
|
6
|
+
message: string
|
|
7
|
+
details?: unknown
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function apiResponse<T>(data: T): ApiResponse<T> {
|
|
12
|
+
return { success: true, data }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Alias for apiResponse
|
|
16
|
+
export const apiSuccess = apiResponse
|
|
17
|
+
|
|
18
|
+
export function apiError(code: string, message: string, details?: unknown): ApiResponse<never> {
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
error: { code, message, details }
|
|
22
|
+
}
|
|
23
|
+
}
|