fix(forgejo): generate + set SECRET_KEY (was empty under INSTALL_LOCK)

Follow-up to the crypto-secret mirror: Forgejo's [security] SECRET_KEY was
EMPTY because the bootstrap skips the web installer (INSTALL_LOCK), which is
what normally generates it. An empty SECRET_KEY weakens at-rest encryption of
2FA secrets, push-mirror/migration passwords, and OAuth app secrets.

Generate it with @pulumi/random (it is a plain high-entropy string, not a
format-constrained JWT — so unlike INTERNAL_TOKEN/JWT_SECRET it CAN be
random-generated, matching CONTRACT_002 §2.3) and inject via
FORGEJO__security__SECRET_KEY; env-to-ini overwrites it in the volume's
app.ini while leaving Forgejo's own INTERNAL_TOKEN + JWT secrets untouched.
The GATE-B mirror then captures the real value into Vault.

Done now while the egg is fresh (no encrypted data yet) → no re-encryption.

Validated live: app.ini + Vault forgejoSecretKey = 40 chars; forge healthz
pass + https 200; scp-form clone works; idempotent at 44 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 23:30:35 +02:00
parent fbd1ad4d1d
commit 522c5d7a54
3 changed files with 16 additions and 4 deletions

View file

@ -37,14 +37,18 @@ export interface RustfsCredentials {
}
/**
* `foundation/forgejo/service-credentials` the admin slice (CONTRACT_002 §2.3).
* The crypto secrets (forgejoSecretKey/InternalToken/Jwt*) are auto-generated by
* Forgejo into its app.ini (format-constrained JWTs, not free random), so they
* are not generated here; capturing them into Vault is a later refinement.
* `foundation/forgejo/service-credentials` admin slice + SECRET_KEY (CONTRACT_002
* §2.3). The OTHER crypto secrets (forgejoInternalToken/Jwt*) are format-constrained
* (JWTs, not free random) so Forgejo auto-generates them into app.ini and the GATE-B
* mirror captures them. SECRET_KEY, by contrast, is a plain high-entropy string, so
* we generate it here (@pulumi/random, per CONTRACT_002) and inject it via env
* Forgejo leaves it EMPTY when the web installer is skipped (INSTALL_LOCK), which
* would weaken at-rest encryption of 2FA/mirror/oauth secrets.
*/
export interface ForgejoCredentials {
adminUser: string; // cfg.forgejo.adminUser (deterministic)
adminPassword: pulumi.Output<string>;
secretKey: pulumi.Output<string>; // [security] SECRET_KEY (injected via env)
}
/** Everything generateCredentials() produces; grows as Wave-2 tasks land. */
@ -80,6 +84,7 @@ export function generateCredentials(ctx: DeployCtx): FoundationCredentials {
forgejo: {
adminUser: ctx.cfg.forgejo.adminUser, // "platform-admin"
adminPassword: secret("forgejo-admin-password"),
secretKey: secret("forgejo-secret-key", 40), // [security] SECRET_KEY
},
};
}