Make link-key secrets 40 characters and keep every mc argument's secret out of its errors (issue 1766)

This commit is contained in:
2026-10-21 20:21:56 +02:00
parent c3564c1acc
commit c83f48b821
3 changed files with 98 additions and 6 deletions
+55
View File
@@ -115,3 +115,58 @@ test("the link key id is the one the object-store tool derives (cmd/object-store
// 1516/AC1
assert.equal(linkKeyId("brother"), "link0b4bd77cec705cf5");
});
// Issue 1766: a link key's secret is minio's length, and no mc failure carries an argument's secret.
import { chmodSync, writeFileSync, mkdirSync } from "node:fs";
import { MinioClient } from "../client.ts";
test("a link key's secret is 40 characters, and a stored 64-character secret is replaced", async () => {
// 1766/AC1
const { minio, dir } = setup();
await ensureLinkKey(minio, dir, "fresh");
assert.equal(minio.keys.get(linkKeyId("fresh"))!.secret.length, 40);
assert.match(minio.keys.get(linkKeyId("fresh"))!.secret, /^[0-9a-f]{40}$/);
mkdirSync(dir, { recursive: true });
const old = "ab".repeat(32);
writeFileSync(linkSecretPath(dir, "old"), old + "\n");
await ensureLinkKey(minio, dir, "old");
const made = minio.keys.get(linkKeyId("old"))!.secret;
assert.equal(made.length, 40);
assert.notEqual(made, old);
assert.equal(readFileSync(linkSecretPath(dir, "old"), "utf8").trim(), made);
});
function failingMc(): { client: MinioClient; root: string } {
const d = mkdtempSync(join(process.env.TMPDIR ?? tmpdir(), "minio-mc-"));
const bin = join(d, "mc");
// Says its arguments and its alias on stderr, then fails, the way a refused command does.
writeFileSync(bin, '#!/bin/sh\necho "args: $*" >&2\necho "env: MC_HOST_mesh=$MC_HOST_mesh" >&2\nexit 1\n');
chmodSync(bin, 0o755);
const root = "RootPass-s3cret/ok";
const client = new MinioClient({ endpoint: "http://127.0.0.1:9000", rootUser: "root", rootPassword: root, region: "us-east-1", mcBin: bin, mcConfigDir: d });
return { client, root };
}
test("an mc failure's message holds no --secret-key value and no MC_HOST value", async () => {
// 1766/AC2
const { client, root } = failingMc();
const secret = "SuperSecretValue0123456789";
const err: any = await client.createAccessKey("b", "ACCESSKEYID1234567", secret).then(() => null, (e) => e);
assert.ok(err, "the failure is raised");
for (const text of [String(err.message), String(err.stack ?? ""), JSON.stringify(Object.getOwnPropertyNames(err).map((k) => err[k]))]) {
assert.ok(!text.includes(secret), "no secret key");
assert.ok(!text.includes("ACCESSKEYID1234567"), "no access key");
assert.ok(!text.includes(root) && !text.includes(encodeURIComponent(root)), "no root password");
assert.ok(!text.includes("root:"), "no MC_HOST value");
}
assert.match(err.message, /\[redacted\]/);
});
test("a consumer key's create passes the minted secret unchanged", async () => {
// 1766/AC3
const s = setup();
await s.h.create(provision);
assert.equal(s.minio.keys.get("brother-key")!.secret, "consumer-secret");
});