@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.
@@ -15,10 +15,12 @@ const vault = await serveVault({
15
15
  socket: env('VAULT_SOCKET'),
16
16
  database: env('DATABASE_URL'),
17
17
  kek: { id: env('KEK_ID'), key: env('KEK') },
18
- // After a rotation, the KEKs before it, so the data keys they wrapped
19
- // still open: previousKeks: [{ id: 'kek-1', key: env('KEK_1') }],
18
+ // After a rotation, the KEKs before it, so the data keys they wrapped still
19
+ // open and the records signed under them before it still verify:
20
+ // previousKeks: [{ id: 'kek-1', key: env('KEK_1') }],
20
21
  rootAdmins: env('ROOT_ADMINS').split(',').map((email) => email.trim()),
21
- signingKey: env('SIGNING_KEY'),
22
+ // The vault derives its signing key from the KEK. With a KEK a key service
23
+ // holds, such as awsKms(…), it needs one of its own: signingKey: env('SIGNING_KEY').
22
24
  });
23
25
  console.log(`the vault is listening on ${vault.socket}`);
24
26
 
@@ -2,15 +2,14 @@
2
2
  # the vault's user alone.
3
3
 
4
4
  VAULT_SOCKET=vault.sock
5
- # The server's Postgres database, as the vault's own login.
5
+ # From `coffre setup`: the server's database, as the vault's own login.
6
6
  DATABASE_URL=postgres://coffre_vault_runtime:CHANGE_ME@127.0.0.1:5432/coffre
7
7
 
8
- # From `coffre keys`, saved in your password manager first.
9
- # The key-encryption key, and its name. A rotation gives the new one a new name.
8
+ # From `coffre setup`, saved in your password manager first: the
9
+ # key-encryption key, and its name. A rotation gives the new one a new name.
10
+ # The vault derives the key it signs its records with from it.
10
11
  KEK_ID=kek-1
11
12
  KEK=
12
- # Signs the vault's log entries, checkpoints and member rows.
13
- SIGNING_KEY=
14
13
 
15
14
  # Comma-separated emails: the first people in, whom nobody can remove.
16
15
  ROOT_ADMINS=you@example.com
@@ -17,76 +17,61 @@ Worker secrets. Everything below runs from this directory.
17
17
  - `app/wrangler.jsonc`: `PUBLIC_URL`, and `GITHUB_CLIENT_ID` from a GitHub
18
18
  OAuth app whose callback is `<PUBLIC_URL>/auth/callback/github`.
19
19
  - `vault/wrangler.jsonc`: `ROOT_ADMINS`, the emails of the first people in,
20
- and `KEK_ID`, which `coffre keys` gives you below.
20
+ and `KEK_ID`, which `coffre setup` gives you below.
21
21
 
22
- ## 2. The database
22
+ ## 2. The database and keys
23
23
 
24
- coffre wants a Postgres database with three logins: its owner, for
25
- migrations; `coffre_runtime`, which the app runs as; and
26
- `coffre_vault_runtime`, which the vault runs as. Create the two as plain
27
- logins. As the database's administrator, connect with `psql` and run:
28
-
29
- ```sql
30
- CREATE ROLE coffre_runtime LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
31
- CREATE ROLE coffre_vault_runtime LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
32
- \password coffre_runtime
33
- \password coffre_vault_runtime
34
- ```
35
-
36
- Each `\password` prompts for a different generated password. The owner
37
- must be able to create and grant roles: the migration creates `coffre_app`
38
- and `coffre_vault`, and grants each login its own group. Only the vault may
39
- write members and grants, and each appends to the log only as itself.
40
- URL-encode special characters in the passwords in these URLs:
24
+ Make a Postgres database, then, from this directory:
41
25
 
42
26
  ```sh
43
- pnpm install
44
- pnpm migrate "postgres://owner:…@db.example.com:5432/coffre"
45
- pnpm exec wrangler hyperdrive create coffre --caching-disabled \
46
- --connection-string="postgres://coffre_runtime:…@db.example.com:5432/coffre"
47
- pnpm exec wrangler hyperdrive create coffre-vault --caching-disabled \
48
- --connection-string="postgres://coffre_vault_runtime:…@db.example.com:5432/coffre"
27
+ npx @coffre/cli setup
49
28
  ```
50
29
 
51
- Keep `--caching-disabled`: Hyperdrive otherwise caches reads for up to a
52
- minute, and a revoked token or a signed-out session could keep working that
53
- long. `wrangler.jsonc` cannot set it, so for a config made another way,
54
- check `caching` in `wrangler hyperdrive get <id>`.
30
+ Run it with the CLI you ran `coffre init` with. It asks for the database
31
+ administrator's connection string at a hidden prompt (a script can pipe it
32
+ in, or set `COFFRE_SETUP_DATABASE_URL`; never pass it as an argument). It
33
+ makes the two logins coffre runs as, `coffre_runtime` for the app and
34
+ `coffre_vault_runtime` for the vault, migrates the database, checks that
35
+ each login holds only its rights, and prints every value at once: the two
36
+ keys, the KEK's id, and each login's connection string. It keeps no copy and
37
+ writes no file. Save its output in your password manager, with the GitHub
38
+ client secret, before anything else. There is one key for each Worker, so
39
+ that the app, which faces the network, never holds what decrypts a value:
40
+
41
+ - `KEK`, the vault's, decrypts every value, and the vault derives from it the
42
+ key it signs its records with. Lose it, and every value is lost.
43
+ - `AUDIT_CHAIN_KEY`, the app's, signs the app's log entries, sessions and
44
+ tokens. Lose it, and everyone is signed out and the log stops verifying.
45
+
46
+ To do the same by hand, see
47
+ [deploy.md](https://github.com/erwinkn/coffre/blob/main/docs/deploy.md#appendix-the-database-by-hand).
48
+
49
+ ## 3. Hyperdrive and secrets
50
+
51
+ Run the two `wrangler hyperdrive create` commands setup printed: one config
52
+ per login, each `--caching-disabled`. Hyperdrive otherwise caches reads for
53
+ up to a minute, and a revoked token or a signed-out session could keep
54
+ working that long. `wrangler.jsonc` cannot set it, so for a config made
55
+ another way, check `caching` in `wrangler hyperdrive get <id>`.
55
56
 
56
57
  Put the ids they print under `hyperdrive`, the first in
57
- `app/wrangler.jsonc` and the second in `vault/wrangler.jsonc`. Run
58
- `pnpm migrate` again after every upgrade of `@coffre/server`, before
59
- deploying it.
60
-
61
- ## 3. Keys
62
-
63
- ```sh
64
- coffre keys
65
- ```
66
-
67
- Run it with the CLI you ran `coffre init` with, or as
68
- `npx @coffre/cli keys`. It prints three keys and the KEK's id, once, and
69
- keeps no copy. Save its output in your password manager, with the GitHub
70
- client secret, before anything else. The vault gets `KEK` and `SIGNING_KEY`,
71
- and the app `AUDIT_CHAIN_KEY`, so that the app, which faces the network,
72
- never holds what decrypts a value:
73
-
74
- - `KEK` decrypts every value. Lose it, and every value is lost.
75
- - `SIGNING_KEY` signs the vault's log entries and member rows.
76
- - `AUDIT_CHAIN_KEY` signs the app's log entries, sessions and tokens.
77
-
78
- Lose either of the last two, and the log stops verifying and everyone is
79
- locked out. Put `KEK_ID` in `vault/wrangler.jsonc`, then set the secrets;
80
- each command prompts for the saved value:
58
+ `app/wrangler.jsonc` and the second in `vault/wrangler.jsonc`, and `KEK_ID`
59
+ in `vault/wrangler.jsonc`. Then set the secrets; each command prompts for
60
+ the saved value:
81
61
 
82
62
  ```sh
83
63
  pnpm exec wrangler secret put KEK -c vault/wrangler.jsonc
84
- pnpm exec wrangler secret put SIGNING_KEY -c vault/wrangler.jsonc
85
64
  pnpm exec wrangler secret put AUDIT_CHAIN_KEY -c app/wrangler.jsonc
86
65
  pnpm exec wrangler secret put GITHUB_CLIENT_SECRET -c app/wrangler.jsonc
87
66
  ```
88
67
 
89
- Keep older KEKs after a rotation too: what they wrapped still needs them.
68
+ Run `pnpm migrate`, with the administrator's URL in `DATABASE_URL`, after
69
+ every upgrade of `@coffre/server`, before deploying it.
70
+
71
+ Keep older KEKs after a rotation, for good: what they wrapped still needs
72
+ them, and so does what the vault signed under them before it. With AWS KMS
73
+ instead of a key of your own, the vault also needs a `SIGNING_KEY`
74
+ ([keys](https://github.com/erwinkn/coffre/blob/main/docs/keys.md#aws-kms)).
90
75
 
91
76
  ## 4. Deploy
92
77
 
@@ -17,7 +17,7 @@
17
17
  "GITHUB_CLIENT_ID": "replace-with-your-client-id"
18
18
  },
19
19
  "secrets": {
20
- // AUDIT_CHAIN_KEY is from `coffre keys`; the app never gets the vault's keys.
20
+ // AUDIT_CHAIN_KEY is from `coffre setup`; the app never gets the vault's keys.
21
21
  "required": ["GITHUB_CLIENT_SECRET", "AUDIT_CHAIN_KEY"]
22
22
  },
23
23
  // `wrangler hyperdrive create coffre --caching-disabled --connection-string=<runtime login URL>`
@@ -14,13 +14,13 @@
14
14
  "conformance": "coffre-conformance workers"
15
15
  },
16
16
  "dependencies": {
17
- "@coffre/server": "0.1.1",
18
- "@coffre/ui": "0.1.1",
19
- "@coffre/vault": "0.1.1"
17
+ "@coffre/server": "0.1.2",
18
+ "@coffre/ui": "0.1.2",
19
+ "@coffre/vault": "0.1.2"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@cloudflare/workers-types": "5.20260920.1",
23
- "@coffre/conformance": "0.1.1",
23
+ "@coffre/conformance": "0.1.2",
24
24
  "typescript": "5.9.3",
25
25
  "wrangler": "4.118.0"
26
26
  }
@@ -7,15 +7,16 @@ type Env = {
7
7
  VAULT_HYPERDRIVE: Hyperdrive;
8
8
  KEK_ID: string;
9
9
  KEK: string;
10
- SIGNING_KEY: string;
11
10
  ROOT_ADMINS: string;
12
11
  };
13
12
 
14
13
  export default vault((env: Env) => ({
15
14
  database: postgres(env.VAULT_HYPERDRIVE),
16
15
  kek: { id: env.KEK_ID, key: env.KEK },
17
- // After a rotation, the KEKs before it, so the data keys they wrapped
18
- // still open: previousKeks: [{ id: 'kek-2026-09', key: env.KEK_2026_09 }],
16
+ // After a rotation, the KEKs before it, so the data keys they wrapped still
17
+ // open and the records signed under them before it still verify:
18
+ // previousKeks: [{ id: 'kek-2026-09', key: env.KEK_2026_09 }],
19
19
  rootAdmins: env.ROOT_ADMINS.split(',').map((email) => email.trim()),
20
- signingKey: env.SIGNING_KEY,
20
+ // The vault derives its signing key from the KEK. With a KEK a key service
21
+ // holds, such as awsKms(…), it needs one of its own: signingKey: env.SIGNING_KEY.
21
22
  }));
@@ -11,14 +11,15 @@
11
11
  "workers_dev": false,
12
12
  "observability": { "enabled": true },
13
13
  "vars": {
14
- // Names the KEK below: from `coffre keys`. A rotation gives the new one a new id.
14
+ // Names the KEK below: from `coffre setup`. A rotation gives the new one a new id.
15
15
  "KEK_ID": "kek-1",
16
16
  // Comma-separated emails: the first people in, whom nobody can remove.
17
17
  "ROOT_ADMINS": "you@example.com"
18
18
  },
19
19
  "secrets": {
20
- // From `coffre keys`: wrangler secret put KEK, then SIGNING_KEY.
21
- "required": ["KEK", "SIGNING_KEY"]
20
+ // From `coffre setup`: wrangler secret put KEK. The vault derives the key
21
+ // it signs its records with from it.
22
+ "required": ["KEK"]
22
23
  },
23
24
  // The app's database, as the vault's own login: `wrangler hyperdrive
24
25
  // create coffre-vault --caching-disabled --connection-string=<vault login
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The coffre command line: secrets in your shell, and `coffre init` for a new deployment.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,13 +23,16 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "26.1.1",
26
+ "@types/pg": "8.20.0",
27
+ "pg": "8.16.3",
26
28
  "tsdown": "0.23.0",
27
29
  "typescript": "5.9.3",
28
- "@coffre/core": "0.1.1",
29
- "@coffre/client": "0.1.1"
30
+ "@coffre/client": "0.1.2",
31
+ "@coffre/core": "0.1.2",
32
+ "@coffre/db": "0.1.2"
30
33
  },
31
34
  "scripts": {
32
- "build": "tsdown && node scripts/copy-templates.ts",
35
+ "build": "tsdown && node scripts/copy-templates.ts && node scripts/copy-migrations.ts",
33
36
  "typecheck": "tsc --noEmit",
34
37
  "test": "node --conditions=coffre:source --test"
35
38
  }