@seekrit/cli 0.9.0 → 0.11.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/README.md +24 -2
- package/dist/index.js +77 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,28 @@ seekrit token revoke <tokenId>
|
|
|
86
86
|
|
|
87
87
|
See the [service tokens guide](https://seekrit.dev/docs/guides/service-tokens).
|
|
88
88
|
|
|
89
|
+
## Docker
|
|
90
|
+
|
|
91
|
+
The CLI also ships as a container image — the full toolchain on a Node runtime,
|
|
92
|
+
for CI runners and agent sandboxes. Multi-arch images (`linux/amd64` +
|
|
93
|
+
`linux/arm64`) are on Docker Hub as
|
|
94
|
+
[`seekritdev/cli`](https://hub.docker.com/r/seekritdev/cli):
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
docker run --rm \
|
|
98
|
+
-e SEEKRIT_TOKEN=skt_… \
|
|
99
|
+
-v "$PWD:/work" -w /work \
|
|
100
|
+
seekritdev/cli run -- ./deploy.sh
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The entrypoint is `seekrit`, so pass subcommands directly (e.g.
|
|
104
|
+
`docker run seekritdev/cli secrets list`). It runs unprivileged (`node`) and
|
|
105
|
+
writes config under `$HOME/.config`. Tags: `:latest`, `:<version>` /
|
|
106
|
+
`:<major>.<minor>`, and `:edge` (latest `main`).
|
|
107
|
+
|
|
108
|
+
For a Node-free runtime image (inject secrets and exec, nothing else), prefer
|
|
109
|
+
the [`seekrit-run`](../run) launcher — `seekritdev/run`.
|
|
110
|
+
|
|
89
111
|
## Configuration
|
|
90
112
|
|
|
91
113
|
Credentials and defaults are read from (highest precedence first) environment
|
|
@@ -121,8 +143,8 @@ See the root [README](../../README.md) for the full end-to-end local loop.
|
|
|
121
143
|
### Build
|
|
122
144
|
|
|
123
145
|
The CLI is bundled with [tsdown](https://tsdown.dev). Workspace packages
|
|
124
|
-
(`@seekrit/*`) are inlined so the published package's only runtime
|
|
125
|
-
`commander`.
|
|
146
|
+
(`@seekrit/*`) are inlined, so the published package's only runtime dependencies
|
|
147
|
+
are `commander`, `zod`, and `@modelcontextprotocol/sdk`.
|
|
126
148
|
|
|
127
149
|
```sh
|
|
128
150
|
pnpm build # bundle to dist/index.js
|
package/dist/index.js
CHANGED
|
@@ -586,7 +586,7 @@ async function unwrapDek(wrapped, privateKey) {
|
|
|
586
586
|
}
|
|
587
587
|
//#endregion
|
|
588
588
|
//#region package.json
|
|
589
|
-
var version = "0.
|
|
589
|
+
var version = "0.11.0";
|
|
590
590
|
const PROJECT_FILE = "seekrit.json";
|
|
591
591
|
function globalConfigPath() {
|
|
592
592
|
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "seekrit", "config.json");
|
|
@@ -814,6 +814,19 @@ var SeekritClient = class {
|
|
|
814
814
|
const qs = params.size > 0 ? `?${params}` : "";
|
|
815
815
|
return this.request("GET", `/v1/orgs/${orgId}/audit${qs}`);
|
|
816
816
|
}
|
|
817
|
+
getLogSink(orgId) {
|
|
818
|
+
return this.request("GET", `/v1/orgs/${orgId}/log-sink`);
|
|
819
|
+
}
|
|
820
|
+
setLogSink(orgId, input) {
|
|
821
|
+
return this.request("PUT", `/v1/orgs/${orgId}/log-sink`, input);
|
|
822
|
+
}
|
|
823
|
+
deleteLogSink(orgId) {
|
|
824
|
+
return this.request("DELETE", `/v1/orgs/${orgId}/log-sink`);
|
|
825
|
+
}
|
|
826
|
+
/** Send a synthetic record to the configured endpoint to verify connectivity. */
|
|
827
|
+
testLogSink(orgId) {
|
|
828
|
+
return this.request("POST", `/v1/orgs/${orgId}/log-sink/test`);
|
|
829
|
+
}
|
|
817
830
|
};
|
|
818
831
|
//#endregion
|
|
819
832
|
//#region src/io.ts
|
|
@@ -940,6 +953,49 @@ function formatSecrets(values, format) {
|
|
|
940
953
|
}
|
|
941
954
|
}
|
|
942
955
|
//#endregion
|
|
956
|
+
//#region src/provisioner.ts
|
|
957
|
+
/**
|
|
958
|
+
* `seekrit provisioner` — helpers for the self-hosted **remote executor**
|
|
959
|
+
* (`seekrit-provisioner`), the daemon that runs a target's provisioning SQL
|
|
960
|
+
* inside the customer's own network so the control plane never sees the database
|
|
961
|
+
* admin credential.
|
|
962
|
+
*
|
|
963
|
+
* The only stateful command is `keygen`: the shared HMAC key authenticates the
|
|
964
|
+
* signed commands the broker sends the daemon. The same base64 value is given to
|
|
965
|
+
* BOTH `--hmac-key` when registering a remote target AND the daemon's
|
|
966
|
+
* `SEEKRIT_PROVISIONER_HMAC_KEY`.
|
|
967
|
+
*/
|
|
968
|
+
function registerProvisionerCommands(program) {
|
|
969
|
+
program.command("provisioner").description("self-hosted remote executor (seekrit-provisioner) helpers").command("keygen").description("generate a shared HMAC key for a remote provisioning target").action(() => {
|
|
970
|
+
const key = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32));
|
|
971
|
+
console.log(toBase64(key));
|
|
972
|
+
console.error("Give this key to BOTH `--hmac-key` when registering a remote target AND the\ndaemon's SEEKRIT_PROVISIONER_HMAC_KEY. Keep it secret; it authenticates the\nbroker's commands to your provisioner.");
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Resolve the "admin secret" a lease target registration wraps to the broker.
|
|
977
|
+
* Its meaning depends on the executor:
|
|
978
|
+
*
|
|
979
|
+
* - **remote** — the shared HMAC key (base64). The real database admin
|
|
980
|
+
* credential is NOT sent to seekrit; it is configured on the daemon instead.
|
|
981
|
+
* Preferred source is `--hmac-key` / `SEEKRIT_PROVISIONER_HMAC_KEY`, with the
|
|
982
|
+
* older `--admin-url` / provider admin-url env kept as a fallback.
|
|
983
|
+
* - **in_do** — the database admin connection string, which the broker decrypts
|
|
984
|
+
* transiently to run the SQL itself.
|
|
985
|
+
*
|
|
986
|
+
* `fail()` never returns, so the result is always a non-empty string.
|
|
987
|
+
*/
|
|
988
|
+
function resolveLeaseAdminSecret(opts) {
|
|
989
|
+
if (opts.executor === "remote") {
|
|
990
|
+
const key = opts.hmacKey ?? process.env.SEEKRIT_PROVISIONER_HMAC_KEY ?? opts.adminUrl ?? process.env[opts.adminUrlEnv];
|
|
991
|
+
if (!key) fail("the remote executor needs the shared HMAC key — pass --hmac-key or set SEEKRIT_PROVISIONER_HMAC_KEY (mint one with `seekrit provisioner keygen`)");
|
|
992
|
+
return key.trim();
|
|
993
|
+
}
|
|
994
|
+
const adminUrl = opts.adminUrl ?? process.env[opts.adminUrlEnv];
|
|
995
|
+
if (!adminUrl) fail(`provide the admin connection string via --admin-url or ${opts.adminUrlEnv}`);
|
|
996
|
+
return adminUrl;
|
|
997
|
+
}
|
|
998
|
+
//#endregion
|
|
943
999
|
//#region src/target.ts
|
|
944
1000
|
/** Resolve the target org from a flag, the committed config, or a lone org. */
|
|
945
1001
|
async function resolveOrg(ctx, orgSlug) {
|
|
@@ -1061,7 +1117,7 @@ function generateUserName(prefix = "tmp") {
|
|
|
1061
1117
|
function registerMysqlCommands(program) {
|
|
1062
1118
|
const mysql = program.command("mysql").description("temporary MySQL/MariaDB credentials (short-lived, zero-knowledge)");
|
|
1063
1119
|
const target = mysql.command("target").description("manage provisioning targets");
|
|
1064
|
-
target.command("add").description("register a MySQL/MariaDB server to lease credentials from").requiredOption("--name <name>", "display name, e.g. prod-db").option("--org <slug>").requiredOption("--host <host>", "database host").option("--port <port>", "database port", "3306").requiredOption("--database <name>", "database to grant access to").option("--access <level>", "readonly | readwrite | custom", "readonly").option("--user-host <host>", "host part of created accounts ('name'@'<host>')", "%").option("--executor <mode>", "in_do | remote", "in_do").option("--provisioner-url <url>", "customer provisioner URL (remote executor)").option("--admin-url <url>", "admin mysql:// connection string (or set SEEKRIT_MYSQL_ADMIN_URL); wrapped locally").option("--create-statement <sql>", "custom CREATE template (repeatable)", collect$2, []).option("--revoke-statement <sql>", "custom REVOKE template (repeatable)", collect$2, []).action(async (options) => {
|
|
1120
|
+
target.command("add").description("register a MySQL/MariaDB server to lease credentials from").requiredOption("--name <name>", "display name, e.g. prod-db").option("--org <slug>").requiredOption("--host <host>", "database host").option("--port <port>", "database port", "3306").requiredOption("--database <name>", "database to grant access to").option("--access <level>", "readonly | readwrite | custom", "readonly").option("--user-host <host>", "host part of created accounts ('name'@'<host>')", "%").option("--executor <mode>", "in_do | remote", "in_do").option("--provisioner-url <url>", "customer provisioner URL (remote executor)").option("--hmac-key <base64>", "shared HMAC key for the remote executor (or set SEEKRIT_PROVISIONER_HMAC_KEY); `seekrit provisioner keygen` mints one").option("--admin-url <url>", "in_do: admin mysql:// connection string (or set SEEKRIT_MYSQL_ADMIN_URL); wrapped locally").option("--create-statement <sql>", "custom CREATE template (repeatable)", collect$2, []).option("--revoke-statement <sql>", "custom REVOKE template (repeatable)", collect$2, []).action(async (options) => {
|
|
1065
1121
|
const ctx = buildContext();
|
|
1066
1122
|
const org = await resolveOrg(ctx, options.org);
|
|
1067
1123
|
const executor = options.executor === "remote" ? "remote" : "in_do";
|
|
@@ -1072,8 +1128,12 @@ function registerMysqlCommands(program) {
|
|
|
1072
1128
|
"custom"
|
|
1073
1129
|
].includes(options.access)) fail("--access must be readonly, readwrite, or custom");
|
|
1074
1130
|
const accessLevel = options.access;
|
|
1075
|
-
const adminSecret =
|
|
1076
|
-
|
|
1131
|
+
const adminSecret = resolveLeaseAdminSecret({
|
|
1132
|
+
executor,
|
|
1133
|
+
hmacKey: options.hmacKey,
|
|
1134
|
+
adminUrl: options.adminUrl,
|
|
1135
|
+
adminUrlEnv: "SEEKRIT_MYSQL_ADMIN_URL"
|
|
1136
|
+
});
|
|
1077
1137
|
const config = {
|
|
1078
1138
|
provider: "mysql",
|
|
1079
1139
|
executor,
|
|
@@ -1466,6 +1526,11 @@ z.object({
|
|
|
1466
1526
|
expiresAt: z.iso.datetime().nullish()
|
|
1467
1527
|
});
|
|
1468
1528
|
z.object({ prefs: z.partialRecord(z.enum(NOTIFICATION_TYPES), z.boolean()) });
|
|
1529
|
+
z.object({
|
|
1530
|
+
endpoint: z.url().max(2048),
|
|
1531
|
+
headers: z.record(z.string().min(1).max(256), z.string().max(4096)).optional(),
|
|
1532
|
+
enabled: z.boolean().default(true)
|
|
1533
|
+
});
|
|
1469
1534
|
z.object({
|
|
1470
1535
|
cursor: z.string().optional(),
|
|
1471
1536
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
@@ -1505,7 +1570,7 @@ function generateRoleName(prefix = "tmp") {
|
|
|
1505
1570
|
function registerPgCommands(program) {
|
|
1506
1571
|
const pg = program.command("pg").description("temporary Postgres credentials (short-lived, zero-knowledge)");
|
|
1507
1572
|
const target = pg.command("target").description("manage provisioning targets");
|
|
1508
|
-
target.command("add").description("register a Postgres cluster to lease credentials from").requiredOption("--name <name>", "display name, e.g. prod-db").option("--org <slug>").requiredOption("--host <host>", "database host").option("--port <port>", "database port", "5432").requiredOption("--database <name>", "database to connect to").option("--access <level>", "readonly | readwrite | custom", "readonly").option("--schema <name>", "schema for preset grants", "public").option("--executor <mode>", "in_do | remote", "in_do").option("--provisioner-url <url>", "customer provisioner URL (remote executor)").option("--admin-url <url>", "admin postgres:// connection string (or set SEEKRIT_PG_ADMIN_URL); wrapped locally").option("--create-statement <sql>", "custom CREATE template (repeatable)", collect$1, []).option("--revoke-statement <sql>", "custom REVOKE template (repeatable)", collect$1, []).action(async (options) => {
|
|
1573
|
+
target.command("add").description("register a Postgres cluster to lease credentials from").requiredOption("--name <name>", "display name, e.g. prod-db").option("--org <slug>").requiredOption("--host <host>", "database host").option("--port <port>", "database port", "5432").requiredOption("--database <name>", "database to connect to").option("--access <level>", "readonly | readwrite | custom", "readonly").option("--schema <name>", "schema for preset grants", "public").option("--executor <mode>", "in_do | remote", "in_do").option("--provisioner-url <url>", "customer provisioner URL (remote executor)").option("--hmac-key <base64>", "shared HMAC key for the remote executor (or set SEEKRIT_PROVISIONER_HMAC_KEY); `seekrit provisioner keygen` mints one").option("--admin-url <url>", "in_do: admin postgres:// connection string (or set SEEKRIT_PG_ADMIN_URL); wrapped locally").option("--create-statement <sql>", "custom CREATE template (repeatable)", collect$1, []).option("--revoke-statement <sql>", "custom REVOKE template (repeatable)", collect$1, []).action(async (options) => {
|
|
1509
1574
|
const ctx = buildContext();
|
|
1510
1575
|
const org = await resolveOrg(ctx, options.org);
|
|
1511
1576
|
const executor = options.executor === "remote" ? "remote" : "in_do";
|
|
@@ -1516,8 +1581,12 @@ function registerPgCommands(program) {
|
|
|
1516
1581
|
"custom"
|
|
1517
1582
|
].includes(options.access)) fail("--access must be readonly, readwrite, or custom");
|
|
1518
1583
|
const accessLevel = options.access;
|
|
1519
|
-
const adminSecret =
|
|
1520
|
-
|
|
1584
|
+
const adminSecret = resolveLeaseAdminSecret({
|
|
1585
|
+
executor,
|
|
1586
|
+
hmacKey: options.hmacKey,
|
|
1587
|
+
adminUrl: options.adminUrl,
|
|
1588
|
+
adminUrlEnv: "SEEKRIT_PG_ADMIN_URL"
|
|
1589
|
+
});
|
|
1521
1590
|
const config = {
|
|
1522
1591
|
provider: "postgres",
|
|
1523
1592
|
executor,
|
|
@@ -2219,6 +2288,7 @@ token.command("revoke <tokenId>").description("revoke a service token").option("
|
|
|
2219
2288
|
});
|
|
2220
2289
|
registerPgCommands(program);
|
|
2221
2290
|
registerMysqlCommands(program);
|
|
2291
|
+
registerProvisionerCommands(program);
|
|
2222
2292
|
registerSshCommands(program);
|
|
2223
2293
|
program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
|
|
2224
2294
|
const { runMcpServer } = await import("./mcp-BEcV_KpM.js");
|