@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/client/index.html
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -19907,7 +19907,7 @@ var init_runner = __esm({
|
|
|
19907
19907
|
/**
|
|
19908
19908
|
* Lint service identity configurations for drift issues.
|
|
19909
19909
|
*/
|
|
19910
|
-
async lintServiceIdentities(identities, manifest,
|
|
19910
|
+
async lintServiceIdentities(identities, manifest, repoRoot, existingCells) {
|
|
19911
19911
|
const issues = [];
|
|
19912
19912
|
const declaredEnvNames = new Set(manifest.environments.map((e) => e.name));
|
|
19913
19913
|
const declaredNsNames = new Set(manifest.namespaces.map((ns) => ns.name));
|
|
@@ -21541,6 +21541,55 @@ var init_manager2 = __esm({
|
|
|
21541
21541
|
get(manifest, name) {
|
|
21542
21542
|
return manifest.service_identities?.find((si) => si.name === name);
|
|
21543
21543
|
}
|
|
21544
|
+
/**
|
|
21545
|
+
* Update environment backends on an existing service identity.
|
|
21546
|
+
* Switches age → KMS (removes old recipient) or updates KMS config.
|
|
21547
|
+
* Returns new private keys for any environments switched from KMS → age.
|
|
21548
|
+
*/
|
|
21549
|
+
async updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot) {
|
|
21550
|
+
const identity = this.get(manifest, name);
|
|
21551
|
+
if (!identity) {
|
|
21552
|
+
throw new Error(`Service identity '${name}' not found.`);
|
|
21553
|
+
}
|
|
21554
|
+
const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
|
|
21555
|
+
const raw = fs15.readFileSync(manifestPath, "utf-8");
|
|
21556
|
+
const doc = YAML10.parse(raw);
|
|
21557
|
+
const identities = doc.service_identities;
|
|
21558
|
+
const siDoc = identities.find((si) => si.name === name);
|
|
21559
|
+
const envs = siDoc.environments;
|
|
21560
|
+
const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
|
|
21561
|
+
const privateKeys = {};
|
|
21562
|
+
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
21563
|
+
const oldConfig = identity.environments[envName];
|
|
21564
|
+
if (!oldConfig) {
|
|
21565
|
+
throw new Error(`Environment '${envName}' not found on identity '${name}'.`);
|
|
21566
|
+
}
|
|
21567
|
+
if (oldConfig.recipient) {
|
|
21568
|
+
const scopedCells = cells.filter(
|
|
21569
|
+
(c) => identity.namespaces.includes(c.namespace) && c.environment === envName
|
|
21570
|
+
);
|
|
21571
|
+
for (const cell of scopedCells) {
|
|
21572
|
+
try {
|
|
21573
|
+
await this.encryption.removeRecipient(cell.filePath, oldConfig.recipient);
|
|
21574
|
+
} catch {
|
|
21575
|
+
}
|
|
21576
|
+
}
|
|
21577
|
+
}
|
|
21578
|
+
envs[envName] = { kms: kmsConfig };
|
|
21579
|
+
identity.environments[envName] = { kms: kmsConfig };
|
|
21580
|
+
}
|
|
21581
|
+
const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
|
|
21582
|
+
try {
|
|
21583
|
+
fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
|
|
21584
|
+
fs15.renameSync(tmp, manifestPath);
|
|
21585
|
+
} finally {
|
|
21586
|
+
try {
|
|
21587
|
+
fs15.unlinkSync(tmp);
|
|
21588
|
+
} catch {
|
|
21589
|
+
}
|
|
21590
|
+
}
|
|
21591
|
+
return { privateKeys };
|
|
21592
|
+
}
|
|
21544
21593
|
/**
|
|
21545
21594
|
* Register a service identity's public keys as SOPS recipients on scoped matrix files.
|
|
21546
21595
|
*/
|
|
@@ -21801,7 +21850,8 @@ var init_packer = __esm({
|
|
|
21801
21850
|
try {
|
|
21802
21851
|
const e = new Encrypter();
|
|
21803
21852
|
e.addRecipient(ephemeralPublicKey);
|
|
21804
|
-
|
|
21853
|
+
const encrypted = await e.encrypt(plaintext);
|
|
21854
|
+
ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
|
|
21805
21855
|
} catch {
|
|
21806
21856
|
throw new Error("Failed to age-encrypt artifact with ephemeral key.");
|
|
21807
21857
|
}
|
|
@@ -21830,7 +21880,8 @@ var init_packer = __esm({
|
|
|
21830
21880
|
const { Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
|
|
21831
21881
|
const e = new Encrypter();
|
|
21832
21882
|
e.addRecipient(resolved.recipient);
|
|
21833
|
-
|
|
21883
|
+
const encrypted = await e.encrypt(plaintext);
|
|
21884
|
+
ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
|
|
21834
21885
|
} catch {
|
|
21835
21886
|
throw new Error("Failed to age-encrypt artifact. Check recipient key.");
|
|
21836
21887
|
}
|
|
@@ -75633,6 +75684,42 @@ var require_api = __commonJS({
|
|
|
75633
75684
|
res.status(500).json({ error: message, code: "RECIPIENTS_REMOVE_ERROR" });
|
|
75634
75685
|
}
|
|
75635
75686
|
});
|
|
75687
|
+
router.get("/service-identities", (_req, res) => {
|
|
75688
|
+
try {
|
|
75689
|
+
setNoCacheHeaders(res);
|
|
75690
|
+
const manifest = loadManifest();
|
|
75691
|
+
const identities = manifest.service_identities ?? [];
|
|
75692
|
+
const result = identities.map((si) => {
|
|
75693
|
+
const environments = {};
|
|
75694
|
+
for (const [envName, envConfig] of Object.entries(si.environments)) {
|
|
75695
|
+
const env = manifest.environments.find((e) => e.name === envName);
|
|
75696
|
+
if (envConfig.kms) {
|
|
75697
|
+
environments[envName] = {
|
|
75698
|
+
type: "kms",
|
|
75699
|
+
kms: envConfig.kms,
|
|
75700
|
+
protected: env?.protected ?? false
|
|
75701
|
+
};
|
|
75702
|
+
} else {
|
|
75703
|
+
environments[envName] = {
|
|
75704
|
+
type: "age",
|
|
75705
|
+
publicKey: envConfig.recipient,
|
|
75706
|
+
protected: env?.protected ?? false
|
|
75707
|
+
};
|
|
75708
|
+
}
|
|
75709
|
+
}
|
|
75710
|
+
return {
|
|
75711
|
+
name: si.name,
|
|
75712
|
+
description: si.description,
|
|
75713
|
+
namespaces: si.namespaces,
|
|
75714
|
+
environments
|
|
75715
|
+
};
|
|
75716
|
+
});
|
|
75717
|
+
res.json({ identities: result });
|
|
75718
|
+
} catch (err) {
|
|
75719
|
+
const message = err instanceof Error ? err.message : "Failed to load service identities";
|
|
75720
|
+
res.status(500).json({ error: message, code: "SERVICE_IDENTITY_ERROR" });
|
|
75721
|
+
}
|
|
75722
|
+
});
|
|
75636
75723
|
function dispose() {
|
|
75637
75724
|
lastScanResult = null;
|
|
75638
75725
|
lastScanAt = null;
|
|
@@ -76052,7 +76139,9 @@ var require_decrypt = __commonJS({
|
|
|
76052
76139
|
const { Decrypter } = await Promise.resolve(`${"age-encryption"}`).then((s) => __importStar(require(s)));
|
|
76053
76140
|
const d = new Decrypter();
|
|
76054
76141
|
d.addIdentity(privateKey);
|
|
76055
|
-
|
|
76142
|
+
const isAgePem = ciphertext.startsWith("age-encryption.org/v1\n");
|
|
76143
|
+
const input = isAgePem ? ciphertext : Buffer.from(ciphertext, "base64");
|
|
76144
|
+
return d.decrypt(input, "text");
|
|
76056
76145
|
}
|
|
76057
76146
|
/**
|
|
76058
76147
|
* Resolve the age private key from either an inline value or a file path.
|
|
@@ -77511,6 +77600,7 @@ ${label}
|
|
|
77511
77600
|
return new Promise((resolve6) => {
|
|
77512
77601
|
rl.question(color(import_picocolors.default.yellow, `${prompt} [y/N] `), (answer) => {
|
|
77513
77602
|
rl.close();
|
|
77603
|
+
process.stdin.pause();
|
|
77514
77604
|
resolve6(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
77515
77605
|
});
|
|
77516
77606
|
});
|
|
@@ -77550,6 +77640,7 @@ ${label}
|
|
|
77550
77640
|
process.stdin.setRawMode(false);
|
|
77551
77641
|
}
|
|
77552
77642
|
process.stdin.removeListener("data", onData);
|
|
77643
|
+
process.stdin.pause();
|
|
77553
77644
|
process.stderr.write("\n");
|
|
77554
77645
|
resolve6(value);
|
|
77555
77646
|
} else if (char === "") {
|
|
@@ -77682,25 +77773,53 @@ async function getDarwin(runner2, account) {
|
|
|
77682
77773
|
if (result.exitCode === 0) {
|
|
77683
77774
|
const key = result.stdout.trim();
|
|
77684
77775
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
77776
|
+
if (key) {
|
|
77777
|
+
formatter.warn(
|
|
77778
|
+
"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."
|
|
77779
|
+
);
|
|
77780
|
+
}
|
|
77685
77781
|
}
|
|
77686
77782
|
return null;
|
|
77687
77783
|
} catch {
|
|
77688
77784
|
return null;
|
|
77689
77785
|
}
|
|
77690
77786
|
}
|
|
77787
|
+
async function readDarwinRaw(runner2, account) {
|
|
77788
|
+
try {
|
|
77789
|
+
const result = await runner2.run("security", [
|
|
77790
|
+
"find-generic-password",
|
|
77791
|
+
"-a",
|
|
77792
|
+
account,
|
|
77793
|
+
"-s",
|
|
77794
|
+
SERVICE,
|
|
77795
|
+
"-w"
|
|
77796
|
+
]);
|
|
77797
|
+
return result.exitCode === 0 ? result.stdout.trim() : "";
|
|
77798
|
+
} catch {
|
|
77799
|
+
return "";
|
|
77800
|
+
}
|
|
77801
|
+
}
|
|
77691
77802
|
async function setDarwin(runner2, privateKey, account) {
|
|
77692
77803
|
await runner2.run("security", ["delete-generic-password", "-a", account, "-s", SERVICE]).catch(() => {
|
|
77693
77804
|
});
|
|
77694
|
-
|
|
77695
|
-
|
|
77696
|
-
|
|
77697
|
-
|
|
77698
|
-
|
|
77699
|
-
|
|
77700
|
-
|
|
77701
|
-
|
|
77702
|
-
|
|
77703
|
-
|
|
77805
|
+
try {
|
|
77806
|
+
const result = await runner2.run(
|
|
77807
|
+
"security",
|
|
77808
|
+
["add-generic-password", "-a", account, "-s", SERVICE, "-w"],
|
|
77809
|
+
{ stdin: privateKey }
|
|
77810
|
+
);
|
|
77811
|
+
if (result.exitCode !== 0) return false;
|
|
77812
|
+
const stored = await readDarwinRaw(runner2, account);
|
|
77813
|
+
if (stored === privateKey) return true;
|
|
77814
|
+
formatter.warn(
|
|
77815
|
+
"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."
|
|
77816
|
+
);
|
|
77817
|
+
await runner2.run("security", ["delete-generic-password", "-a", account, "-s", SERVICE]).catch(() => {
|
|
77818
|
+
});
|
|
77819
|
+
return false;
|
|
77820
|
+
} catch {
|
|
77821
|
+
return false;
|
|
77822
|
+
}
|
|
77704
77823
|
}
|
|
77705
77824
|
async function getLinux(runner2, account) {
|
|
77706
77825
|
try {
|
|
@@ -77714,6 +77833,11 @@ async function getLinux(runner2, account) {
|
|
|
77714
77833
|
if (result.exitCode === 0) {
|
|
77715
77834
|
const key = result.stdout.trim();
|
|
77716
77835
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
77836
|
+
if (key) {
|
|
77837
|
+
formatter.warn(
|
|
77838
|
+
"OS keychain entry exists but contains invalid key data\n (expected AGE-SECRET-KEY-... format). The entry may be corrupted."
|
|
77839
|
+
);
|
|
77840
|
+
}
|
|
77717
77841
|
}
|
|
77718
77842
|
return null;
|
|
77719
77843
|
} catch {
|
|
@@ -77757,6 +77881,11 @@ ${CRED_HELPER_CS}
|
|
|
77757
77881
|
if (result.exitCode === 0) {
|
|
77758
77882
|
const key = result.stdout.trim();
|
|
77759
77883
|
if (key.startsWith("AGE-SECRET-KEY-")) return key;
|
|
77884
|
+
if (key) {
|
|
77885
|
+
formatter.warn(
|
|
77886
|
+
"Windows Credential Manager entry exists but contains invalid key data\n (expected AGE-SECRET-KEY-... format). The entry may be corrupted."
|
|
77887
|
+
);
|
|
77888
|
+
}
|
|
77760
77889
|
}
|
|
77761
77890
|
return null;
|
|
77762
77891
|
} catch {
|
|
@@ -78023,6 +78152,9 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
78023
78152
|
formatter.success("Stored age key in OS keychain");
|
|
78024
78153
|
config = { age_key_storage: "keychain", age_keychain_label: label };
|
|
78025
78154
|
} else {
|
|
78155
|
+
formatter.warn(
|
|
78156
|
+
"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."
|
|
78157
|
+
);
|
|
78026
78158
|
let keyPath;
|
|
78027
78159
|
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
78028
78160
|
keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label);
|
|
@@ -78398,6 +78530,7 @@ function promptWithDefault(message, defaultValue) {
|
|
|
78398
78530
|
return new Promise((resolve6) => {
|
|
78399
78531
|
rl.question(prompt, (answer) => {
|
|
78400
78532
|
rl.close();
|
|
78533
|
+
process.stdin.pause();
|
|
78401
78534
|
resolve6(answer.trim() || defaultValue);
|
|
78402
78535
|
});
|
|
78403
78536
|
});
|
|
@@ -78420,6 +78553,11 @@ async function resolveAgeCredential(repoRoot, runner2) {
|
|
|
78420
78553
|
if (label) {
|
|
78421
78554
|
const keychainKey = await getKeychainKey(runner2, label);
|
|
78422
78555
|
if (keychainKey) return { source: "keychain", privateKey: keychainKey };
|
|
78556
|
+
if (config?.age_key_storage !== "file") {
|
|
78557
|
+
formatter.warn(
|
|
78558
|
+
"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."
|
|
78559
|
+
);
|
|
78560
|
+
}
|
|
78423
78561
|
}
|
|
78424
78562
|
if (process.env.CLEF_AGE_KEY) return { source: "env-key" };
|
|
78425
78563
|
if (process.env.CLEF_AGE_KEY_FILE) return { source: "env-file" };
|
|
@@ -78473,7 +78611,10 @@ async function resolveAgePrivateKey(repoRoot, runner2) {
|
|
|
78473
78611
|
const content = fs18.readFileSync(filePath, "utf-8");
|
|
78474
78612
|
const match = content.match(AGE_SECRET_KEY_RE);
|
|
78475
78613
|
return match ? match[1] : null;
|
|
78476
|
-
} catch {
|
|
78614
|
+
} catch (err) {
|
|
78615
|
+
formatter.warn(
|
|
78616
|
+
`Could not read age key file (CLEF_AGE_KEY_FILE=${filePath}): ${err instanceof Error ? err.message : String(err)}`
|
|
78617
|
+
);
|
|
78477
78618
|
return null;
|
|
78478
78619
|
}
|
|
78479
78620
|
}
|
|
@@ -78482,7 +78623,10 @@ async function resolveAgePrivateKey(repoRoot, runner2) {
|
|
|
78482
78623
|
const content = fs18.readFileSync(credential.path, "utf-8");
|
|
78483
78624
|
const match = content.match(AGE_SECRET_KEY_RE);
|
|
78484
78625
|
return match ? match[1] : null;
|
|
78485
|
-
} catch {
|
|
78626
|
+
} catch (err) {
|
|
78627
|
+
formatter.warn(
|
|
78628
|
+
`Could not read age key file (${credential.path}): ${err instanceof Error ? err.message : String(err)}`
|
|
78629
|
+
);
|
|
78486
78630
|
return null;
|
|
78487
78631
|
}
|
|
78488
78632
|
}
|
|
@@ -78493,16 +78637,52 @@ function readLocalConfig(repoRoot) {
|
|
|
78493
78637
|
try {
|
|
78494
78638
|
if (!fs18.existsSync(clefConfigPath)) return null;
|
|
78495
78639
|
return YAML12.parse(fs18.readFileSync(clefConfigPath, "utf-8"));
|
|
78496
|
-
} catch {
|
|
78640
|
+
} catch (err) {
|
|
78641
|
+
formatter.warn(
|
|
78642
|
+
`Failed to parse ${clefConfigPath}: ${err instanceof Error ? err.message : String(err)}
|
|
78643
|
+
Credential resolution will proceed without local config.`
|
|
78644
|
+
);
|
|
78497
78645
|
return null;
|
|
78498
78646
|
}
|
|
78499
78647
|
}
|
|
78500
78648
|
|
|
78649
|
+
// src/clipboard.ts
|
|
78650
|
+
var import_child_process2 = require("child_process");
|
|
78651
|
+
function copyToClipboard(text) {
|
|
78652
|
+
try {
|
|
78653
|
+
switch (process.platform) {
|
|
78654
|
+
case "darwin":
|
|
78655
|
+
(0, import_child_process2.execFileSync)("pbcopy", { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
78656
|
+
return true;
|
|
78657
|
+
case "win32":
|
|
78658
|
+
(0, import_child_process2.execFileSync)("clip", { input: text, stdio: ["pipe", "ignore", "ignore"], shell: true });
|
|
78659
|
+
return true;
|
|
78660
|
+
default: {
|
|
78661
|
+
for (const cmd of ["xclip", "xsel"]) {
|
|
78662
|
+
try {
|
|
78663
|
+
const args = cmd === "xclip" ? ["-selection", "clipboard"] : ["--clipboard", "--input"];
|
|
78664
|
+
(0, import_child_process2.execFileSync)(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
78665
|
+
return true;
|
|
78666
|
+
} catch {
|
|
78667
|
+
continue;
|
|
78668
|
+
}
|
|
78669
|
+
}
|
|
78670
|
+
return false;
|
|
78671
|
+
}
|
|
78672
|
+
}
|
|
78673
|
+
} catch {
|
|
78674
|
+
return false;
|
|
78675
|
+
}
|
|
78676
|
+
}
|
|
78677
|
+
function maskedPlaceholder() {
|
|
78678
|
+
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
78679
|
+
}
|
|
78680
|
+
|
|
78501
78681
|
// src/commands/get.ts
|
|
78502
78682
|
function registerGetCommand(program3, deps2) {
|
|
78503
78683
|
program3.command("get <target> <key>").description(
|
|
78504
|
-
"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
|
|
78505
|
-
).action(async (target, key) => {
|
|
78684
|
+
"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"
|
|
78685
|
+
).option("--raw", "Print the plaintext value to stdout (for piping/scripting)").action(async (target, key, opts) => {
|
|
78506
78686
|
try {
|
|
78507
78687
|
const [namespace, environment] = parseTarget(target);
|
|
78508
78688
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -78521,7 +78701,17 @@ function registerGetCommand(program3, deps2) {
|
|
|
78521
78701
|
process.exit(1);
|
|
78522
78702
|
return;
|
|
78523
78703
|
}
|
|
78524
|
-
|
|
78704
|
+
const val = decrypted.values[key];
|
|
78705
|
+
if (opts.raw) {
|
|
78706
|
+
formatter.raw(val);
|
|
78707
|
+
} else {
|
|
78708
|
+
const copied = copyToClipboard(val);
|
|
78709
|
+
if (copied) {
|
|
78710
|
+
formatter.print(` ${key}: ${maskedPlaceholder()} (copied to clipboard)`);
|
|
78711
|
+
} else {
|
|
78712
|
+
formatter.keyValue(key, val);
|
|
78713
|
+
}
|
|
78714
|
+
}
|
|
78525
78715
|
} catch (err) {
|
|
78526
78716
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
78527
78717
|
formatter.formatDependencyError(err);
|
|
@@ -79233,7 +79423,7 @@ async function fetchCheckpoint(config) {
|
|
|
79233
79423
|
}
|
|
79234
79424
|
|
|
79235
79425
|
// package.json
|
|
79236
|
-
var version = "0.1.
|
|
79426
|
+
var version = "0.1.7-beta.43";
|
|
79237
79427
|
var package_default = {
|
|
79238
79428
|
name: "@clef-sh/cli",
|
|
79239
79429
|
version,
|
|
@@ -79596,7 +79786,7 @@ async function openBrowser(url, runner2) {
|
|
|
79596
79786
|
|
|
79597
79787
|
// src/commands/exec.ts
|
|
79598
79788
|
var path30 = __toESM(require("path"));
|
|
79599
|
-
var
|
|
79789
|
+
var import_child_process3 = require("child_process");
|
|
79600
79790
|
init_src();
|
|
79601
79791
|
function collect(value, previous) {
|
|
79602
79792
|
return previous.concat([value]);
|
|
@@ -79701,7 +79891,7 @@ function spawnChild(command, args, env) {
|
|
|
79701
79891
|
return new Promise((resolve6) => {
|
|
79702
79892
|
let child;
|
|
79703
79893
|
try {
|
|
79704
|
-
child = (0,
|
|
79894
|
+
child = (0, import_child_process3.spawn)(command, args, {
|
|
79705
79895
|
env,
|
|
79706
79896
|
stdio: "inherit"
|
|
79707
79897
|
});
|
|
@@ -79754,8 +79944,8 @@ var path31 = __toESM(require("path"));
|
|
|
79754
79944
|
init_src();
|
|
79755
79945
|
function registerExportCommand(program3, deps2) {
|
|
79756
79946
|
program3.command("export <target>").description(
|
|
79757
|
-
"
|
|
79758
|
-
).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) => {
|
|
79947
|
+
"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"
|
|
79948
|
+
).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) => {
|
|
79759
79949
|
try {
|
|
79760
79950
|
if (options.format !== "env") {
|
|
79761
79951
|
if (options.format === "dotenv" || options.format === "json" || options.format === "yaml") {
|
|
@@ -79788,12 +79978,28 @@ Usage: clef export payments/production --format env`
|
|
|
79788
79978
|
const decrypted = await sopsClient.decrypt(filePath);
|
|
79789
79979
|
const consumption = new ConsumptionClient();
|
|
79790
79980
|
const output = consumption.formatExport(decrypted, "env", !options.export);
|
|
79791
|
-
if (
|
|
79792
|
-
|
|
79793
|
-
|
|
79794
|
-
|
|
79981
|
+
if (options.raw) {
|
|
79982
|
+
if (process.platform === "linux") {
|
|
79983
|
+
formatter.warn(
|
|
79984
|
+
"Exported values will be visible in /proc/<pid>/environ to processes with ptrace access. Use clef exec when possible."
|
|
79985
|
+
);
|
|
79986
|
+
}
|
|
79987
|
+
formatter.raw(output);
|
|
79988
|
+
} else {
|
|
79989
|
+
const keyCount = Object.keys(decrypted.values).length;
|
|
79990
|
+
const copied = copyToClipboard(output);
|
|
79991
|
+
if (copied) {
|
|
79992
|
+
formatter.success(`${keyCount} secret(s) copied to clipboard as env exports.`);
|
|
79993
|
+
formatter.hint("eval $(clef export " + target + " --raw) to inject into shell");
|
|
79994
|
+
} else {
|
|
79995
|
+
if (process.platform === "linux") {
|
|
79996
|
+
formatter.warn(
|
|
79997
|
+
"Exported values will be visible in /proc/<pid>/environ to processes with ptrace access. Use clef exec when possible."
|
|
79998
|
+
);
|
|
79999
|
+
}
|
|
80000
|
+
formatter.raw(output);
|
|
80001
|
+
}
|
|
79795
80002
|
}
|
|
79796
|
-
formatter.raw(output);
|
|
79797
80003
|
} catch (err) {
|
|
79798
80004
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
79799
80005
|
formatter.formatDependencyError(err);
|
|
@@ -80453,6 +80659,7 @@ function waitForEnter(message) {
|
|
|
80453
80659
|
});
|
|
80454
80660
|
rl.question(message, () => {
|
|
80455
80661
|
rl.close();
|
|
80662
|
+
process.stdin.pause();
|
|
80456
80663
|
resolve6();
|
|
80457
80664
|
});
|
|
80458
80665
|
});
|
|
@@ -81002,6 +81209,9 @@ function registerServiceCommand(program3, deps2) {
|
|
|
81002
81209
|
`Invalid KMS provider '${provider}'. Must be one of: aws, gcp, azure.`
|
|
81003
81210
|
);
|
|
81004
81211
|
}
|
|
81212
|
+
if (kmsEnvConfigs[envName]) {
|
|
81213
|
+
throw new Error(`Duplicate --kms-env for environment '${envName}'.`);
|
|
81214
|
+
}
|
|
81005
81215
|
kmsEnvConfigs[envName] = {
|
|
81006
81216
|
provider,
|
|
81007
81217
|
keyId
|
|
@@ -81040,13 +81250,24 @@ function registerServiceCommand(program3, deps2) {
|
|
|
81040
81250
|
`
|
|
81041
81251
|
);
|
|
81042
81252
|
if (Object.keys(result.privateKeys).length > 0) {
|
|
81043
|
-
|
|
81044
|
-
|
|
81045
|
-
);
|
|
81046
|
-
|
|
81047
|
-
formatter.
|
|
81048
|
-
|
|
81253
|
+
const entries = Object.entries(result.privateKeys);
|
|
81254
|
+
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81255
|
+
const copied = copyToClipboard(block);
|
|
81256
|
+
if (copied) {
|
|
81257
|
+
formatter.warn("Private keys copied to clipboard. Store them securely.\n");
|
|
81258
|
+
for (const [envName] of entries) {
|
|
81259
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81260
|
+
}
|
|
81261
|
+
formatter.print("");
|
|
81262
|
+
} else {
|
|
81263
|
+
formatter.warn(
|
|
81264
|
+
"Private keys are shown ONCE. Store them securely (e.g. AWS Secrets Manager, Vault).\n"
|
|
81265
|
+
);
|
|
81266
|
+
for (const [envName, privateKey] of entries) {
|
|
81267
|
+
formatter.print(` ${envName}:`);
|
|
81268
|
+
formatter.print(` ${privateKey}
|
|
81049
81269
|
`);
|
|
81270
|
+
}
|
|
81050
81271
|
}
|
|
81051
81272
|
for (const k of Object.keys(result.privateKeys)) result.privateKeys[k] = "";
|
|
81052
81273
|
}
|
|
@@ -81181,6 +81402,71 @@ Service Identity: ${identity.name}`);
|
|
|
81181
81402
|
process.exit(1);
|
|
81182
81403
|
}
|
|
81183
81404
|
});
|
|
81405
|
+
serviceCmd.command("update <name>").description("Update an existing service identity's environment backends.").option(
|
|
81406
|
+
"--kms-env <mapping>",
|
|
81407
|
+
"Switch an environment to KMS envelope encryption: env=provider:keyId (repeatable)",
|
|
81408
|
+
(val, acc) => {
|
|
81409
|
+
acc.push(val);
|
|
81410
|
+
return acc;
|
|
81411
|
+
},
|
|
81412
|
+
[]
|
|
81413
|
+
).action(async (name, opts) => {
|
|
81414
|
+
try {
|
|
81415
|
+
if (opts.kmsEnv.length === 0) {
|
|
81416
|
+
formatter.error("Nothing to update. Provide --kms-env to change environment backends.");
|
|
81417
|
+
process.exit(1);
|
|
81418
|
+
return;
|
|
81419
|
+
}
|
|
81420
|
+
const repoRoot = program3.opts().dir || process.cwd();
|
|
81421
|
+
const parser = new ManifestParser();
|
|
81422
|
+
const manifest = parser.parse(path38.join(repoRoot, "clef.yaml"));
|
|
81423
|
+
const kmsEnvConfigs = {};
|
|
81424
|
+
for (const mapping of opts.kmsEnv) {
|
|
81425
|
+
const eqIdx = mapping.indexOf("=");
|
|
81426
|
+
if (eqIdx === -1) {
|
|
81427
|
+
throw new Error(`Invalid --kms-env format: '${mapping}'. Expected: env=provider:keyId`);
|
|
81428
|
+
}
|
|
81429
|
+
const envName = mapping.slice(0, eqIdx);
|
|
81430
|
+
const rest = mapping.slice(eqIdx + 1);
|
|
81431
|
+
const colonIdx = rest.indexOf(":");
|
|
81432
|
+
if (colonIdx === -1) {
|
|
81433
|
+
throw new Error(`Invalid --kms-env format: '${mapping}'. Expected: env=provider:keyId`);
|
|
81434
|
+
}
|
|
81435
|
+
const provider = rest.slice(0, colonIdx);
|
|
81436
|
+
const keyId = rest.slice(colonIdx + 1);
|
|
81437
|
+
if (!["aws", "gcp", "azure"].includes(provider)) {
|
|
81438
|
+
throw new Error(`Invalid KMS provider '${provider}'. Must be one of: aws, gcp, azure.`);
|
|
81439
|
+
}
|
|
81440
|
+
if (kmsEnvConfigs[envName]) {
|
|
81441
|
+
throw new Error(`Duplicate --kms-env for environment '${envName}'.`);
|
|
81442
|
+
}
|
|
81443
|
+
kmsEnvConfigs[envName] = {
|
|
81444
|
+
provider,
|
|
81445
|
+
keyId
|
|
81446
|
+
};
|
|
81447
|
+
}
|
|
81448
|
+
const matrixManager = new MatrixManager();
|
|
81449
|
+
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
81450
|
+
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
81451
|
+
formatter.print(`${sym("working")} Updating service identity '${name}'...`);
|
|
81452
|
+
await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
|
|
81453
|
+
formatter.success(`Service identity '${name}' updated.`);
|
|
81454
|
+
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
81455
|
+
formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
|
|
81456
|
+
}
|
|
81457
|
+
formatter.hint(
|
|
81458
|
+
`git add clef.yaml && git commit -m "chore: update service identity '${name}'"`
|
|
81459
|
+
);
|
|
81460
|
+
} catch (err) {
|
|
81461
|
+
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
81462
|
+
formatter.formatDependencyError(err);
|
|
81463
|
+
process.exit(1);
|
|
81464
|
+
return;
|
|
81465
|
+
}
|
|
81466
|
+
formatter.error(err.message);
|
|
81467
|
+
process.exit(1);
|
|
81468
|
+
}
|
|
81469
|
+
});
|
|
81184
81470
|
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, opts) => {
|
|
81185
81471
|
try {
|
|
81186
81472
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -81210,11 +81496,22 @@ Service Identity: ${identity.name}`);
|
|
|
81210
81496
|
formatter.print(`${sym("working")} Rotating key for '${name}'...`);
|
|
81211
81497
|
const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts.environment);
|
|
81212
81498
|
formatter.success(`Key rotated for '${name}'.`);
|
|
81213
|
-
|
|
81214
|
-
|
|
81215
|
-
|
|
81216
|
-
|
|
81499
|
+
const entries = Object.entries(newKeys);
|
|
81500
|
+
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81501
|
+
const copied = copyToClipboard(block);
|
|
81502
|
+
if (copied) {
|
|
81503
|
+
formatter.warn("New private keys copied to clipboard. Store them securely.\n");
|
|
81504
|
+
for (const [envName] of entries) {
|
|
81505
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81506
|
+
}
|
|
81507
|
+
formatter.print("");
|
|
81508
|
+
} else {
|
|
81509
|
+
formatter.warn("New private keys are shown ONCE. Store them securely.\n");
|
|
81510
|
+
for (const [envName, privateKey] of entries) {
|
|
81511
|
+
formatter.print(` ${envName}:`);
|
|
81512
|
+
formatter.print(` ${privateKey}
|
|
81217
81513
|
`);
|
|
81514
|
+
}
|
|
81218
81515
|
}
|
|
81219
81516
|
for (const k of Object.keys(newKeys)) newKeys[k] = "";
|
|
81220
81517
|
formatter.hint(
|
|
@@ -81228,11 +81525,25 @@ Service Identity: ${identity.name}`);
|
|
|
81228
81525
|
}
|
|
81229
81526
|
if (err instanceof PartialRotationError) {
|
|
81230
81527
|
formatter.error(err.message);
|
|
81231
|
-
|
|
81232
|
-
|
|
81233
|
-
|
|
81234
|
-
|
|
81528
|
+
const partialEntries = Object.entries(err.rotatedKeys);
|
|
81529
|
+
const partialBlock = partialEntries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
81530
|
+
const partialCopied = copyToClipboard(partialBlock);
|
|
81531
|
+
if (partialCopied) {
|
|
81532
|
+
formatter.warn(
|
|
81533
|
+
"Partial rotation succeeded. Rotated keys copied to clipboard \u2014 store them NOW.\n"
|
|
81534
|
+
);
|
|
81535
|
+
for (const [envName] of partialEntries) {
|
|
81536
|
+
formatter.print(` ${envName}: ${maskedPlaceholder()}`);
|
|
81537
|
+
}
|
|
81538
|
+
} else {
|
|
81539
|
+
formatter.warn(
|
|
81540
|
+
"Partial rotation succeeded. New private keys below \u2014 store them NOW.\n"
|
|
81541
|
+
);
|
|
81542
|
+
for (const [envName, privateKey] of partialEntries) {
|
|
81543
|
+
formatter.print(` ${envName}:`);
|
|
81544
|
+
formatter.print(` ${privateKey}
|
|
81235
81545
|
`);
|
|
81546
|
+
}
|
|
81236
81547
|
}
|
|
81237
81548
|
for (const k of Object.keys(err.rotatedKeys)) {
|
|
81238
81549
|
err.rotatedKeys[k] = "";
|