fix(staging): rotate SSH secret handling and env ownership
This commit is contained in:
@@ -192,6 +192,37 @@ test("database env validator rejects symlinks and unsafe modes", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("database env validator enforces an explicit staging owner uid without printing values", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "jyotisha-database-env-owner-"));
|
||||
const envFile = join(root, ".env.staging.database");
|
||||
|
||||
try {
|
||||
writeFileSync(envFile, `${validEnvironment.join("\n")}\n`, { mode: 0o600 });
|
||||
chmodSync(envFile, 0o600);
|
||||
const currentUid = process.getuid?.();
|
||||
assert.equal(typeof currentUid, "number");
|
||||
|
||||
const accepted = spawnSync("bash", [validator, envFile], {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, EXPECTED_STAGING_ENV_OWNER_UID: String(currentUid) },
|
||||
});
|
||||
assert.equal(accepted.status, 0, accepted.stderr);
|
||||
|
||||
const rejected = spawnSync("bash", [validator, envFile], {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, EXPECTED_STAGING_ENV_OWNER_UID: String((currentUid ?? 0) + 1) },
|
||||
});
|
||||
assert.notEqual(rejected.status, 0);
|
||||
assert.match(rejected.stderr, /invalid owner/);
|
||||
assert.doesNotMatch(
|
||||
`${rejected.stdout}${rejected.stderr}`,
|
||||
/postgres-test-password|schema-owner-test-password|staging-backup-test-password/,
|
||||
);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("database env validator accepts a private valid file without printing values", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "jyotisha-database-env-"));
|
||||
const envFile = join(root, ".env.staging.database");
|
||||
|
||||
@@ -46,6 +46,10 @@ const rolloutWorkflow = new URL(
|
||||
"../../.github/workflows/configure-staging-rectification-rollout.yml",
|
||||
import.meta.url,
|
||||
);
|
||||
const resetStagingAccountWorkflow = new URL(
|
||||
"../../.github/workflows/reset-staging-account.yml",
|
||||
import.meta.url,
|
||||
);
|
||||
const deployScript = new URL(
|
||||
"../../deploy/run-staging-deploy.sh",
|
||||
import.meta.url,
|
||||
@@ -259,6 +263,40 @@ test("Gitea staging mutation workflows use the available runner and pinned Node
|
||||
}
|
||||
});
|
||||
|
||||
test("staging SSH secrets are single-line base64 and never injected as multiline private keys", () => {
|
||||
for (const workflow of [
|
||||
read(giteaDeployWorkflow),
|
||||
read(giteaMigrationWorkflow),
|
||||
read(deployWorkflow),
|
||||
read(migrationWorkflow),
|
||||
read(rolloutWorkflow),
|
||||
read(resetStagingAccountWorkflow),
|
||||
]) {
|
||||
assert.match(workflow, /SSH_PRIVATE_KEY_BASE64: \$\{\{ secrets\.STAGING_SSH_PRIVATE_KEY \}\}/);
|
||||
assert.match(workflow, /printf '%s' "\$SSH_PRIVATE_KEY_BASE64" \| base64 --decode/);
|
||||
assert.match(workflow, /chmod 600 [^\n]*(?:\$key_path|jyotisha-staging)/);
|
||||
assert.match(workflow, /ssh-keygen -y -f [^\n]+>\/dev\/null/);
|
||||
assert.doesNotMatch(workflow, /\n\s+SSH_PRIVATE_KEY: \$\{\{ secrets\.STAGING_SSH_PRIVATE_KEY \}\}/);
|
||||
assert.doesNotMatch(workflow, /printf '%s\\n' "\$SSH_PRIVATE_KEY"/);
|
||||
}
|
||||
});
|
||||
|
||||
test("staging scripts validate deploy-owned env files and rollout preserves their owner", () => {
|
||||
const deployRunner = read(deployScript);
|
||||
const migrationRunner = read(migrationScript);
|
||||
const rolloutRunner = read(rolloutScript);
|
||||
|
||||
for (const runner of [deployRunner, migrationRunner]) {
|
||||
assert.match(runner, /EXPECTED_STAGING_ENV_OWNER_UID=.*stat[^\n]+"\$DEPLOY_PATH"/);
|
||||
assert.match(runner, /export EXPECTED_STAGING_ENV_OWNER_UID/);
|
||||
assert.ok(runner.indexOf("EXPECTED_STAGING_ENV_OWNER_UID=") < runner.indexOf("validate-staging-database-env.sh"));
|
||||
}
|
||||
assert.match(rolloutRunner, /EXPECTED_STAGING_ENV_OWNER_UID=.*stat[^\n]+"\$DEPLOY_PATH"/);
|
||||
assert.match(rolloutRunner, /EXPECTED_STAGING_ENV_OWNER_GID=.*stat[^\n]+"\$DEPLOY_PATH"/);
|
||||
assert.match(rolloutRunner, /chown "\$EXPECTED_STAGING_ENV_OWNER_UID:\$EXPECTED_STAGING_ENV_OWNER_GID" "\$temporary"/);
|
||||
assert.ok(rolloutRunner.indexOf("chown \"$EXPECTED_STAGING_ENV_OWNER_UID") < rolloutRunner.indexOf("mv -f -- \"$temporary\" \"$env_file\""));
|
||||
});
|
||||
|
||||
test("quality gate builds the Python package with its declared backend dependencies", () => {
|
||||
const workflow = read(qualityWorkflow);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user