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:
parent
fbd1ad4d1d
commit
522c5d7a54
3 changed files with 16 additions and 4 deletions
|
|
@ -37,14 +37,18 @@ export interface RustfsCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `foundation/forgejo/service-credentials` — the admin slice (CONTRACT_002 §2.3).
|
* `foundation/forgejo/service-credentials` — admin slice + SECRET_KEY (CONTRACT_002
|
||||||
* The crypto secrets (forgejoSecretKey/InternalToken/Jwt*) are auto-generated by
|
* §2.3). The OTHER crypto secrets (forgejoInternalToken/Jwt*) are format-constrained
|
||||||
* Forgejo into its app.ini (format-constrained — JWTs, not free random), so they
|
* (JWTs, not free random) so Forgejo auto-generates them into app.ini and the GATE-B
|
||||||
* are not generated here; capturing them into Vault is a later refinement.
|
* 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 {
|
export interface ForgejoCredentials {
|
||||||
adminUser: string; // cfg.forgejo.adminUser (deterministic)
|
adminUser: string; // cfg.forgejo.adminUser (deterministic)
|
||||||
adminPassword: pulumi.Output<string>;
|
adminPassword: pulumi.Output<string>;
|
||||||
|
secretKey: pulumi.Output<string>; // [security] SECRET_KEY (injected via env)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Everything generateCredentials() produces; grows as Wave-2 tasks land. */
|
/** Everything generateCredentials() produces; grows as Wave-2 tasks land. */
|
||||||
|
|
@ -80,6 +84,7 @@ export function generateCredentials(ctx: DeployCtx): FoundationCredentials {
|
||||||
forgejo: {
|
forgejo: {
|
||||||
adminUser: ctx.cfg.forgejo.adminUser, // "platform-admin"
|
adminUser: ctx.cfg.forgejo.adminUser, // "platform-admin"
|
||||||
adminPassword: secret("forgejo-admin-password"),
|
adminPassword: secret("forgejo-admin-password"),
|
||||||
|
secretKey: secret("forgejo-secret-key", 40), // [security] SECRET_KEY
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export interface ForgejoDeps {
|
||||||
rustfs: RustfsOutputs;
|
rustfs: RustfsOutputs;
|
||||||
pgCreds: PostgresCredentials;
|
pgCreds: PostgresCredentials;
|
||||||
rustfsCreds: RustfsCredentials;
|
rustfsCreds: RustfsCredentials;
|
||||||
|
forgejoCreds: ForgejoCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForgejoOutputs {
|
export interface ForgejoOutputs {
|
||||||
|
|
@ -88,6 +89,11 @@ export function deployForgejo(
|
||||||
// Go SSH server colliding on :22. SSH_PORT is the clone-URL port; the sshd is
|
// Go SSH server colliding on :22. SSH_PORT is the clone-URL port; the sshd is
|
||||||
// published on host :22 (scp-form goal) + :2222 (CONTRACT_003).
|
// published on host :22 (scp-form goal) + :2222 (CONTRACT_003).
|
||||||
"FORGEJO__server__START_SSH_SERVER=false",
|
"FORGEJO__server__START_SSH_SERVER=false",
|
||||||
|
// [security] SECRET_KEY — Forgejo leaves this EMPTY when the installer is
|
||||||
|
// skipped (INSTALL_LOCK); set it explicitly so at-rest encryption of 2FA /
|
||||||
|
// mirror / oauth secrets is keyed. env-to-ini overwrites it in the volume's
|
||||||
|
// app.ini (INTERNAL_TOKEN + JWT secrets are left untouched — Forgejo's own).
|
||||||
|
pulumi.interpolate`FORGEJO__security__SECRET_KEY=${deps.forgejoCreds.secretKey}`,
|
||||||
"FORGEJO__server__SSH_LISTEN_PORT=22",
|
"FORGEJO__server__SSH_LISTEN_PORT=22",
|
||||||
`FORGEJO__server__SSH_PORT=${cfg.forgeSshPort}`,
|
`FORGEJO__server__SSH_PORT=${cfg.forgeSshPort}`,
|
||||||
`FORGEJO__server__SSH_DOMAIN=${cfg.hosts.git}`,
|
`FORGEJO__server__SSH_DOMAIN=${cfg.hosts.git}`,
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ const forgejo = deployForgejo(ctx, {
|
||||||
rustfs,
|
rustfs,
|
||||||
pgCreds: credentials.postgres,
|
pgCreds: credentials.postgres,
|
||||||
rustfsCreds: credentials.rustfs,
|
rustfsCreds: credentials.rustfs,
|
||||||
|
forgejoCreds: credentials.forgejo,
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- GATE B: Forgejo healthy → headless admin + org + repo + operator SSH key
|
// --- GATE B: Forgejo healthy → headless admin + org + repo + operator SSH key
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue