@tinycloud/cli 0.7.0-beta.5 → 0.7.0-beta.6
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.js +152 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10990,6 +10990,7 @@ async function loadManifestPermissions(source, profile) {
|
|
|
10990
10990
|
)
|
|
10991
10991
|
};
|
|
10992
10992
|
});
|
|
10993
|
+
permissions.push(...await secretPermissionsFromAppManifest(manifest, profile));
|
|
10993
10994
|
return resolvePermissionSpaces(permissions, profile);
|
|
10994
10995
|
}
|
|
10995
10996
|
throw new CLIError(
|
|
@@ -10998,6 +10999,46 @@ async function loadManifestPermissions(source, profile) {
|
|
|
10998
10999
|
ExitCode.USAGE_ERROR
|
|
10999
11000
|
);
|
|
11000
11001
|
}
|
|
11002
|
+
async function secretPermissionsFromAppManifest(manifest, profile) {
|
|
11003
|
+
if (manifest.secrets === void 0) {
|
|
11004
|
+
return [];
|
|
11005
|
+
}
|
|
11006
|
+
const resolved = resolveManifest({
|
|
11007
|
+
app_id: String(manifest.app_id),
|
|
11008
|
+
name: typeof manifest.name === "string" ? manifest.name : String(manifest.app_id),
|
|
11009
|
+
defaults: false,
|
|
11010
|
+
prefix: "",
|
|
11011
|
+
secrets: manifest.secrets
|
|
11012
|
+
});
|
|
11013
|
+
const permissions = resolved.resources.filter(
|
|
11014
|
+
(resource) => resource.service === "tinycloud.kv" && resource.space === "secrets" && resource.path.startsWith("vault/secrets/")
|
|
11015
|
+
);
|
|
11016
|
+
const needsDecrypt = permissions.some(
|
|
11017
|
+
(permission) => permission.actions.includes("tinycloud.kv/get")
|
|
11018
|
+
);
|
|
11019
|
+
if (needsDecrypt) {
|
|
11020
|
+
permissions.push({
|
|
11021
|
+
service: ENCRYPTION_PERMISSION_SERVICE,
|
|
11022
|
+
space: ENCRYPTION_MANIFEST_SPACE,
|
|
11023
|
+
path: await defaultSecretsNetworkId(profile),
|
|
11024
|
+
actions: ["tinycloud.encryption/decrypt"],
|
|
11025
|
+
skipPrefix: true
|
|
11026
|
+
});
|
|
11027
|
+
}
|
|
11028
|
+
return permissions;
|
|
11029
|
+
}
|
|
11030
|
+
async function defaultSecretsNetworkId(profileName) {
|
|
11031
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
11032
|
+
const ownerDid = (profile.ownerDid ?? profile.did)?.split("#")[0];
|
|
11033
|
+
if (!ownerDid) {
|
|
11034
|
+
throw new CLIError(
|
|
11035
|
+
"OWNER_DID_UNKNOWN",
|
|
11036
|
+
`Cannot determine owner DID for profile "${profileName}". Run \`tc auth login\` first.`,
|
|
11037
|
+
ExitCode.AUTH_REQUIRED
|
|
11038
|
+
);
|
|
11039
|
+
}
|
|
11040
|
+
return `urn:tinycloud:encryption:${ownerDid}:default`;
|
|
11041
|
+
}
|
|
11001
11042
|
function permissionsFromDelegation(delegation) {
|
|
11002
11043
|
if (delegation.resources?.length) {
|
|
11003
11044
|
return delegation.resources.map((resource) => ({
|
|
@@ -13877,6 +13918,30 @@ function secretPermissionEntries(params) {
|
|
|
13877
13918
|
}
|
|
13878
13919
|
return permissions;
|
|
13879
13920
|
}
|
|
13921
|
+
function formatSecretScopeFlag(options) {
|
|
13922
|
+
return options?.scope ? ` --scope ${JSON.stringify(options.scope)}` : "";
|
|
13923
|
+
}
|
|
13924
|
+
function outputSecretDoctor(result) {
|
|
13925
|
+
if (shouldOutputJson()) {
|
|
13926
|
+
outputJson(result);
|
|
13927
|
+
return;
|
|
13928
|
+
}
|
|
13929
|
+
process.stderr.write(formatSection("Secrets") + "\n");
|
|
13930
|
+
for (const check of result.checks) {
|
|
13931
|
+
process.stdout.write(formatCheck(check.ok, check.name, check.detail) + "\n");
|
|
13932
|
+
if (check.hint) {
|
|
13933
|
+
process.stdout.write(` ${theme.hint(check.hint)}
|
|
13934
|
+
`);
|
|
13935
|
+
}
|
|
13936
|
+
}
|
|
13937
|
+
process.stdout.write("\n");
|
|
13938
|
+
if (result.healthy) {
|
|
13939
|
+
process.stdout.write(theme.success("Secrets checks passed.") + "\n");
|
|
13940
|
+
} else {
|
|
13941
|
+
const failed = result.checks.filter((check) => check.ok === false).length;
|
|
13942
|
+
process.stdout.write(theme.warn(`${failed} secrets check${failed > 1 ? "s" : ""} need attention.`) + "\n");
|
|
13943
|
+
}
|
|
13944
|
+
}
|
|
13880
13945
|
function registerSecretsCommand(program2) {
|
|
13881
13946
|
const secrets = program2.command("secrets").description("Encrypted secrets management");
|
|
13882
13947
|
const network = secrets.command("network").description("Manage the default secrets encryption network");
|
|
@@ -13918,6 +13983,93 @@ function registerSecretsCommand(program2) {
|
|
|
13918
13983
|
handleError(error);
|
|
13919
13984
|
}
|
|
13920
13985
|
});
|
|
13986
|
+
secrets.command("doctor [name]").description("Check secrets setup and optional secret access").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--network <name>", "Encryption network name", "default").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
13987
|
+
try {
|
|
13988
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
13989
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
13990
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
13991
|
+
const networkName = options.network ?? "default";
|
|
13992
|
+
const networkId = networkName.startsWith("urn:tinycloud:encryption:") ? networkName : node.getDefaultEncryptionNetworkId(networkName);
|
|
13993
|
+
const descriptor = await withSpinner(
|
|
13994
|
+
"Checking secrets encryption network...",
|
|
13995
|
+
() => node.getEncryptionNetwork(networkName)
|
|
13996
|
+
);
|
|
13997
|
+
const checks = [
|
|
13998
|
+
descriptor ? {
|
|
13999
|
+
name: "Encryption network",
|
|
14000
|
+
ok: descriptor.state === "active" ? true : "warn",
|
|
14001
|
+
detail: `${networkName} (${descriptor.state})`
|
|
14002
|
+
} : {
|
|
14003
|
+
name: "Encryption network",
|
|
14004
|
+
ok: false,
|
|
14005
|
+
detail: `${networkName} not found`,
|
|
14006
|
+
hint: `tc secrets network init ${networkName}`
|
|
14007
|
+
}
|
|
14008
|
+
];
|
|
14009
|
+
let secret;
|
|
14010
|
+
if (name) {
|
|
14011
|
+
const scopeOptions = resolveSecretScope(options);
|
|
14012
|
+
const resolved = resolveSecretPath2(name, scopeOptions);
|
|
14013
|
+
const result = await runSecretOperation({
|
|
14014
|
+
ctx,
|
|
14015
|
+
node,
|
|
14016
|
+
action: "get",
|
|
14017
|
+
name,
|
|
14018
|
+
scopeOptions,
|
|
14019
|
+
label: `Checking secret ${name}...`,
|
|
14020
|
+
operation: () => node.secrets.get(name, scopeOptions)
|
|
14021
|
+
});
|
|
14022
|
+
if (result.ok) {
|
|
14023
|
+
secret = {
|
|
14024
|
+
name: resolved.name,
|
|
14025
|
+
path: resolved.permissionPaths.vault,
|
|
14026
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
14027
|
+
exists: true,
|
|
14028
|
+
readable: true
|
|
14029
|
+
};
|
|
14030
|
+
checks.push({
|
|
14031
|
+
name: "Secret access",
|
|
14032
|
+
ok: true,
|
|
14033
|
+
detail: `${resolved.permissionPaths.vault} readable`
|
|
14034
|
+
});
|
|
14035
|
+
} else {
|
|
14036
|
+
const notFound = result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND";
|
|
14037
|
+
secret = {
|
|
14038
|
+
name: resolved.name,
|
|
14039
|
+
path: resolved.permissionPaths.vault,
|
|
14040
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
14041
|
+
exists: !notFound,
|
|
14042
|
+
readable: false
|
|
14043
|
+
};
|
|
14044
|
+
checks.push({
|
|
14045
|
+
name: "Secret access",
|
|
14046
|
+
ok: false,
|
|
14047
|
+
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
14048
|
+
hint: notFound ? `tc secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} <value>` : "Ask the owner profile to grant tinycloud.kv/get and tinycloud.encryption/decrypt."
|
|
14049
|
+
});
|
|
14050
|
+
}
|
|
14051
|
+
} else {
|
|
14052
|
+
checks.push({
|
|
14053
|
+
name: "Secret access",
|
|
14054
|
+
ok: "warn",
|
|
14055
|
+
detail: "skipped; pass a secret name to verify read access"
|
|
14056
|
+
});
|
|
14057
|
+
}
|
|
14058
|
+
outputSecretDoctor({
|
|
14059
|
+
healthy: checks.every((check) => check.ok !== false),
|
|
14060
|
+
network: {
|
|
14061
|
+
name: networkName,
|
|
14062
|
+
networkId,
|
|
14063
|
+
exists: descriptor !== null,
|
|
14064
|
+
...descriptor?.state ? { state: descriptor.state } : {}
|
|
14065
|
+
},
|
|
14066
|
+
...secret ? { secret } : {},
|
|
14067
|
+
checks
|
|
14068
|
+
});
|
|
14069
|
+
} catch (error) {
|
|
14070
|
+
handleError(error);
|
|
14071
|
+
}
|
|
14072
|
+
});
|
|
13921
14073
|
secrets.command("list").description("List secrets").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
|
|
13922
14074
|
try {
|
|
13923
14075
|
const globalOpts = cmd.optsWithGlobals();
|