mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 10:50:38 +09:00
Fix legacy local secret key path fallback
This commit is contained in:
parent
ce18c1a9e2
commit
fba999e8e9
2 changed files with 87 additions and 2 deletions
67
server/src/__tests__/local-encrypted-provider.test.ts
Normal file
67
server/src/__tests__/local-encrypted-provider.test.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { randomBytes } from "node:crypto";
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { localEncryptedProvider } from "../secrets/local-encrypted-provider.js";
|
||||
|
||||
describe("localEncryptedProvider legacy key-path fallback", () => {
|
||||
const previousHome = process.env.HOME;
|
||||
const previousPaperclipHome = process.env.PAPERCLIP_HOME;
|
||||
const previousKeyFile = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||
const previousMasterKey = process.env.PAPERCLIP_SECRETS_MASTER_KEY;
|
||||
const tmpDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
if (previousHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = previousHome;
|
||||
if (previousPaperclipHome === undefined) delete process.env.PAPERCLIP_HOME;
|
||||
else process.env.PAPERCLIP_HOME = previousPaperclipHome;
|
||||
if (previousKeyFile === undefined) delete process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||
else process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE = previousKeyFile;
|
||||
if (previousMasterKey === undefined) delete process.env.PAPERCLIP_SECRETS_MASTER_KEY;
|
||||
else process.env.PAPERCLIP_SECRETS_MASTER_KEY = previousMasterKey;
|
||||
for (const dir of tmpDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("prefers the legacy $HOME/instances key when PAPERCLIP_HOME is unset", async () => {
|
||||
const homeDir = path.join(os.tmpdir(), `paperclip-legacy-key-${randomBytes(6).toString("hex")}`);
|
||||
tmpDirs.push(homeDir);
|
||||
process.env.HOME = homeDir;
|
||||
delete process.env.PAPERCLIP_HOME;
|
||||
delete process.env.PAPERCLIP_SECRETS_MASTER_KEY;
|
||||
|
||||
const legacyKeyPath = path.join(homeDir, "instances", "default", "secrets", "master.key");
|
||||
const modernKeyPath = path.join(homeDir, ".paperclip", "instances", "default", "secrets", "master.key");
|
||||
mkdirSync(path.dirname(legacyKeyPath), { recursive: true });
|
||||
mkdirSync(path.dirname(modernKeyPath), { recursive: true });
|
||||
writeFileSync(legacyKeyPath, randomBytes(32).toString("base64"), "utf8");
|
||||
writeFileSync(modernKeyPath, randomBytes(32).toString("base64"), "utf8");
|
||||
|
||||
process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE = legacyKeyPath;
|
||||
const prepared = await localEncryptedProvider.createSecret({ value: "forgejo-token" });
|
||||
|
||||
delete process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||
await expect(
|
||||
localEncryptedProvider.resolveVersion({
|
||||
material: prepared.material,
|
||||
externalRef: prepared.externalRef,
|
||||
providerConfig: null,
|
||||
context: {
|
||||
companyId: "company-1",
|
||||
secretId: "secret-1",
|
||||
version: 1,
|
||||
},
|
||||
}),
|
||||
).resolves.toBe("forgejo-token");
|
||||
|
||||
await expect(localEncryptedProvider.healthCheck()).resolves.toMatchObject({
|
||||
provider: "local_encrypted",
|
||||
details: {
|
||||
keyFilePath: legacyKeyPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveDefaultSecretsKeyFilePath } from "../home-paths.js";
|
||||
import { resolveDefaultSecretsKeyFilePath, resolvePaperclipInstanceId } from "../home-paths.js";
|
||||
import type {
|
||||
PreparedSecretVersion,
|
||||
SecretProviderHealthCheck,
|
||||
|
|
@ -21,7 +22,24 @@ interface LocalEncryptedMaterial extends StoredSecretVersionMaterial {
|
|||
function resolveMasterKeyFilePath() {
|
||||
const fromEnv = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||
if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv.trim());
|
||||
return resolveDefaultSecretsKeyFilePath();
|
||||
const preferredDefault = resolveDefaultSecretsKeyFilePath();
|
||||
if (process.env.PAPERCLIP_HOME?.trim()) return preferredDefault;
|
||||
|
||||
// Backwards compatibility for deployments that historically stored instance
|
||||
// data directly under $HOME/instances/<id> and later restarted without an
|
||||
// explicit PAPERCLIP_HOME. Prefer the legacy key when it already exists so
|
||||
// previously encrypted secrets remain decryptable.
|
||||
const legacyDefault = path.resolve(
|
||||
os.homedir(),
|
||||
"instances",
|
||||
resolvePaperclipInstanceId(),
|
||||
"secrets",
|
||||
"master.key",
|
||||
);
|
||||
if (legacyDefault !== preferredDefault && existsSync(legacyDefault)) {
|
||||
return legacyDefault;
|
||||
}
|
||||
return preferredDefault;
|
||||
}
|
||||
|
||||
function decodeMasterKey(raw: string): Buffer | null {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue