mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 19: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 { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
||||||
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { resolveDefaultSecretsKeyFilePath } from "../home-paths.js";
|
import { resolveDefaultSecretsKeyFilePath, resolvePaperclipInstanceId } from "../home-paths.js";
|
||||||
import type {
|
import type {
|
||||||
PreparedSecretVersion,
|
PreparedSecretVersion,
|
||||||
SecretProviderHealthCheck,
|
SecretProviderHealthCheck,
|
||||||
|
|
@ -21,7 +22,24 @@ interface LocalEncryptedMaterial extends StoredSecretVersionMaterial {
|
||||||
function resolveMasterKeyFilePath() {
|
function resolveMasterKeyFilePath() {
|
||||||
const fromEnv = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
const fromEnv = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||||
if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv.trim());
|
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 {
|
function decodeMasterKey(raw: string): Buffer | null {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue