@seekrit/cli 0.14.0 → 0.15.0
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 +243 -7
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -12,7 +12,8 @@ z.enum([
|
|
|
12
12
|
"mysql",
|
|
13
13
|
"ssh",
|
|
14
14
|
"redis",
|
|
15
|
-
"aws"
|
|
15
|
+
"aws",
|
|
16
|
+
"gcp"
|
|
16
17
|
]);
|
|
17
18
|
const executorModeSchema = z.enum(["in_do", "remote"]);
|
|
18
19
|
/**
|
|
@@ -83,6 +84,22 @@ const awsRegionSchema = z.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/, "must be a
|
|
|
83
84
|
const awsExternalIdSchema = z.string().regex(/^[\w+=,.@:/-]{2,1224}$/, "must be a valid STS external id");
|
|
84
85
|
z.string().regex(/^[\w+=,.@-]{2,64}$/, "must be 2–64 chars of [A-Za-z0-9_+=,.@-]");
|
|
85
86
|
/**
|
|
87
|
+
* A GCP service-account email the broker is allowed to impersonate (or that
|
|
88
|
+
* appears in a delegation chain). Structurally validated and bounded: it is
|
|
89
|
+
* interpolated into the IAM Credentials API URL path, so the charset excludes
|
|
90
|
+
* anything that could break out of a path segment. Covers user-managed
|
|
91
|
+
* (`name@<project>.iam.gserviceaccount.com`) and Google-managed
|
|
92
|
+
* (`<project-number>-compute@developer.gserviceaccount.com`) forms.
|
|
93
|
+
*/
|
|
94
|
+
const gcpServiceAccountEmailSchema = z.string().max(256).regex(/^[a-z0-9-]+@[a-z0-9.-]+\.gserviceaccount\.com$/, "must be a service-account email (…@….gserviceaccount.com)");
|
|
95
|
+
/**
|
|
96
|
+
* An OAuth 2.0 scope granted to the minted access token, e.g.
|
|
97
|
+
* `https://www.googleapis.com/auth/cloud-platform`. Bounded and whitespace-free
|
|
98
|
+
* (scopes are space-delimited); passed to the IAM Credentials API in a JSON body
|
|
99
|
+
* array, not a URL, so this is sanity/DoS hardening rather than an injection gate.
|
|
100
|
+
*/
|
|
101
|
+
const gcpOauthScopeSchema = z.string().min(1).max(256).regex(/^\S+$/, "must be a single OAuth scope with no whitespace");
|
|
102
|
+
/**
|
|
86
103
|
* The consumer's ephemeral P-256 public key (JWK-serialized) that a tier-2
|
|
87
104
|
* credential is wrapped to before it is returned. Validated structurally here;
|
|
88
105
|
* the executor imports it defensively before wrapping. Bounded so a giant blob
|
|
@@ -184,12 +201,22 @@ const awsTargetConfigSchema = z.object({
|
|
|
184
201
|
sessionPolicy: z.string().min(1).max(4e3).optional(),
|
|
185
202
|
maxTtlSeconds: z.number().int().min(900).max(AWS_MAX_TTL_SECONDS).optional()
|
|
186
203
|
});
|
|
204
|
+
const GCP_MAX_TTL_SECONDS = 3600 * 12;
|
|
205
|
+
const gcpTargetConfigSchema = z.object({
|
|
206
|
+
provider: z.literal("gcp"),
|
|
207
|
+
executor: z.literal("in_do"),
|
|
208
|
+
serviceAccount: gcpServiceAccountEmailSchema,
|
|
209
|
+
scopes: z.array(gcpOauthScopeSchema).min(1).max(32).optional(),
|
|
210
|
+
delegates: z.array(gcpServiceAccountEmailSchema).max(8).optional(),
|
|
211
|
+
maxTtlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS).optional()
|
|
212
|
+
});
|
|
187
213
|
const leaseTargetConfigSchema = z.discriminatedUnion("provider", [
|
|
188
214
|
postgresTargetConfigSchema,
|
|
189
215
|
mysqlTargetConfigSchema,
|
|
190
216
|
redisTargetConfigSchema,
|
|
191
217
|
sshTargetConfigSchema,
|
|
192
|
-
awsTargetConfigSchema
|
|
218
|
+
awsTargetConfigSchema,
|
|
219
|
+
gcpTargetConfigSchema
|
|
193
220
|
]);
|
|
194
221
|
z.object({
|
|
195
222
|
name: z.string().trim().min(1).max(128),
|
|
@@ -266,12 +293,29 @@ const mintAwsLeaseSchema = z.object({
|
|
|
266
293
|
recipientPublicKey: p256PublicKeyJwkSchema,
|
|
267
294
|
ttlSeconds: z.number().int().min(900).max(AWS_MAX_TTL_SECONDS)
|
|
268
295
|
});
|
|
296
|
+
/**
|
|
297
|
+
* Client → API: mint a GCP lease. Like AWS (tier 2): the client generates an
|
|
298
|
+
* ephemeral P-256 keypair locally and sends only the public key; the IAM
|
|
299
|
+
* Credentials API mints the access token and the broker returns it wrapped to
|
|
300
|
+
* that key. The private key never leaves the requesting machine, so the plaintext
|
|
301
|
+
* token is only decryptable there.
|
|
302
|
+
*
|
|
303
|
+
* TTL bounds are GCP's `generateAccessToken` limits (1 min – 12 h); tokens over
|
|
304
|
+
* 1 h require the credential-lifetime-extension org policy.
|
|
305
|
+
*/
|
|
306
|
+
const mintGcpLeaseSchema = z.object({
|
|
307
|
+
provider: z.literal("gcp"),
|
|
308
|
+
targetId: z.string().min(1),
|
|
309
|
+
recipientPublicKey: p256PublicKeyJwkSchema,
|
|
310
|
+
ttlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS)
|
|
311
|
+
});
|
|
269
312
|
z.discriminatedUnion("provider", [
|
|
270
313
|
mintPostgresLeaseSchema,
|
|
271
314
|
mintMysqlLeaseSchema,
|
|
272
315
|
mintRedisLeaseSchema,
|
|
273
316
|
mintSshLeaseSchema,
|
|
274
|
-
mintAwsLeaseSchema
|
|
317
|
+
mintAwsLeaseSchema,
|
|
318
|
+
mintGcpLeaseSchema
|
|
275
319
|
]);
|
|
276
320
|
//#endregion
|
|
277
321
|
//#region ../../packages/core/src/providers/aws.ts
|
|
@@ -302,6 +346,29 @@ function awsTrustPolicyInstructions(config) {
|
|
|
302
346
|
].join("\n");
|
|
303
347
|
}
|
|
304
348
|
//#endregion
|
|
349
|
+
//#region ../../packages/core/src/providers/gcp.ts
|
|
350
|
+
/**
|
|
351
|
+
* The one-time IAM setup an admin performs so seekrit can impersonate the target
|
|
352
|
+
* service account. Analogue of `awsTrustPolicyInstructions` — printed for the
|
|
353
|
+
* admin to apply, never executed by seekrit. Grants the *source* service account
|
|
354
|
+
* (the one whose key was registered as the admin secret) the token-creator role
|
|
355
|
+
* on the target service account.
|
|
356
|
+
*/
|
|
357
|
+
function gcpSetupInstructions(config) {
|
|
358
|
+
return [
|
|
359
|
+
"# Grant the service account whose key you registered as the admin secret",
|
|
360
|
+
"# permission to mint tokens for the target service account. Replace",
|
|
361
|
+
"# <SOURCE_SA_EMAIL> with the client_email from that key JSON.",
|
|
362
|
+
`gcloud iam service-accounts add-iam-policy-binding ${config.serviceAccount} \\`,
|
|
363
|
+
" --member=\"serviceAccount:<SOURCE_SA_EMAIL>\" \\",
|
|
364
|
+
" --role=\"roles/iam.serviceAccountTokenCreator\"",
|
|
365
|
+
"",
|
|
366
|
+
"# The source service account's project must have the IAM Service Account",
|
|
367
|
+
"# Credentials API enabled (seekrit only ever calls generateAccessToken):",
|
|
368
|
+
"gcloud services enable iamcredentials.googleapis.com"
|
|
369
|
+
].join("\n");
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
305
372
|
//#region ../../packages/core/src/providers/postgres.ts
|
|
306
373
|
/**
|
|
307
374
|
* The one-time setup SQL an admin runs to create the shared group role that a
|
|
@@ -690,6 +757,33 @@ async function unwrapAwsCredential(wrapped, privateKeyJwk) {
|
|
|
690
757
|
};
|
|
691
758
|
}
|
|
692
759
|
//#endregion
|
|
760
|
+
//#region ../../packages/crypto/src/gcp.ts
|
|
761
|
+
/** Generate the ephemeral P-256 keypair a client uses to receive one GCP lease. */
|
|
762
|
+
async function generateGcpRecipientKeyPair() {
|
|
763
|
+
return generateKeyPair();
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Unwrap the `wd1.` blob returned by a GCP lease mint using the ephemeral
|
|
767
|
+
* private key generated for that lease, yielding the plaintext access token.
|
|
768
|
+
*/
|
|
769
|
+
async function unwrapGcpCredential(wrapped, privateKeyJwk) {
|
|
770
|
+
const bytes = await unwrapDek(wrapped, await importPrivateKey(privateKeyJwk));
|
|
771
|
+
let parsed;
|
|
772
|
+
try {
|
|
773
|
+
parsed = JSON.parse(utf8Decode(bytes));
|
|
774
|
+
} catch {
|
|
775
|
+
throw new SeekritCryptoError("DECRYPT_FAILED", "unwrapped GCP credential was not valid JSON");
|
|
776
|
+
}
|
|
777
|
+
const c = parsed;
|
|
778
|
+
if (typeof c.accessToken !== "string" || typeof c.expiration !== "string" || typeof c.serviceAccount !== "string" || !Array.isArray(c.scopes) || !c.scopes.every((s) => typeof s === "string")) throw new SeekritCryptoError("DECRYPT_FAILED", "unwrapped GCP credential is missing fields");
|
|
779
|
+
return {
|
|
780
|
+
accessToken: c.accessToken,
|
|
781
|
+
expiration: c.expiration,
|
|
782
|
+
serviceAccount: c.serviceAccount,
|
|
783
|
+
scopes: c.scopes
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
//#endregion
|
|
693
787
|
//#region ../../packages/crypto/src/mysql.ts
|
|
694
788
|
/**
|
|
695
789
|
* Client-side construction of a MySQL/MariaDB `mysql_native_password`
|
|
@@ -1134,7 +1228,7 @@ function isServiceToken(value) {
|
|
|
1134
1228
|
}
|
|
1135
1229
|
//#endregion
|
|
1136
1230
|
//#region package.json
|
|
1137
|
-
var version = "0.
|
|
1231
|
+
var version = "0.15.0";
|
|
1138
1232
|
//#endregion
|
|
1139
1233
|
//#region ../../packages/api-client/src/index.ts
|
|
1140
1234
|
var SeekritApiError = class extends Error {
|
|
@@ -1583,7 +1677,7 @@ async function resolveGroup(ctx, opts) {
|
|
|
1583
1677
|
* `sts:AssumeRole` on the target role.
|
|
1584
1678
|
*/
|
|
1585
1679
|
/** Parse a duration like `30m`, `1h`, or a bare seconds count. */
|
|
1586
|
-
function parseTtlSeconds$
|
|
1680
|
+
function parseTtlSeconds$5(input) {
|
|
1587
1681
|
const m = /^(\d+)\s*([smhd]?)$/.exec(input.trim());
|
|
1588
1682
|
if (!m) fail(`invalid --ttl "${input}" (try 30m, 1h, 12h)`);
|
|
1589
1683
|
return Number(m[1]) * ({
|
|
@@ -1623,7 +1717,7 @@ function registerAwsCommands(program) {
|
|
|
1623
1717
|
region: options.region,
|
|
1624
1718
|
...options.externalId ? { externalId: options.externalId } : {},
|
|
1625
1719
|
...sessionPolicy ? { sessionPolicy } : {},
|
|
1626
|
-
...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$
|
|
1720
|
+
...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$5(options.maxTtl) } : {}
|
|
1627
1721
|
};
|
|
1628
1722
|
const baseCredential = resolveBaseCredential(options);
|
|
1629
1723
|
const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
|
|
@@ -1671,7 +1765,7 @@ function registerAwsCommands(program) {
|
|
|
1671
1765
|
if (!t) fail(`no target "${targetRef}" in ${org.slug}`);
|
|
1672
1766
|
const cfg = t.config;
|
|
1673
1767
|
if (cfg.provider !== "aws") fail(`"${t.name}" is not an aws target (see \`seekrit pg\`)`);
|
|
1674
|
-
const ttlSeconds = parseTtlSeconds$
|
|
1768
|
+
const ttlSeconds = parseTtlSeconds$5(options.ttl);
|
|
1675
1769
|
if (ttlSeconds < 900) fail(`--ttl must be at least ${900 / 60}m (STS minimum)`);
|
|
1676
1770
|
const recipient = await generateAwsRecipientKeyPair();
|
|
1677
1771
|
const { aws: leased } = await ctx.client.mintLease(org.id, {
|
|
@@ -1761,6 +1855,147 @@ function formatSecrets(values, format) {
|
|
|
1761
1855
|
}
|
|
1762
1856
|
}
|
|
1763
1857
|
//#endregion
|
|
1858
|
+
//#region src/gcp.ts
|
|
1859
|
+
/**
|
|
1860
|
+
* `seekrit gcp` — temporary GCP credentials via IAM Credentials
|
|
1861
|
+
* `generateAccessToken` (Vault-style dynamic secrets, the tier-2 sibling of
|
|
1862
|
+
* `seekrit aws`).
|
|
1863
|
+
*
|
|
1864
|
+
* Zero-knowledge for the leased credential: minting generates an ephemeral P-256
|
|
1865
|
+
* keypair on THIS machine and sends only the public key; GCP mints the token and
|
|
1866
|
+
* the broker returns it wrapped to that key, so the control plane only ever
|
|
1867
|
+
* relays ciphertext and only this machine can unwrap it. Registering a target
|
|
1868
|
+
* wraps the service-account key JSON to the broker's public key locally, so the
|
|
1869
|
+
* control plane never sees it either — the source service account needs only
|
|
1870
|
+
* `roles/iam.serviceAccountTokenCreator` on the target.
|
|
1871
|
+
*/
|
|
1872
|
+
/** Parse a duration like `30m`, `1h`, or a bare seconds count. */
|
|
1873
|
+
function parseTtlSeconds$4(input) {
|
|
1874
|
+
const m = /^(\d+)\s*([smhd]?)$/.exec(input.trim());
|
|
1875
|
+
if (!m) fail(`invalid --ttl "${input}" (try 30m, 1h, 12h)`);
|
|
1876
|
+
return Number(m[1]) * ({
|
|
1877
|
+
s: 1,
|
|
1878
|
+
m: 60,
|
|
1879
|
+
h: 3600,
|
|
1880
|
+
d: 86400
|
|
1881
|
+
}[m[2] || "s"] ?? 1);
|
|
1882
|
+
}
|
|
1883
|
+
/** Collect a repeatable flag (e.g. --scope) into a list. */
|
|
1884
|
+
function collectList$1(value, acc = []) {
|
|
1885
|
+
acc.push(value);
|
|
1886
|
+
return acc;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* The service-account key JSON the broker impersonates with. From --key-file or
|
|
1890
|
+
* GOOGLE_APPLICATION_CREDENTIALS. Never leaves this machine unwrapped — it is
|
|
1891
|
+
* wrapped to the broker key before upload.
|
|
1892
|
+
*/
|
|
1893
|
+
function resolveServiceAccountKey(opts) {
|
|
1894
|
+
const path = opts.keyFile ?? process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
1895
|
+
if (!path) fail("provide the source service-account key JSON via --key-file or GOOGLE_APPLICATION_CREDENTIALS (it needs roles/iam.serviceAccountTokenCreator on the target)");
|
|
1896
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
1897
|
+
try {
|
|
1898
|
+
const parsed = JSON.parse(raw);
|
|
1899
|
+
if (typeof parsed.client_email !== "string" || typeof parsed.private_key !== "string") fail(`${path} is not a service-account key JSON (missing client_email/private_key)`);
|
|
1900
|
+
} catch {
|
|
1901
|
+
fail(`${path} is not valid JSON`);
|
|
1902
|
+
}
|
|
1903
|
+
return raw;
|
|
1904
|
+
}
|
|
1905
|
+
function registerGcpCommands(program) {
|
|
1906
|
+
const gcp = program.command("gcp").description("temporary GCP credentials (IAM generateAccessToken, zero-knowledge)");
|
|
1907
|
+
const target = gcp.command("target").description("manage GCP service-account targets");
|
|
1908
|
+
target.command("add").description("register an impersonable service account to issue temporary tokens from").requiredOption("--name <name>", "display name, e.g. prod-deploy").requiredOption("--service-account <email>", "the service account to impersonate, name@project.iam.gserviceaccount.com").option("--org <slug>").option("--scope <scope>", "OAuth scope to grant (repeatable; default cloud-platform)", collectList$1).option("--delegate <email>", "delegation-chain service account (repeatable)", collectList$1).option("--max-ttl <duration>", "clamp requested token lifetime, e.g. 1h").option("--key-file <path>", "source SA key JSON (else GOOGLE_APPLICATION_CREDENTIALS)").action(async (options) => {
|
|
1909
|
+
const ctx = buildContext();
|
|
1910
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1911
|
+
const config = {
|
|
1912
|
+
provider: "gcp",
|
|
1913
|
+
executor: "in_do",
|
|
1914
|
+
serviceAccount: options.serviceAccount,
|
|
1915
|
+
...options.scope?.length ? { scopes: options.scope } : {},
|
|
1916
|
+
...options.delegate?.length ? { delegates: options.delegate } : {},
|
|
1917
|
+
...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$4(options.maxTtl) } : {}
|
|
1918
|
+
};
|
|
1919
|
+
const keyJson = resolveServiceAccountKey(options);
|
|
1920
|
+
const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
|
|
1921
|
+
const wrappedAdminSecret = await wrapDek(new TextEncoder().encode(keyJson), publicKeyJwk);
|
|
1922
|
+
const { target: created } = await ctx.client.registerLeaseTarget(org.id, {
|
|
1923
|
+
name: options.name,
|
|
1924
|
+
config,
|
|
1925
|
+
wrappedAdminSecret
|
|
1926
|
+
});
|
|
1927
|
+
console.error(`registered GCP target ${created.name} (${created.id})`);
|
|
1928
|
+
console.error("\nGrant the source SA the token-creator role, then `seekrit gcp lease`:\n");
|
|
1929
|
+
console.log(gcpSetupInstructions(config));
|
|
1930
|
+
});
|
|
1931
|
+
target.command("list").description("list GCP service-account targets").option("--org <slug>").action(async (options) => {
|
|
1932
|
+
const ctx = buildContext();
|
|
1933
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1934
|
+
const { targets } = await ctx.client.listLeaseTargets(org.id);
|
|
1935
|
+
for (const t of targets) {
|
|
1936
|
+
const cfg = t.config;
|
|
1937
|
+
if (cfg.provider !== "gcp") continue;
|
|
1938
|
+
console.log(`${t.id}\t${t.name}\t${cfg.serviceAccount}`);
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
target.command("setup <targetId>").description("reprint the IAM setup for a GCP target").option("--org <slug>").action(async (targetId, options) => {
|
|
1942
|
+
const ctx = buildContext();
|
|
1943
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1944
|
+
const { targets } = await ctx.client.listLeaseTargets(org.id);
|
|
1945
|
+
const t = targets.find((x) => x.id === targetId || x.name === targetId);
|
|
1946
|
+
if (!t) fail(`no target "${targetId}" in ${org.slug}`);
|
|
1947
|
+
const cfg = t.config;
|
|
1948
|
+
if (cfg.provider !== "gcp") fail("not a gcp target (see `seekrit aws`/`seekrit ssh`)");
|
|
1949
|
+
console.log(gcpSetupInstructions(cfg));
|
|
1950
|
+
});
|
|
1951
|
+
target.command("rm <targetId>").description("delete a GCP service-account target").option("--org <slug>").action(async (targetId, options) => {
|
|
1952
|
+
const ctx = buildContext();
|
|
1953
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1954
|
+
await ctx.client.deleteLeaseTarget(org.id, targetId);
|
|
1955
|
+
console.error(`deleted ${targetId}`);
|
|
1956
|
+
});
|
|
1957
|
+
gcp.command("lease <target>").description("mint a short-lived GCP access token; prints ready-to-source export lines").option("--org <slug>").option("--ttl <duration>", "token lifetime, e.g. 15m, 1h", "1h").option("--json", "print the full credential as JSON").action(async (targetRef, options) => {
|
|
1958
|
+
const ctx = buildContext();
|
|
1959
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1960
|
+
const { targets } = await ctx.client.listLeaseTargets(org.id);
|
|
1961
|
+
const t = targets.find((x) => x.id === targetRef || x.name === targetRef);
|
|
1962
|
+
if (!t) fail(`no target "${targetRef}" in ${org.slug}`);
|
|
1963
|
+
if (t.config.provider !== "gcp") fail(`"${t.name}" is not a gcp target (see \`seekrit aws\`)`);
|
|
1964
|
+
const ttlSeconds = parseTtlSeconds$4(options.ttl);
|
|
1965
|
+
if (ttlSeconds < 60) fail(`--ttl must be at least 60s`);
|
|
1966
|
+
if (ttlSeconds > 43200) fail(`--ttl must be at most ${GCP_MAX_TTL_SECONDS / 3600}h`);
|
|
1967
|
+
const recipient = await generateGcpRecipientKeyPair();
|
|
1968
|
+
const { gcp: leased } = await ctx.client.mintLease(org.id, {
|
|
1969
|
+
provider: "gcp",
|
|
1970
|
+
targetId: t.id,
|
|
1971
|
+
recipientPublicKey: recipient.publicKeyJwk,
|
|
1972
|
+
ttlSeconds
|
|
1973
|
+
});
|
|
1974
|
+
const cred = await unwrapGcpCredential(leased.wrappedCredential, recipient.privateKeyJwk);
|
|
1975
|
+
console.error(`leased ${cred.serviceAccount} — expires ${cred.expiration}`);
|
|
1976
|
+
if (options.json) console.log(JSON.stringify(cred, null, 2));
|
|
1977
|
+
else {
|
|
1978
|
+
console.log(`export CLOUDSDK_AUTH_ACCESS_TOKEN=${cred.accessToken}`);
|
|
1979
|
+
console.log(`export GOOGLE_OAUTH_ACCESS_TOKEN=${cred.accessToken}`);
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
gcp.command("leases").description("list GCP leases (the ledger — never secret material)").option("--org <slug>").action(async (options) => {
|
|
1983
|
+
const ctx = buildContext();
|
|
1984
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1985
|
+
const { leases } = await ctx.client.listLeases(org.id);
|
|
1986
|
+
for (const l of leases) {
|
|
1987
|
+
if (l.provider !== "gcp") continue;
|
|
1988
|
+
console.log(`${l.id}\t${l.status}\texpires ${l.expiresAt}`);
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
gcp.command("revoke <leaseId>").description("mark a lease revoked in the ledger (tokens stay valid until they expire)").option("--org <slug>").action(async (leaseId, options) => {
|
|
1992
|
+
const ctx = buildContext();
|
|
1993
|
+
const org = await resolveOrg(ctx, options.org);
|
|
1994
|
+
await ctx.client.revokeLease(org.id, leaseId);
|
|
1995
|
+
console.error(`revoked ${leaseId} (issued tokens remain valid until they expire)`);
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
//#endregion
|
|
1764
1999
|
//#region src/provisioner.ts
|
|
1765
2000
|
/**
|
|
1766
2001
|
* `seekrit provisioner` — helpers for the self-hosted **remote executor**
|
|
@@ -2851,6 +3086,7 @@ registerRedisCommands(program);
|
|
|
2851
3086
|
registerProvisionerCommands(program);
|
|
2852
3087
|
registerSshCommands(program);
|
|
2853
3088
|
registerAwsCommands(program);
|
|
3089
|
+
registerGcpCommands(program);
|
|
2854
3090
|
program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
|
|
2855
3091
|
const { runMcpServer } = await import("./mcp-ARBuneH3.js");
|
|
2856
3092
|
await runMcpServer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^26.1.0",
|
|
25
25
|
"tsdown": "^0.22.3",
|
|
26
|
-
"@seekrit/api-client": "0.0.1",
|
|
27
26
|
"@seekrit/core": "0.0.1",
|
|
28
|
-
"@seekrit/crypto": "0.0.1"
|
|
27
|
+
"@seekrit/crypto": "0.0.1",
|
|
28
|
+
"@seekrit/api-client": "0.0.1"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsdown",
|