@clef-sh/core 0.1.15 → 0.1.16
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/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -44
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +83 -44
- package/dist/index.mjs.map +2 -2
- package/dist/lint/runner.d.ts.map +1 -1
- package/dist/manifest/parser.d.ts.map +1 -1
- package/dist/service-identity/manager.d.ts +13 -5
- package/dist/service-identity/manager.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2638,6 +2638,12 @@ var ManifestParser = class {
|
|
|
2638
2638
|
"service_identities"
|
|
2639
2639
|
);
|
|
2640
2640
|
}
|
|
2641
|
+
if (siObj.pack_only !== void 0 && typeof siObj.pack_only !== "boolean") {
|
|
2642
|
+
throw new ManifestValidationError(
|
|
2643
|
+
`Service identity '${siName}' has a non-boolean 'pack_only' field.`,
|
|
2644
|
+
"service_identities"
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2641
2647
|
if (!Array.isArray(siObj.namespaces) || siObj.namespaces.length === 0) {
|
|
2642
2648
|
throw new ManifestValidationError(
|
|
2643
2649
|
`Service identity '${siName}' must have a non-empty 'namespaces' array.`,
|
|
@@ -2743,7 +2749,8 @@ var ManifestParser = class {
|
|
|
2743
2749
|
name: siName,
|
|
2744
2750
|
description: siObj.description ?? "",
|
|
2745
2751
|
namespaces: siObj.namespaces,
|
|
2746
|
-
environments: parsedEnvs
|
|
2752
|
+
environments: parsedEnvs,
|
|
2753
|
+
...siObj.pack_only === true ? { pack_only: true } : {}
|
|
2747
2754
|
};
|
|
2748
2755
|
});
|
|
2749
2756
|
const siNames = /* @__PURE__ */ new Set();
|
|
@@ -5089,6 +5096,18 @@ var LintRunner = class {
|
|
|
5089
5096
|
});
|
|
5090
5097
|
}
|
|
5091
5098
|
}
|
|
5099
|
+
if (si.pack_only) {
|
|
5100
|
+
const ageRecipients = Object.values(si.environments).filter((cfg) => !isKmsEnvelope(cfg) && cfg.recipient).map((cfg) => cfg.recipient);
|
|
5101
|
+
if (ageRecipients.length >= 2 && new Set(ageRecipients).size === 1) {
|
|
5102
|
+
issues.push({
|
|
5103
|
+
severity: "warning",
|
|
5104
|
+
category: "service-identity",
|
|
5105
|
+
file: "clef.yaml",
|
|
5106
|
+
message: `Runtime identity '${si.name}' uses a shared recipient across all environments. A compromised key in any environment decrypts artifacts for all environments. Consider per-environment keys for runtime workloads.`
|
|
5107
|
+
});
|
|
5108
|
+
}
|
|
5109
|
+
continue;
|
|
5110
|
+
}
|
|
5092
5111
|
for (const cell of existingCells) {
|
|
5093
5112
|
const envConfig = si.environments[cell.environment];
|
|
5094
5113
|
if (!envConfig) continue;
|
|
@@ -6448,12 +6467,10 @@ var ServiceIdentityManager = class {
|
|
|
6448
6467
|
* Create a new service identity with per-environment age key pairs or KMS envelope config.
|
|
6449
6468
|
* For age-only: generates keys, updates the manifest, and registers public keys as SOPS recipients.
|
|
6450
6469
|
* For KMS: stores KMS config in manifest, no age keys generated.
|
|
6451
|
-
*
|
|
6452
|
-
* @param kmsEnvConfigs - Optional per-environment KMS config. When provided, those envs use
|
|
6453
|
-
* KMS envelope encryption instead of generating age keys.
|
|
6454
|
-
* @returns The created identity definition and the per-environment private keys (empty for KMS envs).
|
|
6470
|
+
* For pack-only (runtime) identities: keys are generated but NOT registered on SOPS files.
|
|
6455
6471
|
*/
|
|
6456
|
-
async create(name, namespaces, description, manifest, repoRoot,
|
|
6472
|
+
async create(name, namespaces, description, manifest, repoRoot, options) {
|
|
6473
|
+
const { kmsEnvConfigs, sharedRecipient, packOnly } = options ?? {};
|
|
6457
6474
|
if (manifest.service_identities?.some((si) => si.name === name)) {
|
|
6458
6475
|
throw new Error(`Service identity '${name}' already exists.`);
|
|
6459
6476
|
}
|
|
@@ -6465,23 +6482,25 @@ var ServiceIdentityManager = class {
|
|
|
6465
6482
|
}
|
|
6466
6483
|
const environments = {};
|
|
6467
6484
|
const privateKeys = {};
|
|
6485
|
+
const sharedKey = sharedRecipient ? await generateAgeIdentity() : void 0;
|
|
6468
6486
|
for (const env of manifest.environments) {
|
|
6469
6487
|
const kmsConfig = kmsEnvConfigs?.[env.name];
|
|
6470
6488
|
if (kmsConfig) {
|
|
6471
6489
|
environments[env.name] = { kms: kmsConfig };
|
|
6472
6490
|
} else {
|
|
6473
|
-
const
|
|
6474
|
-
environments[env.name] = { recipient:
|
|
6475
|
-
privateKeys[env.name] =
|
|
6491
|
+
const ageIdentity = sharedKey ?? await generateAgeIdentity();
|
|
6492
|
+
environments[env.name] = { recipient: ageIdentity.publicKey };
|
|
6493
|
+
privateKeys[env.name] = ageIdentity.privateKey;
|
|
6476
6494
|
}
|
|
6477
6495
|
}
|
|
6478
6496
|
const definition = {
|
|
6479
6497
|
name,
|
|
6480
6498
|
description,
|
|
6481
6499
|
namespaces,
|
|
6482
|
-
environments
|
|
6500
|
+
environments,
|
|
6501
|
+
...packOnly ? { pack_only: true } : {}
|
|
6483
6502
|
};
|
|
6484
|
-
const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && namespaces.includes(c.namespace));
|
|
6503
|
+
const cells = packOnly ? [] : this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && namespaces.includes(c.namespace));
|
|
6485
6504
|
await this.tx.run(repoRoot, {
|
|
6486
6505
|
description: `clef service create ${name}`,
|
|
6487
6506
|
paths: this.txPaths(repoRoot, cells),
|
|
@@ -6495,12 +6514,13 @@ var ServiceIdentityManager = class {
|
|
|
6495
6514
|
name,
|
|
6496
6515
|
description,
|
|
6497
6516
|
namespaces,
|
|
6498
|
-
environments
|
|
6517
|
+
environments,
|
|
6518
|
+
...packOnly ? { pack_only: true } : {}
|
|
6499
6519
|
});
|
|
6500
6520
|
writeManifestYaml(repoRoot, doc);
|
|
6501
6521
|
}
|
|
6502
6522
|
});
|
|
6503
|
-
return { identity: definition, privateKeys };
|
|
6523
|
+
return { identity: definition, privateKeys, sharedRecipient: sharedKey !== void 0 };
|
|
6504
6524
|
}
|
|
6505
6525
|
/**
|
|
6506
6526
|
* List all service identities from the manifest.
|
|
@@ -6523,7 +6543,7 @@ var ServiceIdentityManager = class {
|
|
|
6523
6543
|
if (!identity) {
|
|
6524
6544
|
throw new Error(`Service identity '${name}' not found.`);
|
|
6525
6545
|
}
|
|
6526
|
-
const scopedCells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && identity.namespaces.includes(c.namespace));
|
|
6546
|
+
const scopedCells = identity.pack_only ? [] : this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && identity.namespaces.includes(c.namespace));
|
|
6527
6547
|
await this.tx.run(repoRoot, {
|
|
6528
6548
|
description: `clef service delete ${name}`,
|
|
6529
6549
|
paths: this.txPaths(repoRoot, scopedCells),
|
|
@@ -6579,7 +6599,7 @@ var ServiceIdentityManager = class {
|
|
|
6579
6599
|
const envs = siDoc.environments;
|
|
6580
6600
|
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
6581
6601
|
const oldConfig = identity.environments[envName];
|
|
6582
|
-
if (oldConfig?.recipient && !isKmsEnvelope(oldConfig)) {
|
|
6602
|
+
if (!identity.pack_only && oldConfig?.recipient && !isKmsEnvelope(oldConfig)) {
|
|
6583
6603
|
const scopedCells = cells.filter((c) => c.environment === envName);
|
|
6584
6604
|
for (const cell of scopedCells) {
|
|
6585
6605
|
try {
|
|
@@ -6598,8 +6618,10 @@ var ServiceIdentityManager = class {
|
|
|
6598
6618
|
}
|
|
6599
6619
|
/**
|
|
6600
6620
|
* Register a service identity's public keys as SOPS recipients on scoped matrix files.
|
|
6621
|
+
* Pack-only (runtime) identities skip registration entirely.
|
|
6601
6622
|
*/
|
|
6602
6623
|
async registerRecipients(identity, manifest, repoRoot) {
|
|
6624
|
+
if (identity.pack_only) return;
|
|
6603
6625
|
const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
|
|
6604
6626
|
for (const cell of cells) {
|
|
6605
6627
|
if (!identity.namespaces.includes(cell.namespace)) continue;
|
|
@@ -6649,18 +6671,20 @@ var ServiceIdentityManager = class {
|
|
|
6649
6671
|
description: `clef service update ${name}: add namespaces ${toAdd.join(",")}`,
|
|
6650
6672
|
paths: this.txPaths(repoRoot, cells),
|
|
6651
6673
|
mutate: async () => {
|
|
6652
|
-
|
|
6653
|
-
const
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6674
|
+
if (!identity.pack_only) {
|
|
6675
|
+
for (const cell of cells) {
|
|
6676
|
+
const envConfig = identity.environments[cell.environment];
|
|
6677
|
+
if (!envConfig) continue;
|
|
6678
|
+
if (isKmsEnvelope(envConfig)) continue;
|
|
6679
|
+
if (!envConfig.recipient) continue;
|
|
6680
|
+
try {
|
|
6681
|
+
await this.encryption.addRecipient(cell.filePath, envConfig.recipient);
|
|
6682
|
+
affectedFiles.push(cell.filePath);
|
|
6683
|
+
} catch (err) {
|
|
6684
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6685
|
+
if (!message.includes("already")) {
|
|
6686
|
+
throw err;
|
|
6687
|
+
}
|
|
6664
6688
|
}
|
|
6665
6689
|
}
|
|
6666
6690
|
}
|
|
@@ -6708,15 +6732,17 @@ var ServiceIdentityManager = class {
|
|
|
6708
6732
|
description: `clef service update ${name}: remove namespaces ${namespacesToRemove.join(",")}`,
|
|
6709
6733
|
paths: this.txPaths(repoRoot, cells),
|
|
6710
6734
|
mutate: async () => {
|
|
6711
|
-
|
|
6712
|
-
const
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6735
|
+
if (!identity.pack_only) {
|
|
6736
|
+
for (const cell of cells) {
|
|
6737
|
+
const envConfig = identity.environments[cell.environment];
|
|
6738
|
+
if (!envConfig) continue;
|
|
6739
|
+
if (isKmsEnvelope(envConfig)) continue;
|
|
6740
|
+
if (!envConfig.recipient) continue;
|
|
6741
|
+
try {
|
|
6742
|
+
await this.encryption.removeRecipient(cell.filePath, envConfig.recipient);
|
|
6743
|
+
affectedFiles.push(cell.filePath);
|
|
6744
|
+
} catch {
|
|
6745
|
+
}
|
|
6720
6746
|
}
|
|
6721
6747
|
}
|
|
6722
6748
|
const doc = readManifestYaml(repoRoot);
|
|
@@ -6779,7 +6805,7 @@ var ServiceIdentityManager = class {
|
|
|
6779
6805
|
description: `clef service add-env ${name} ${envName}`,
|
|
6780
6806
|
paths: this.txPaths(repoRoot, cells),
|
|
6781
6807
|
mutate: async () => {
|
|
6782
|
-
if (!isKmsEnvelope(envConfig) && envConfig.recipient) {
|
|
6808
|
+
if (!identity.pack_only && !isKmsEnvelope(envConfig) && envConfig.recipient) {
|
|
6783
6809
|
for (const cell of cells) {
|
|
6784
6810
|
try {
|
|
6785
6811
|
await this.encryption.addRecipient(cell.filePath, envConfig.recipient);
|
|
@@ -6838,7 +6864,7 @@ var ServiceIdentityManager = class {
|
|
|
6838
6864
|
if (targetEnvNames.size === 0) {
|
|
6839
6865
|
return newPrivateKeys;
|
|
6840
6866
|
}
|
|
6841
|
-
const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter(
|
|
6867
|
+
const cells = identity.pack_only ? [] : this.matrixManager.resolveMatrix(manifest, repoRoot).filter(
|
|
6842
6868
|
(c) => c.exists && identity.namespaces.includes(c.namespace) && targetEnvNames.has(c.environment)
|
|
6843
6869
|
);
|
|
6844
6870
|
await this.tx.run(repoRoot, {
|
|
@@ -6855,13 +6881,15 @@ var ServiceIdentityManager = class {
|
|
|
6855
6881
|
const oldRecipient = identity.environments[envName].recipient;
|
|
6856
6882
|
const newPublicKey = newPublicKeys[envName];
|
|
6857
6883
|
envs[envName] = { recipient: newPublicKey };
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6884
|
+
if (!identity.pack_only) {
|
|
6885
|
+
const scopedCells = cells.filter((c) => c.environment === envName);
|
|
6886
|
+
for (const cell of scopedCells) {
|
|
6887
|
+
try {
|
|
6888
|
+
await this.encryption.removeRecipient(cell.filePath, oldRecipient);
|
|
6889
|
+
} catch {
|
|
6890
|
+
}
|
|
6891
|
+
await this.encryption.addRecipient(cell.filePath, newPublicKey);
|
|
6863
6892
|
}
|
|
6864
|
-
await this.encryption.addRecipient(cell.filePath, newPublicKey);
|
|
6865
6893
|
}
|
|
6866
6894
|
}
|
|
6867
6895
|
writeManifestYaml(repoRoot, doc);
|
|
@@ -6901,6 +6929,17 @@ var ServiceIdentityManager = class {
|
|
|
6901
6929
|
});
|
|
6902
6930
|
}
|
|
6903
6931
|
}
|
|
6932
|
+
if (si.pack_only) {
|
|
6933
|
+
const ageRecipients = Object.values(si.environments).filter((cfg) => !isKmsEnvelope(cfg) && cfg.recipient).map((cfg) => cfg.recipient);
|
|
6934
|
+
if (ageRecipients.length >= 2 && new Set(ageRecipients).size === 1) {
|
|
6935
|
+
issues.push({
|
|
6936
|
+
identity: si.name,
|
|
6937
|
+
type: "runtime_shared_recipient",
|
|
6938
|
+
message: `Runtime identity '${si.name}' uses a shared recipient across all environments. A compromised key in any environment decrypts artifacts for all environments. Consider per-environment keys for runtime workloads.`
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
continue;
|
|
6942
|
+
}
|
|
6904
6943
|
for (const cell of cells) {
|
|
6905
6944
|
const envConfig = si.environments[cell.environment];
|
|
6906
6945
|
if (!envConfig) continue;
|