@coffre/cli 0.1.1 → 0.1.2

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.
@@ -0,0 +1,64 @@
1
+ import { parseArgs } from "node:util";
2
+ import { randomBytes } from "node:crypto";
3
+ //#region src/keys.ts
4
+ /**
5
+ * Two fresh keys, each 32 random bytes in base64, and an id for the KEK
6
+ * dated to the day, so that a rotation, even one the same month, gets a new
7
+ * one: the vault refuses two KEKs with the same id.
8
+ */
9
+ function generateKeys(now = /* @__PURE__ */ new Date()) {
10
+ const key = () => randomBytes(32).toString("base64");
11
+ return {
12
+ KEK_ID: `kek-${now.toISOString().slice(0, 10)}`,
13
+ KEK: key(),
14
+ AUDIT_CHAIN_KEY: key()
15
+ };
16
+ }
17
+ /** What each key is for, as comments: `coffre keys` and `coffre setup` both say it. */
18
+ const KEYS_EXPLAINED = `# Two keys, one for each component, so that the app, which faces the
19
+ # network, never holds what decrypts a value:
20
+ #
21
+ # KEK the vault's. Decrypts every value, and signs the vault's
22
+ # log entries, member rows and checkpoints. Lose it, and
23
+ # every value is lost for good.
24
+ # AUDIT_CHAIN_KEY the app's. Signs the app's log entries, sessions and
25
+ # tokens. Lose it, and everyone is signed out and the log
26
+ # stops verifying.
27
+ # KEK_ID names the KEK; not secret.
28
+ #
29
+ # Whoever has the KEK and a copy of the database has every value: keep it
30
+ # apart from the backups.
31
+ `;
32
+ /** The keys as a dotenv block, then, as comments, what each is for and where it goes. */
33
+ function formatKeys(keys) {
34
+ return `KEK_ID=${keys.KEK_ID}
35
+ KEK=${keys.KEK}
36
+ AUDIT_CHAIN_KEY=${keys.AUDIT_CHAIN_KEY}
37
+
38
+ # Save all three in your password manager now. They are shown once, and
39
+ # coffre keeps no copy.
40
+ #
41
+ ${KEYS_EXPLAINED}#
42
+ # On Workers, KEK_ID is a var in vault/wrangler.jsonc, and KEK and
43
+ # AUDIT_CHAIN_KEY are Worker secrets (wrangler secret put). On Node, KEK_ID
44
+ # and KEK go in vault.env, and AUDIT_CHAIN_KEY in server.env.
45
+ #
46
+ # These are for a new deployment. To rotate a deployment's KEK, take only
47
+ # KEK_ID and KEK, and keep the old pair in previousKeks, for what it wrapped
48
+ # and signed. AUDIT_CHAIN_KEY cannot be changed.
49
+ `;
50
+ }
51
+ function keys(args) {
52
+ const { values } = parseArgs({
53
+ args,
54
+ options: { json: {
55
+ type: "boolean",
56
+ default: false
57
+ } },
58
+ allowPositionals: false
59
+ });
60
+ const fresh = generateKeys();
61
+ process.stdout.write(values.json ? `${JSON.stringify(fresh)}\n` : formatKeys(fresh));
62
+ }
63
+ //#endregion
64
+ export { generateKeys as n, keys as r, KEYS_EXPLAINED as t };
package/dist/main.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { r as keys } from "./keys-D9CG_wYV.js";
3
+ import { i as isRole, r as assignableToEnvironment, t as ROLES } from "./access-CO1kGjWW.js";
2
4
  import { parseArgs, promisify } from "node:util";
3
5
  import { execFile, spawn } from "node:child_process";
4
6
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
5
7
  import { homedir, hostname } from "node:os";
6
8
  import { basename, dirname, join, resolve } from "node:path";
7
9
  import { fileURLToPath } from "node:url";
8
- import { randomBytes } from "node:crypto";
9
10
  //#region src/init.ts
10
11
  const KINDS = ["workers", "node"];
11
12
  /** Where a template lives: in the package when published, else in the repository. */
@@ -69,67 +70,6 @@ function init(kind, target, version) {
69
70
  return written;
70
71
  }
71
72
  //#endregion
72
- //#region src/keys.ts
73
- /**
74
- * Three fresh keys, each 32 random bytes in base64, and an id for the KEK
75
- * dated to the day, so that a rotation, even one the same month, gets a new
76
- * one: the vault refuses two KEKs with the same id.
77
- */
78
- function generateKeys(now = /* @__PURE__ */ new Date()) {
79
- const key = () => randomBytes(32).toString("base64");
80
- return {
81
- KEK_ID: `kek-${now.toISOString().slice(0, 10)}`,
82
- KEK: key(),
83
- SIGNING_KEY: key(),
84
- AUDIT_CHAIN_KEY: key()
85
- };
86
- }
87
- /** The keys as a dotenv block, then, as comments, what each is for and where it goes. */
88
- function formatKeys(keys) {
89
- return `KEK_ID=${keys.KEK_ID}
90
- KEK=${keys.KEK}
91
- SIGNING_KEY=${keys.SIGNING_KEY}
92
- AUDIT_CHAIN_KEY=${keys.AUDIT_CHAIN_KEY}
93
-
94
- # Save all four in your password manager now. They are shown once, and
95
- # coffre keeps no copy.
96
- #
97
- # The vault gets KEK_ID, KEK and SIGNING_KEY; the app gets AUDIT_CHAIN_KEY.
98
- # They are kept apart so that the app, which faces the network, never holds
99
- # what decrypts a value: whoever has the KEK and a copy of the database has
100
- # every value.
101
- #
102
- # KEK decrypts every value. Lose it, and every value is lost.
103
- # SIGNING_KEY signs the vault's log entries, checkpoints and member
104
- # rows. Lose it, and the log stops verifying and every
105
- # member is refused.
106
- # AUDIT_CHAIN_KEY signs the app's log entries, sessions and tokens. Lose
107
- # it, and the log stops verifying and everyone is signed
108
- # out.
109
- # KEK_ID names the KEK; not secret.
110
- #
111
- # On Workers, KEK_ID is a var in vault/wrangler.jsonc, and the other three are
112
- # Worker secrets (wrangler secret put). On Node, they go in vault.env and
113
- # server.env.
114
- #
115
- # These are for a new deployment. To rotate a deployment's KEK, take only
116
- # KEK_ID and KEK, and keep the old pair in previousKeks: its SIGNING_KEY and
117
- # AUDIT_CHAIN_KEY cannot be changed.
118
- `;
119
- }
120
- function keys(args) {
121
- const { values } = parseArgs({
122
- args,
123
- options: { json: {
124
- type: "boolean",
125
- default: false
126
- } },
127
- allowPositionals: false
128
- });
129
- const fresh = generateKeys();
130
- process.stdout.write(values.json ? `${JSON.stringify(fresh)}\n` : formatKeys(fresh));
131
- }
132
- //#endregion
133
73
  //#region src/instance.ts
134
74
  const MODES = ["signin", "cloudflare"];
135
75
  function emptyStore() {
@@ -528,79 +468,6 @@ async function planImport(coffre, path, entries) {
528
468
  };
529
469
  }
530
470
  //#endregion
531
- //#region ../core/dist/access.js
532
- /**
533
- * Who may do what: seven permissions and the six roles that bundle them.
534
- *
535
- * Deliberately constants and not tables or a policy language. A grant names
536
- * one role for one member on one place, and a check is a lookup in this file,
537
- * so authorisation stays something you can read end to end.
538
- */
539
- const PERMISSIONS = [
540
- "secret.read",
541
- "secret.write",
542
- "secret.archive",
543
- "audit.read",
544
- "environment.manage",
545
- "grant.manage",
546
- "project.manage"
547
- ];
548
- /**
549
- * Permissions that only make sense on a project. Granting
550
- * `environment.manage` on one environment would be incoherent: what it
551
- * authorises is creating that environment's siblings.
552
- */
553
- const PROJECT_ONLY_PERMISSIONS = [
554
- "environment.manage",
555
- "grant.manage",
556
- "project.manage"
557
- ];
558
- const ROLES = {
559
- viewer: {
560
- name: "Viewer",
561
- description: "Read secret values.",
562
- permissions: ["secret.read"]
563
- },
564
- developer: {
565
- name: "Developer",
566
- description: "Read and write secrets.",
567
- permissions: ["secret.read", "secret.write"]
568
- },
569
- maintainer: {
570
- name: "Maintainer",
571
- description: "Read, write and retire secrets, and manage environments.",
572
- permissions: [
573
- "secret.read",
574
- "secret.write",
575
- "secret.archive",
576
- "environment.manage"
577
- ]
578
- },
579
- "access-manager": {
580
- name: "Access manager",
581
- description: "Manage who has access. Cannot read secret values.",
582
- permissions: ["grant.manage"]
583
- },
584
- auditor: {
585
- name: "Auditor",
586
- description: "Read the audit log. Cannot read secret values.",
587
- permissions: ["audit.read"]
588
- },
589
- owner: {
590
- name: "Owner",
591
- description: "Everything, including reading secret values.",
592
- permissions: [...PERMISSIONS]
593
- }
594
- };
595
- Object.keys(ROLES);
596
- function isRole(value) {
597
- return Object.hasOwn(ROLES, value);
598
- }
599
- /** Roles that hold a project-only permission can only be granted on a project. */
600
- function assignableToEnvironment(role) {
601
- return !ROLES[role].permissions.some((permission) => PROJECT_ONLY_PERMISSIONS.includes(permission));
602
- }
603
- //#endregion
604
471
  //#region ../core/dist/dotenv.js
605
472
  const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
606
473
  function parseDotenv(input) {
@@ -1593,7 +1460,10 @@ const USAGE = `coffre - secrets, with an audit log
1593
1460
  New deployment
1594
1461
  coffre init --workers [<dir>] two Cloudflare Workers: the app and its vault
1595
1462
  coffre init --node [<dir>] a Node server, and its vault beside it
1596
- coffre keys [--json] its three keys and the KEK's id, made here and shown once
1463
+ coffre setup [--reset-passwords] [--json]
1464
+ its database logins, migrations and keys, in one go,
1465
+ shown once (the connection string is asked for)
1466
+ coffre keys [--json] its keys and the KEK's id alone, made here and shown once
1597
1467
 
1598
1468
  Session
1599
1469
  coffre login [<url>] [--no-browser] sign in, and make <url> the current instance
@@ -1645,6 +1515,9 @@ switch (command) {
1645
1515
  case "keys":
1646
1516
  keys(rest);
1647
1517
  break;
1518
+ case "setup":
1519
+ await (await import("./setup-DPHVyr2N.js")).setup(rest);
1520
+ break;
1648
1521
  case "login":
1649
1522
  await login(rest);
1650
1523
  break;