@caelo-cms/provisioning 0.2.79 → 0.2.81

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/provisioning",
3
- "version": "0.2.79",
3
+ "version": "0.2.81",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
@@ -38,7 +38,7 @@
38
38
  "prepublishOnly": "tsc -b --force"
39
39
  },
40
40
  "dependencies": {
41
- "@caelo-cms/shared": "^0.2.79",
41
+ "@caelo-cms/shared": "^0.2.81",
42
42
  "@clack/prompts": "1.3.0",
43
43
  "@pulumi/aws": "^7",
44
44
  "@pulumi/awsx": "^3",
@@ -46,6 +46,7 @@
46
46
  "@pulumi/command": "^1",
47
47
  "@pulumi/gcp": "^9",
48
48
  "@pulumi/pulumi": "^3",
49
+ "@pulumi/random": "^4.19.2",
49
50
  "kleur": "4.1.5"
50
51
  },
51
52
  "publishConfig": {
@@ -2,7 +2,7 @@ encryptionsalt: v1:mEv+SGjVDdU=:v1:1Tmd1g2Yh0KXxhp5:fGsA3LiAPntjAGUMORKO+B1gxQgI
2
2
  config:
3
3
  caelo-gcp:adminMinInstances: "0"
4
4
  caelo-gcp:anthropicApiKey:
5
- secure: v1:ph6Zq48OzILq6xjh:jsEB22xYWF6SNL7e1GADWVljfl6Kd2Iqv4TYdoMABkYgOovvp5lPnFtTTZ61tEjDplnEcxjx5kWXHsYixMHpJzcIIxscT1zcneY44zJDUbvN7fSdsLehbJHMVsHj/o1oLiNqjgn5BXk/jI1mIIOIZjEQNsq9THYsWIDdAA==
5
+ secure: v1:/RCE2zS+Y8ZrTdcj:gIXucRKkT1vX+qg0yE8Uq3oIL+lHa23wLOheAKeR762d97ydAo332XwfteqJOgDg/scAtD2dict1NoNxNgxXpJnw1jltUi8fsF9vMt9elh2aV71eFB2oRGBlxIWbAgYdXqs/gfMxRtZ8r0dhYADuSCrtTQcI7GAFWg5EhA==
6
6
  caelo-gcp:cloudSqlHa: "false"
7
7
  caelo-gcp:cloudSqlTier: db-f1-micro
8
8
  caelo-gcp:domain: caelo-cms.com
@@ -12,5 +12,5 @@ config:
12
12
  caelo-gcp:project: caelo-website
13
13
  caelo-gcp:region: europe-west1
14
14
  caelo-gcp:wafAdaptiveProtection: "false"
15
- caelo-gcp:image-digest-admin: sha256:4ba672e8445900d8d2525f8a36a3446a6d0282b32690c989fb7a09ffbc2e3b6e
16
- caelo-gcp:image-digest-gateway: sha256:3cad91141f4224af50f251c176aeca13cce32708f7a3fdc82336fd1a3587615b
15
+ caelo-gcp:image-digest-admin: sha256:92fb8ee1267de781f37c664e34d299b2f29c6051a113fa1bdbd6fddb753ef81e
16
+ caelo-gcp:image-digest-gateway: sha256:b4ceac2fd371be2850f7fc9549700fcd9e595eae6144f4a87b27192adf0cd9eb
@@ -19,6 +19,7 @@
19
19
 
20
20
  import * as gcp from "@pulumi/gcp";
21
21
  import * as pulumi from "@pulumi/pulumi";
22
+ import * as random from "@pulumi/random";
22
23
  import type { CloudAdapterOutputs, DnsRecord } from "../../dist/adapter.js";
23
24
  import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
24
25
 
@@ -128,22 +129,37 @@ const sqlPeering = new gcp.servicenetworking.Connection(
128
129
  // Secret Manager — never env-var literal; runtime mounts via secret_environment_variables
129
130
  // =========================================================================
130
131
 
131
- function randomHex(bytes: number): string {
132
- const buf = new Uint8Array(bytes);
133
- crypto.getRandomValues(buf);
134
- return [...buf].map((b) => b.toString(16).padStart(2, "0")).join("");
135
- }
136
-
137
- const postgresPassword = pulumi.secret(randomHex(32));
138
- const csrfSecret = pulumi.secret(randomHex(32));
139
- const cookieSecret = pulumi.secret(randomHex(32));
140
- // P18 — project KEK (32 bytes hex) encrypts AI provider API keys + future
141
- // at-rest secrets in cms_admin. Auto-generated per stack so the operator
142
- // never types it; Cloud Run binds it as CAELO_SECRET_KEK env via
143
- // `valueSource.secretKeyRef` below. Rotating it requires a re-encrypt
144
- // CLI (deferred — the api_key_kek_fp column lets a future tool find rows
145
- // under the old KEK and refuse to silently return garbage).
146
- const caeloSecretKek = pulumi.secret(randomHex(32));
132
+ // v0.2.81 — secrets MUST be generated by a Pulumi resource that
133
+ // persists in stack state, NOT by `randomHex(32)` at program-eval
134
+ // time. Pulumi re-evaluates the program on every `pulumi up`, so
135
+ // any plain-JS random() call produces a new value each run, and
136
+ // the matching SecretVersion gets re-created with the new value.
137
+ // Symptom: after a re-up, all secrets encrypted under the old KEK
138
+ // (AI provider API keys in ai_providers) became permanently
139
+ // unreadable — and Secret Manager destroyed the prior versions
140
+ // after their soft-delete window so recovery was impossible.
141
+ //
142
+ // `random.RandomBytes` materialises ONCE, persists the bytes in
143
+ // state, reuses on subsequent runs. This is the canonical fix for
144
+ // "auto-generated but stable" secret values in Pulumi.
145
+ // random.RandomBytes uses the @pulumi/random provider, not the gcp
146
+ // one — don't pass `opts` (which carries gcpProvider). Pulumi
147
+ // auto-loads the random provider on first reference.
148
+ const pgPasswordBytes = new random.RandomBytes(`${namePrefix}-pg-password-bytes`, { length: 32 });
149
+ const csrfSecretBytes = new random.RandomBytes(`${namePrefix}-csrf-secret-bytes`, { length: 32 });
150
+ const cookieSecretBytes = new random.RandomBytes(`${namePrefix}-cookie-secret-bytes`, {
151
+ length: 32,
152
+ });
153
+ const kekBytes = new random.RandomBytes(`${namePrefix}-secret-kek-bytes`, { length: 32 });
154
+
155
+ const postgresPassword = pulumi.secret(pgPasswordBytes.hex);
156
+ const csrfSecret = pulumi.secret(csrfSecretBytes.hex);
157
+ const cookieSecret = pulumi.secret(cookieSecretBytes.hex);
158
+ // P18 — project KEK (32 bytes hex) encrypts AI provider API keys
159
+ // + future at-rest secrets in cms_admin. Stable across `pulumi up`
160
+ // (see comment above). Cloud Run binds it as CAELO_SECRET_KEK env
161
+ // via `valueSource.secretKeyRef` below.
162
+ const caeloSecretKek = pulumi.secret(kekBytes.hex);
147
163
  // anthropicApiKey is now OPTIONAL — operators configure the key via
148
164
  // /security/ai (encrypted into ai_providers) after first login. Setting
149
165
  // it via Pulumi config is supported as a backwards-compat path for