mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
e356bb1f5d
When encryption_key.yaml changes and is pushed via `wmill sync push`, pushWorkspaceKey prompted interactively to confirm re-encrypting the remote secrets with the new key. That prompt ignored `--yes` and had no TTY guard, so a CI/non-interactive push that included the key would block (or behave undefinedly) on the prompt. Thread a key-push options object (non-interactive flag + explicit re-encryption choice) through pushObj into pushWorkspaceKey: - Non-interactive (`--yes` or no TTY) and no explicit choice: skip the prompt and default to re-encrypting all remote secrets with the new key (matches the interactive default), preserving their plaintext values. - New `--skip-reencrypt-on-key-change` flag (and the WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true env var for CI) opt out of re-encryption — only safe when the remote ciphertexts are already encrypted with the new key (e.g. workspace/instance migration). - Interactive behavior (TTY, no `--yes`) is unchanged. Regenerates system_prompts for the new option and adds unit tests for the no-op, re-encrypt-by-default, flag-skip, and env-skip paths. Fixes WIN-2005 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
/**
|
|
* Unit tests for pushWorkspaceKey in settings.ts.
|
|
*
|
|
* Covers WIN-2005: changing the encryption key in encryption_key.yaml and
|
|
* pushing it must (by default) re-encrypt the remote secrets with the new key.
|
|
*
|
|
* Verifies that:
|
|
* - an unchanged key is a no-op (no setWorkspaceEncryptionKey call)
|
|
* - a changed key in non-interactive mode re-encrypts by default
|
|
* (skip_reencrypt = false), so secret plaintext values are preserved
|
|
* - the --skip-reencrypt-on-key-change flag keeps the remote ciphertexts
|
|
* untouched (skip_reencrypt = true)
|
|
* - WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true does the same via env var
|
|
*/
|
|
|
|
import { expect, test, describe, beforeEach, afterEach, mock } from "bun:test";
|
|
|
|
// Track calls to mocked wmill functions
|
|
let remoteKey = "";
|
|
let setEncryptionKeyCalls: {
|
|
workspace: string;
|
|
requestBody: { new_key: string; skip_reencrypt?: boolean };
|
|
}[] = [];
|
|
|
|
// Mock the wmill module before importing settings.ts
|
|
mock.module("../gen/services.gen.ts", () => ({
|
|
getWorkspaceEncryptionKey: async (_args: { workspace: string }) => ({
|
|
key: remoteKey,
|
|
}),
|
|
setWorkspaceEncryptionKey: async (args: {
|
|
workspace: string;
|
|
requestBody: { new_key: string; skip_reencrypt?: boolean };
|
|
}) => {
|
|
setEncryptionKeyCalls.push(args);
|
|
},
|
|
}));
|
|
|
|
import { pushWorkspaceKey } from "../src/core/settings.ts";
|
|
|
|
describe("pushWorkspaceKey", () => {
|
|
const ws = "test-workspace";
|
|
|
|
beforeEach(() => {
|
|
remoteKey = "";
|
|
setEncryptionKeyCalls = [];
|
|
delete process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE;
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE;
|
|
});
|
|
|
|
test("no-op when local key matches the remote key", async () => {
|
|
remoteKey = "samekey";
|
|
await pushWorkspaceKey(ws, "encryption_key", undefined, "samekey", {
|
|
noninteractive: true,
|
|
});
|
|
expect(setEncryptionKeyCalls.length).toBe(0);
|
|
});
|
|
|
|
test("changed key re-encrypts by default in non-interactive mode", async () => {
|
|
remoteKey = "oldkey";
|
|
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
|
|
noninteractive: true,
|
|
});
|
|
expect(setEncryptionKeyCalls.length).toBe(1);
|
|
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
|
|
// skip_reencrypt false => backend re-encrypts existing secrets with new key
|
|
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(false);
|
|
});
|
|
|
|
test("--skip-reencrypt-on-key-change skips re-encryption", async () => {
|
|
remoteKey = "oldkey";
|
|
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
|
|
noninteractive: true,
|
|
skipReencrypt: true,
|
|
});
|
|
expect(setEncryptionKeyCalls.length).toBe(1);
|
|
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
|
|
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(true);
|
|
});
|
|
|
|
test("WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true skips re-encryption non-interactively", async () => {
|
|
remoteKey = "oldkey";
|
|
process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE = "true";
|
|
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
|
|
noninteractive: true,
|
|
});
|
|
expect(setEncryptionKeyCalls.length).toBe(1);
|
|
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
|
|
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(true);
|
|
});
|
|
});
|