@clef-sh/cli 0.1.6-beta.32 → 0.1.7-beta.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-DDSYn57I.js +46 -0
- package/dist/client/index.html +1 -1
- package/dist/index.cjs +355 -44
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +353 -42
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
- package/dist/client/assets/index-DvB73bYZ.js +0 -46
package/dist/index.mjs
CHANGED
|
@@ -20307,7 +20307,7 @@ var init_runner = __esm({
|
|
|
20307
20307
|
/**
|
|
20308
20308
|
* Lint service identity configurations for drift issues.
|
|
20309
20309
|
*/
|
|
20310
|
-
async lintServiceIdentities(identities, manifest,
|
|
20310
|
+
async lintServiceIdentities(identities, manifest, repoRoot, existingCells) {
|
|
20311
20311
|
const issues = [];
|
|
20312
20312
|
const declaredEnvNames = new Set(manifest.environments.map((e) => e.name));
|
|
20313
20313
|
const declaredNsNames = new Set(manifest.namespaces.map((ns) => ns.name));
|
|
@@ -21941,6 +21941,55 @@ var init_manager2 = __esm({
|
|
|
21941
21941
|
get(manifest, name) {
|
|
21942
21942
|
return manifest.service_identities?.find((si) => si.name === name);
|
|
21943
21943
|
}
|
|
21944
|
+
/**
|
|
21945
|
+
* Update environment backends on an existing service identity.
|
|
21946
|
+
* Switches age → KMS (removes old recipient) or updates KMS config.
|
|
21947
|
+
* Returns new private keys for any environments switched from KMS → age.
|
|
21948
|
+
*/
|
|
21949
|
+
async updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot) {
|
|
21950
|
+
const identity = this.get(manifest, name);
|
|
21951
|
+
if (!identity) {
|
|
21952
|
+
throw new Error(`Service identity '${name}' not found.`);
|
|
21953
|
+
}
|
|
21954
|
+
const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
|
|
21955
|
+
const raw = fs15.readFileSync(manifestPath, "utf-8");
|
|
21956
|
+
const doc = YAML10.parse(raw);
|
|
21957
|
+
const identities = doc.service_identities;
|
|
21958
|
+
const siDoc = identities.find((si) => si.name === name);
|
|
21959
|
+
const envs = siDoc.environments;
|
|
21960
|
+
const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
|
|
21961
|
+
const privateKeys = {};
|
|
21962
|
+
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
21963
|
+
const oldConfig = identity.environments[envName];
|
|
21964
|
+
if (!oldConfig) {
|
|
21965
|
+
throw new Error(`Environment '${envName}' not found on identity '${name}'.`);
|
|
21966
|
+
}
|
|
21967
|
+
if (oldConfig.recipient) {
|
|
21968
|
+
const scopedCells = cells.filter(
|
|
21969
|
+
(c) => identity.namespaces.includes(c.namespace) && c.environment === envName
|
|
21970
|
+
);
|
|
21971
|
+
for (const cell of scopedCells) {
|
|
21972
|
+
try {
|
|
21973
|
+
await this.encryption.removeRecipient(cell.filePath, oldConfig.recipient);
|
|
21974
|
+
} catch {
|
|
21975
|
+
}
|
|
21976
|
+
}
|
|
21977
|
+
}
|
|
21978
|
+
envs[envName] = { kms: kmsConfig };
|
|
21979
|
+
identity.environments[envName] = { kms: kmsConfig };
|
|
21980
|
+
}
|
|
21981
|
+
const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
|
|
21982
|
+
try {
|
|
21983
|
+
fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
|
|
21984
|
+
fs15.renameSync(tmp, manifestPath);
|
|
21985
|
+
} finally {
|
|
21986
|
+
try {
|
|
21987
|
+
fs15.unlinkSync(tmp);
|
|
21988
|
+
} catch {
|
|
21989
|
+
}
|
|
21990
|
+
}
|
|
21991
|
+
return { privateKeys };
|
|
21992
|
+
}
|
|
21944
21993
|
/**
|
|
21945
21994
|
* Register a service identity's public keys as SOPS recipients on scoped matrix files.
|
|
21946
21995
|
*/
|
|
@@ -22201,7 +22250,8 @@ var init_packer = __esm({
|
|
|
22201
22250
|
try {
|
|
22202
22251
|
const e = new Encrypter2();
|
|
22203
22252
|
e.addRecipient(ephemeralPublicKey);
|
|
22204
|
-
|
|
22253
|
+
const encrypted = await e.encrypt(plaintext);
|
|
22254
|
+
ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
|
|
22205
22255
|
} catch {
|
|
22206
22256
|
throw new Error("Failed to age-encrypt artifact with ephemeral key.");
|
|
22207
22257
|
}
|
|
@@ -22230,7 +22280,8 @@ var init_packer = __esm({
|
|
|
22230
22280
|
const { Encrypter: Encrypter2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
22231
22281
|
const e = new Encrypter2();
|
|
22232
22282
|
e.addRecipient(resolved.recipient);
|
|
22233
|
-
|
|
22283
|
+
const encrypted = await e.encrypt(plaintext);
|
|
22284
|
+
ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
|
|
22234
22285
|
} catch {
|
|
22235
22286
|
throw new Error("Failed to age-encrypt artifact. Check recipient key.");
|
|
22236
22287
|
}
|
|
@@ -76033,6 +76084,42 @@ var require_api = __commonJS({
|
|
|
76033
76084
|
res.status(500).json({ error: message, code: "RECIPIENTS_REMOVE_ERROR" });
|
|
76034
76085
|
}
|
|
76035
76086
|
});
|
|
76087
|
+
router.get("/service-identities", (_req, res) => {
|
|
76088
|
+
try {
|
|
76089
|
+
setNoCacheHeaders(res);
|
|
76090
|
+
const manifest = loadManifest();
|
|
76091
|
+
const identities = manifest.service_identities ?? [];
|
|
76092
|
+
const result = identities.map((si) => {
|
|
76093
|
+
const environments = {};
|
|
76094
|
+
for (const [envName, envConfig] of Object.entries(si.environments)) {
|
|
76095
|
+
const env = manifest.environments.find((e) => e.name === envName);
|
|
76096
|
+
if (envConfig.kms) {
|
|
76097
|
+
environments[envName] = {
|
|
76098
|
+
type: "kms",
|
|
76099
|
+
kms: envConfig.kms,
|
|
76100
|
+
protected: env?.protected ?? false
|
|
76101
|
+
};
|
|
76102
|
+
} else {
|
|
76103
|
+
environments[envName] = {
|
|
76104
|
+
type: "age",
|
|
76105
|
+
publicKey: envConfig.recipient,
|
|
76106
|
+
protected: env?.protected ?? false
|
|
76107
|
+
};
|
|
76108
|
+
}
|
|
76109
|
+
}
|
|
76110
|
+
return {
|
|
76111
|
+
name: si.name,
|
|
76112
|
+
description: si.description,
|
|
76113
|
+
namespaces: si.namespaces,
|
|
76114
|
+
environments
|
|
76115
|
+
};
|
|
76116
|
+
});
|
|
76117
|
+
res.json({ identities: result });
|
|
76118
|
+
} catch (err) {
|
|
76119
|
+
const message = err instanceof Error ? err.message : "Failed to load service identities";
|
|
76120
|
+
res.status(500).json({ error: message, code: "SERVICE_IDENTITY_ERROR" });
|
|
76121
|
+
}
|
|
76122
|
+
});
|
|
76036
76123
|
function dispose() {
|
|
76037
76124
|
lastScanResult = null;
|
|
76038
76125
|
lastScanAt = null;
|
|
@@ -76452,7 +76539,9 @@ var require_decrypt = __commonJS({
|
|
|
76452
76539
|
const { Decrypter: Decrypter2 } = await Promise.resolve(`${"age-encryption"}`).then((s) => __importStar(__require(s)));
|
|
76453
76540
|
const d = new Decrypter2();
|
|
76454
76541
|
d.addIdentity(privateKey);
|
|
76455
|
-
|
|
76542
|
+
const isAgePem = ciphertext.startsWith("age-encryption.org/v1\n");
|
|
76543
|
+
const input = isAgePem ? ciphertext : Buffer.from(ciphertext, "base64");
|
|
76544
|
+
return d.decrypt(input, "text");
|
|
76456
76545
|
}
|
|
76457
76546
|
/**
|
|
76458
76547
|
* Resolve the age private key from either an inline value or a file path.
|
|
@@ -77911,6 +78000,7 @@ ${label2}
|
|
|
77911
78000
|
return new Promise((resolve6) => {
|
|
77912
78001
|
rl.question(color(import_picocolors.default.yellow, `${prompt} [y/N] `), (answer) => {
|
|
77913
78002
|
rl.close();
|
|
78003
|
+
process.stdin.pause();
|
|
77914
78004
|
resolve6(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
77915
78005
|
});
|
|
77916
78006
|
});
|
|
@@ -77950,6 +78040,7 @@ ${label2}
|
|
|
77950
78040
|
process.stdin.setRawMode(false);
|
|
77951
78041
|
}
|
|
77952
78042
|
process.stdin.removeListener("data", onData);
|
|
78043
|
+
process.stdin.pause();
|
|
77953
78044
|
process.stderr.write("\n");
|
|
77954
78045
|
resolve6(value);
|
|
77955
78046
|
} else if (char === "") {
|
|
@@ -78082,25 +78173,53 @@ async function getDarwin(runner2, account) {
|
|
|
78082
78173
|
if (result.exitCode === 0) {
|
|
78083
78174
|
const key = result.stdout.trim();
|
|
78084
78175
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
78176
|
+
if (key) {
|
|
78177
|
+
formatter.warn(
|
|
78178
|
+
"OS keychain entry exists but contains invalid key data\n (expected AGE-SECRET-KEY-... format). The entry may be corrupted.\n Delete the 'clef' entry in Keychain Access and re-run clef init."
|
|
78179
|
+
);
|
|
78180
|
+
}
|
|
78085
78181
|
}
|
|
78086
78182
|
return null;
|
|
78087
78183
|
} catch {
|
|
78088
78184
|
return null;
|
|
78089
78185
|
}
|
|
78090
78186
|
}
|
|
78187
|
+
async function readDarwinRaw(runner2, account) {
|
|
78188
|
+
try {
|
|
78189
|
+
const result = await runner2.run("security", [
|
|
78190
|
+
"find-generic-password",
|
|
78191
|
+
"-a",
|
|
78192
|
+
account,
|
|
78193
|
+
"-s",
|
|
78194
|
+
SERVICE,
|
|
78195
|
+
"-w"
|
|
78196
|
+
]);
|
|
78197
|
+
return result.exitCode === 0 ? result.stdout.trim() : "";
|
|
78198
|
+
} catch {
|
|
78199
|
+
return "";
|
|
78200
|
+
}
|
|
78201
|
+
}
|
|
78091
78202
|
async function setDarwin(runner2, privateKey, account) {
|
|
78092
78203
|
await runner2.run("security", ["delete-generic-password", "-a", account, "-s", SERVICE]).catch(() => {
|
|
78093
78204
|
});
|
|
78094
|
-
|
|
78095
|
-
|
|
78096
|
-
|
|
78097
|
-
|
|
78098
|
-
|
|
78099
|
-
|
|
78100
|
-
|
|
78101
|
-
|
|
78102
|
-
|
|
78103
|
-
|
|
78205
|
+
try {
|
|
78206
|
+
const result = await runner2.run(
|
|
78207
|
+
"security",
|
|
78208
|
+
["add-generic-password", "-a", account, "-s", SERVICE, "-w"],
|
|
78209
|
+
{ stdin: privateKey }
|
|
78210
|
+
);
|
|
78211
|
+
if (result.exitCode !== 0) return false;
|
|
78212
|
+
const stored = await readDarwinRaw(runner2, account);
|
|
78213
|
+
if (stored === privateKey) return true;
|
|
78214
|
+
formatter.warn(
|
|
78215
|
+
"Keychain write succeeded but read-back verification failed \u2014\n the stored value may be truncated or corrupted.\n Falling back to file-based key storage."
|
|
78216
|
+
);
|
|
78217
|
+
await runner2.run("security", ["delete-generic-password", "-a", account, "-s", SERVICE]).catch(() => {
|
|
78218
|
+
});
|
|
78219
|
+
return false;
|
|
78220
|
+
} catch {
|
|
78221
|
+
return false;
|
|
78222
|
+
}
|
|
78104
78223
|
}
|
|
78105
78224
|
async function getLinux(runner2, account) {
|
|
78106
78225
|
try {
|
|
@@ -78114,6 +78233,11 @@ async function getLinux(runner2, account) {
|
|
|
78114
78233
|
if (result.exitCode === 0) {
|
|
78115
78234
|
const key = result.stdout.trim();
|
|
78116
78235
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
78236
|
+
if (key) {
|
|
78237
|
+
formatter.warn(
|
|
78238
|
+
"OS keychain entry exists but contains invalid key data\n (expected AGE-SECRET-KEY-... format). The entry may be corrupted."
|
|
78239
|
+
);
|
|
78240
|
+
}
|
|
78117
78241
|
}
|
|
78118
78242
|
return null;
|
|
78119
78243
|
} catch {
|
|
@@ -78157,6 +78281,11 @@ ${CRED_HELPER_CS}
|
|
|
78157
78281
|
if (result.exitCode === 0) {
|
|
78158
78282
|
const key = result.stdout.trim();
|
|
78159
78283
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
78284
|
+
if (key) {
|
|
78285
|
+
formatter.warn(
|
|
78286
|
+
"Windows Credential Manager entry exists but contains invalid key data\n (expected AGE-SECRET-KEY-... format). The entry may be corrupted."
|
|
78287
|
+
);
|
|
78288
|
+
}
|
|
78160
78289
|
}
|
|
78161
78290
|
return null;
|
|
78162
78291
|
} catch {
|
|
@@ -78423,6 +78552,9 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
78423
78552
|
formatter.success("Stored age key in OS keychain");
|
|
78424
78553
|
config = { age_key_storage: "keychain", age_keychain_label: label2 };
|
|
78425
78554
|
} else {
|
|
78555
|
+
formatter.warn(
|
|
78556
|
+
"OS keychain is not available on this system.\n The private key will be written to the filesystem instead.\n See https://docs.clef.sh/guide/key-storage for security implications."
|
|
78557
|
+
);
|
|
78426
78558
|
let keyPath;
|
|
78427
78559
|
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
78428
78560
|
keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label2);
|
|
@@ -78798,6 +78930,7 @@ function promptWithDefault(message, defaultValue) {
|
|
|
78798
78930
|
return new Promise((resolve6) => {
|
|
78799
78931
|
rl.question(prompt, (answer) => {
|
|
78800
78932
|
rl.close();
|
|
78933
|
+
process.stdin.pause();
|
|
78801
78934
|
resolve6(answer.trim() || defaultValue);
|
|
78802
78935
|
});
|
|
78803
78936
|
});
|
|
@@ -78820,6 +78953,11 @@ async function resolveAgeCredential(repoRoot, runner2) {
|
|
|
78820
78953
|
if (label2) {
|
|
78821
78954
|
const keychainKey = await getKeychainKey(runner2, label2);
|
|
78822
78955
|
if (keychainKey) return { source: "keychain", privateKey: keychainKey };
|
|
78956
|
+
if (config?.age_key_storage !== "file") {
|
|
78957
|
+
formatter.warn(
|
|
78958
|
+
"OS keychain is configured but the age key could not be retrieved.\n Falling back to environment variables / key file.\n Run clef doctor for diagnostics."
|
|
78959
|
+
);
|
|
78960
|
+
}
|
|
78823
78961
|
}
|
|
78824
78962
|
if (process.env.CLEF_AGE_KEY) return { source: "env-key" };
|
|
78825
78963
|
if (process.env.CLEF_AGE_KEY_FILE) return { source: "env-file" };
|
|
@@ -78873,7 +79011,10 @@ async function resolveAgePrivateKey(repoRoot, runner2) {
|
|
|
78873
79011
|
const content = fs18.readFileSync(filePath, "utf-8");
|
|
78874
79012
|
const match = content.match(AGE_SECRET_KEY_RE);
|
|
78875
79013
|
return match ? match[1] : null;
|
|
78876
|
-
} catch {
|
|
79014
|
+
} catch (err) {
|
|
79015
|
+
formatter.warn(
|
|
79016
|
+
`Could not read age key file (CLEF_AGE_KEY_FILE=${filePath}): ${err instanceof Error ? err.message : String(err)}`
|
|
79017
|
+
);
|
|
78877
79018
|
return null;
|
|
78878
79019
|
}
|
|
78879
79020
|
}
|
|
@@ -78882,7 +79023,10 @@ async function resolveAgePrivateKey(repoRoot, runner2) {
|
|
|
78882
79023
|
const content = fs18.readFileSync(credential.path, "utf-8");
|
|
78883
79024
|
const match = content.match(AGE_SECRET_KEY_RE);
|
|
78884
79025
|
return match ? match[1] : null;
|
|
78885
|
-
} catch {
|
|
79026
|
+
} catch (err) {
|
|
79027
|
+
formatter.warn(
|
|
79028
|
+
`Could not read age key file (${credential.path}): ${err instanceof Error ? err.message : String(err)}`
|
|
79029
|
+
);
|
|
78886
79030
|
return null;
|
|
78887
79031
|
}
|
|
78888
79032
|
}
|
|
@@ -78893,16 +79037,52 @@ function readLocalConfig(repoRoot) {
|
|
|
78893
79037
|
try {
|
|
78894
79038
|
if (!fs18.existsSync(clefConfigPath)) return null;
|
|
78895
79039
|
return YAML12.parse(fs18.readFileSync(clefConfigPath, "utf-8"));
|
|
78896
|
-
} catch {
|
|
79040
|
+
} catch (err) {
|
|
79041
|
+
formatter.warn(
|
|
79042
|
+
`Failed to parse ${clefConfigPath}: ${err instanceof Error ? err.message : String(err)}
|
|
79043
|
+
Credential resolution will proceed without local config.`
|
|
79044
|
+
);
|
|
78897
79045
|
return null;
|
|
78898
79046
|
}
|
|
78899
79047
|
}
|
|
78900
79048
|
|
|
79049
|
+
// src/clipboard.ts
|
|
79050
|
+
import { execFileSync } from "child_process";
|
|
79051
|
+
function copyToClipboard(text) {
|
|
79052
|
+
try {
|
|
79053
|
+
switch (process.platform) {
|
|
79054
|
+
case "darwin":
|
|
79055
|
+
execFileSync("pbcopy", { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
79056
|
+
return true;
|
|
79057
|
+
case "win32":
|
|
79058
|
+
execFileSync("clip", { input: text, stdio: ["pipe", "ignore", "ignore"], shell: true });
|
|
79059
|
+
return true;
|
|
79060
|
+
default: {
|
|
79061
|
+
for (const cmd of ["xclip", "xsel"]) {
|
|
79062
|
+
try {
|
|
79063
|
+
const args = cmd === "xclip" ? ["-selection", "clipboard"] : ["--clipboard", "--input"];
|
|
79064
|
+
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
79065
|
+
return true;
|
|
79066
|
+
} catch {
|
|
79067
|
+
continue;
|
|
79068
|
+
}
|
|
79069
|
+
}
|
|
79070
|
+
return false;
|
|
79071
|
+
}
|
|
79072
|
+
}
|
|
79073
|
+
} catch {
|
|
79074
|
+
return false;
|
|
79075
|
+
}
|
|
79076
|
+
}
|
|
79077
|
+
function maskedPlaceholder() {
|
|
79078
|
+
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
79079
|
+
}
|
|
79080
|
+
|
|
78901
79081
|
// src/commands/get.ts
|
|
78902
79082
|
function registerGetCommand(program3, deps2) {
|
|
78903
79083
|
program3.command("get <target> <key>").description(
|
|
78904
|
-
"Get a single decrypted value.\n\n target: namespace/environment (e.g. payments/production)\n key: the key name to retrieve\n\nExit codes:\n 0 Value found
|
|
78905
|
-
).action(async (target, key) => {
|
|
79084
|
+
"Get a single decrypted value.\n\n target: namespace/environment (e.g. payments/production)\n key: the key name to retrieve\n\nBy default, the value is copied to clipboard and obfuscated on screen.\nUse --raw to print the plaintext value to stdout.\n\nExit codes:\n 0 Value found\n 1 Key not found or decryption error"
|
|
79085
|
+
).option("--raw", "Print the plaintext value to stdout (for piping/scripting)").action(async (target, key, opts2) => {
|
|
78906
79086
|
try {
|
|
78907
79087
|
const [namespace, environment] = parseTarget(target);
|
|
78908
79088
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -78921,7 +79101,17 @@ function registerGetCommand(program3, deps2) {
|
|
|
78921
79101
|
process.exit(1);
|
|
78922
79102
|
return;
|
|
78923
79103
|
}
|
|
78924
|
-
|
|
79104
|
+
const val = decrypted.values[key];
|
|
79105
|
+
if (opts2.raw) {
|
|
79106
|
+
formatter.raw(val);
|
|
79107
|
+
} else {
|
|
79108
|
+
const copied = copyToClipboard(val);
|
|
79109
|
+
if (copied) {
|
|
79110
|
+
formatter.print(` ${key}: ${maskedPlaceholder()} (copied to clipboard)`);
|
|
79111
|
+
} else {
|
|
79112
|
+
formatter.keyValue(key, val);
|
|
79113
|
+
}
|
|
79114
|
+
}
|
|
78925
79115
|
} catch (err) {
|
|
78926
79116
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
78927
79117
|
formatter.formatDependencyError(err);
|
|
@@ -79633,7 +79823,7 @@ async function fetchCheckpoint(config) {
|
|
|
79633
79823
|
}
|
|
79634
79824
|
|
|
79635
79825
|
// package.json
|
|
79636
|
-
var version = "0.1.
|
|
79826
|
+
var version = "0.1.7-beta.43";
|
|
79637
79827
|
var package_default = {
|
|
79638
79828
|
name: "@clef-sh/cli",
|
|
79639
79829
|
version,
|
|
@@ -80154,8 +80344,8 @@ init_src();
|
|
|
80154
80344
|
import * as path31 from "path";
|
|
80155
80345
|
function registerExportCommand(program3, deps2) {
|
|
80156
80346
|
program3.command("export <target>").description(
|
|
80157
|
-
"
|
|
80158
|
-
).option("--format <format>", "Output format (only 'env' is supported)", "env").option("--no-export", "Omit the 'export' keyword \u2014 output bare KEY=value pairs").action(async (target, options) => {
|
|
80347
|
+
"Export decrypted secrets as shell export statements.\n\n target: namespace/environment (e.g. payments/production)\n\nBy default, exports are copied to clipboard. Use --raw to print to stdout.\n\nUsage:\n clef export payments/production (copies to clipboard)\n eval $(clef export payments/production --raw) (injects into shell)\n\nExit codes:\n 0 Values exported successfully\n 1 Decryption error or invalid arguments"
|
|
80348
|
+
).option("--format <format>", "Output format (only 'env' is supported)", "env").option("--no-export", "Omit the 'export' keyword \u2014 output bare KEY=value pairs").option("--raw", "Print to stdout instead of clipboard (for eval/piping)").action(async (target, options) => {
|
|
80159
80349
|
try {
|
|
80160
80350
|
if (options.format !== "env") {
|
|
80161
80351
|
if (options.format === "dotenv" || options.format === "json" || options.format === "yaml") {
|
|
@@ -80188,12 +80378,28 @@ Usage: clef export payments/production --format env`
|
|
|
80188
80378
|
const decrypted = await sopsClient.decrypt(filePath);
|
|
80189
80379
|
const consumption = new ConsumptionClient();
|
|
80190
80380
|
const output = consumption.formatExport(decrypted, "env", !options.export);
|
|
80191
|
-
if (
|
|
80192
|
-
|
|
80193
|
-
|
|
80194
|
-
|
|
80381
|
+
if (options.raw) {
|
|
80382
|
+
if (process.platform === "linux") {
|
|
80383
|
+
formatter.warn(
|
|
80384
|
+
"Exported values will be visible in /proc/<pid>/environ to processes with ptrace access. Use clef exec when possible."
|
|
80385
|
+
);
|
|
80386
|
+
}
|
|
80387
|
+
formatter.raw(output);
|
|
80388
|
+
} else {
|
|
80389
|
+
const keyCount = Object.keys(decrypted.values).length;
|
|
80390
|
+
const copied = copyToClipboard(output);
|
|
80391
|
+
if (copied) {
|
|
80392
|
+
formatter.success(`${keyCount} secret(s) copied to clipboard as env exports.`);
|
|
80393
|
+
formatter.hint("eval $(clef export " + target + " --raw) to inject into shell");
|
|
80394
|
+
} else {
|
|
80395
|
+
if (process.platform === "linux") {
|
|
80396
|
+
formatter.warn(
|
|
80397
|
+
"Exported values will be visible in /proc/<pid>/environ to processes with ptrace access. Use clef exec when possible."
|
|
80398
|
+
);
|
|
80399
|
+
}
|
|
80400
|
+
formatter.raw(output);
|
|
80401
|
+
}
|
|
80195
80402
|
}
|
|
80196
|
-
formatter.raw(output);
|
|
80197
80403
|
} catch (err) {
|
|
80198
80404
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
80199
80405
|
formatter.formatDependencyError(err);
|
|
@@ -80853,6 +81059,7 @@ function waitForEnter(message) {
|
|
|
80853
81059
|
});
|
|
80854
81060
|
rl.question(message, () => {
|
|
80855
81061
|
rl.close();
|
|
81062
|
+
process.stdin.pause();
|
|
80856
81063
|
resolve6();
|
|
80857
81064
|
});
|
|
80858
81065
|
});
|
|
@@ -81402,6 +81609,9 @@ function registerServiceCommand(program3, deps2) {
|
|
|
81402
81609
|
`Invalid KMS provider '${provider}'. Must be one of: aws, gcp, azure.`
|
|
81403
81610
|
);
|
|
81404
81611
|
}
|
|
81612
|
+
if (kmsEnvConfigs[envName]) {
|
|
81613
|
+
throw new Error(`Duplicate --kms-env for environment '${envName}'.`);
|
|
81614
|
+
}
|
|
81405
81615
|
kmsEnvConfigs[envName] = {
|
|
81406
81616
|
provider,
|
|
81407
81617
|
keyId
|
|
@@ -81440,13 +81650,24 @@ function registerServiceCommand(program3, deps2) {
|
|
|
81440
81650
|
`
|
|
81441
81651
|
);
|
|
81442
81652
|
if (Object.keys(result.privateKeys).length > 0) {
|
|
81443
|
-
|
|
81444
|
-
|
|
81445
|
-
);
|
|
81446
|
-
|
|
81447
|
-
formatter.
|
|
81448
|
-
|
|
81653
|
+
const entries = Object.entries(result.privateKeys);
|
|
81654
|
+
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81655
|
+
const copied = copyToClipboard(block);
|
|
81656
|
+
if (copied) {
|
|
81657
|
+
formatter.warn("Private keys copied to clipboard. Store them securely.\n");
|
|
81658
|
+
for (const [envName] of entries) {
|
|
81659
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81660
|
+
}
|
|
81661
|
+
formatter.print("");
|
|
81662
|
+
} else {
|
|
81663
|
+
formatter.warn(
|
|
81664
|
+
"Private keys are shown ONCE. Store them securely (e.g. AWS Secrets Manager, Vault).\n"
|
|
81665
|
+
);
|
|
81666
|
+
for (const [envName, privateKey] of entries) {
|
|
81667
|
+
formatter.print(` ${envName}:`);
|
|
81668
|
+
formatter.print(` ${privateKey}
|
|
81449
81669
|
`);
|
|
81670
|
+
}
|
|
81450
81671
|
}
|
|
81451
81672
|
for (const k of Object.keys(result.privateKeys)) result.privateKeys[k] = "";
|
|
81452
81673
|
}
|
|
@@ -81581,6 +81802,71 @@ Service Identity: ${identity.name}`);
|
|
|
81581
81802
|
process.exit(1);
|
|
81582
81803
|
}
|
|
81583
81804
|
});
|
|
81805
|
+
serviceCmd.command("update <name>").description("Update an existing service identity's environment backends.").option(
|
|
81806
|
+
"--kms-env <mapping>",
|
|
81807
|
+
"Switch an environment to KMS envelope encryption: env=provider:keyId (repeatable)",
|
|
81808
|
+
(val, acc) => {
|
|
81809
|
+
acc.push(val);
|
|
81810
|
+
return acc;
|
|
81811
|
+
},
|
|
81812
|
+
[]
|
|
81813
|
+
).action(async (name, opts2) => {
|
|
81814
|
+
try {
|
|
81815
|
+
if (opts2.kmsEnv.length === 0) {
|
|
81816
|
+
formatter.error("Nothing to update. Provide --kms-env to change environment backends.");
|
|
81817
|
+
process.exit(1);
|
|
81818
|
+
return;
|
|
81819
|
+
}
|
|
81820
|
+
const repoRoot = program3.opts().dir || process.cwd();
|
|
81821
|
+
const parser = new ManifestParser();
|
|
81822
|
+
const manifest = parser.parse(path38.join(repoRoot, "clef.yaml"));
|
|
81823
|
+
const kmsEnvConfigs = {};
|
|
81824
|
+
for (const mapping of opts2.kmsEnv) {
|
|
81825
|
+
const eqIdx = mapping.indexOf("=");
|
|
81826
|
+
if (eqIdx === -1) {
|
|
81827
|
+
throw new Error(`Invalid --kms-env format: '${mapping}'. Expected: env=provider:keyId`);
|
|
81828
|
+
}
|
|
81829
|
+
const envName = mapping.slice(0, eqIdx);
|
|
81830
|
+
const rest = mapping.slice(eqIdx + 1);
|
|
81831
|
+
const colonIdx = rest.indexOf(":");
|
|
81832
|
+
if (colonIdx === -1) {
|
|
81833
|
+
throw new Error(`Invalid --kms-env format: '${mapping}'. Expected: env=provider:keyId`);
|
|
81834
|
+
}
|
|
81835
|
+
const provider = rest.slice(0, colonIdx);
|
|
81836
|
+
const keyId = rest.slice(colonIdx + 1);
|
|
81837
|
+
if (!["aws", "gcp", "azure"].includes(provider)) {
|
|
81838
|
+
throw new Error(`Invalid KMS provider '${provider}'. Must be one of: aws, gcp, azure.`);
|
|
81839
|
+
}
|
|
81840
|
+
if (kmsEnvConfigs[envName]) {
|
|
81841
|
+
throw new Error(`Duplicate --kms-env for environment '${envName}'.`);
|
|
81842
|
+
}
|
|
81843
|
+
kmsEnvConfigs[envName] = {
|
|
81844
|
+
provider,
|
|
81845
|
+
keyId
|
|
81846
|
+
};
|
|
81847
|
+
}
|
|
81848
|
+
const matrixManager = new MatrixManager();
|
|
81849
|
+
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
81850
|
+
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
81851
|
+
formatter.print(`${sym("working")} Updating service identity '${name}'...`);
|
|
81852
|
+
await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
|
|
81853
|
+
formatter.success(`Service identity '${name}' updated.`);
|
|
81854
|
+
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
81855
|
+
formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
|
|
81856
|
+
}
|
|
81857
|
+
formatter.hint(
|
|
81858
|
+
`git add clef.yaml && git commit -m "chore: update service identity '${name}'"`
|
|
81859
|
+
);
|
|
81860
|
+
} catch (err) {
|
|
81861
|
+
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
81862
|
+
formatter.formatDependencyError(err);
|
|
81863
|
+
process.exit(1);
|
|
81864
|
+
return;
|
|
81865
|
+
}
|
|
81866
|
+
formatter.error(err.message);
|
|
81867
|
+
process.exit(1);
|
|
81868
|
+
}
|
|
81869
|
+
});
|
|
81584
81870
|
serviceCmd.command("rotate <name>").description("Rotate the age key for a service identity.").option("-e, --environment <env>", "Rotate only a specific environment").action(async (name, opts2) => {
|
|
81585
81871
|
try {
|
|
81586
81872
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -81610,11 +81896,22 @@ Service Identity: ${identity.name}`);
|
|
|
81610
81896
|
formatter.print(`${sym("working")} Rotating key for '${name}'...`);
|
|
81611
81897
|
const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts2.environment);
|
|
81612
81898
|
formatter.success(`Key rotated for '${name}'.`);
|
|
81613
|
-
|
|
81614
|
-
|
|
81615
|
-
|
|
81616
|
-
|
|
81899
|
+
const entries = Object.entries(newKeys);
|
|
81900
|
+
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81901
|
+
const copied = copyToClipboard(block);
|
|
81902
|
+
if (copied) {
|
|
81903
|
+
formatter.warn("New private keys copied to clipboard. Store them securely.\n");
|
|
81904
|
+
for (const [envName] of entries) {
|
|
81905
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81906
|
+
}
|
|
81907
|
+
formatter.print("");
|
|
81908
|
+
} else {
|
|
81909
|
+
formatter.warn("New private keys are shown ONCE. Store them securely.\n");
|
|
81910
|
+
for (const [envName, privateKey] of entries) {
|
|
81911
|
+
formatter.print(` ${envName}:`);
|
|
81912
|
+
formatter.print(` ${privateKey}
|
|
81617
81913
|
`);
|
|
81914
|
+
}
|
|
81618
81915
|
}
|
|
81619
81916
|
for (const k of Object.keys(newKeys)) newKeys[k] = "";
|
|
81620
81917
|
formatter.hint(
|
|
@@ -81628,11 +81925,25 @@ Service Identity: ${identity.name}`);
|
|
|
81628
81925
|
}
|
|
81629
81926
|
if (err instanceof PartialRotationError) {
|
|
81630
81927
|
formatter.error(err.message);
|
|
81631
|
-
|
|
81632
|
-
|
|
81633
|
-
|
|
81634
|
-
|
|
81928
|
+
const partialEntries = Object.entries(err.rotatedKeys);
|
|
81929
|
+
const partialBlock = partialEntries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81930
|
+
const partialCopied = copyToClipboard(partialBlock);
|
|
81931
|
+
if (partialCopied) {
|
|
81932
|
+
formatter.warn(
|
|
81933
|
+
"Partial rotation succeeded. Rotated keys copied to clipboard \u2014 store them NOW.\n"
|
|
81934
|
+
);
|
|
81935
|
+
for (const [envName] of partialEntries) {
|
|
81936
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81937
|
+
}
|
|
81938
|
+
} else {
|
|
81939
|
+
formatter.warn(
|
|
81940
|
+
"Partial rotation succeeded. New private keys below \u2014 store them NOW.\n"
|
|
81941
|
+
);
|
|
81942
|
+
for (const [envName, privateKey] of partialEntries) {
|
|
81943
|
+
formatter.print(` ${envName}:`);
|
|
81944
|
+
formatter.print(` ${privateKey}
|
|
81635
81945
|
`);
|
|
81946
|
+
}
|
|
81636
81947
|
}
|
|
81637
81948
|
for (const k of Object.keys(err.rotatedKeys)) {
|
|
81638
81949
|
err.rotatedKeys[k] = "";
|