@coffre/cli 0.1.1 → 0.1.3
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 +4 -1
- package/dist/access-CO1kGjWW.js +74 -0
- package/dist/connect-BONR52al.js +770 -0
- package/dist/connect-BZdPYNbZ.js +2 -0
- package/dist/esm-B69mMLcR.js +2 -0
- package/dist/esm-Bts-esAl.js +4202 -0
- package/dist/keys-RhB7ayOY.js +726 -0
- package/dist/main.js +10 -137
- package/dist/migrations/postgres/0000_baseline.sql +505 -0
- package/dist/migrations/postgres/meta/0000_snapshot.json +1939 -0
- package/dist/migrations/postgres/meta/_journal.json +13 -0
- package/dist/migrator-W1a-ooWi.js +37 -0
- package/dist/node-postgres-BlNvYfq_.js +2 -0
- package/dist/node-postgres-_y-EeSHQ.js +2831 -0
- package/dist/portable-CPh2thNN.js +87 -0
- package/dist/relations-CJCGPRst.js +2712 -0
- package/dist/schema-sqlite-Byp7B3yI.js +1078 -0
- package/dist/setup-3OJc_VCA.js +756 -0
- package/dist/templates/node/README.md +41 -48
- package/dist/templates/node/package.json +3 -3
- package/dist/templates/node/server.env.example +5 -4
- package/dist/templates/node/src/server.ts +3 -3
- package/dist/templates/node/src/vault.ts +7 -5
- package/dist/templates/node/vault.env.example +7 -7
- package/dist/templates/workers/README.md +47 -57
- package/dist/templates/workers/app/src/worker.ts +2 -2
- package/dist/templates/workers/app/wrangler.jsonc +4 -4
- package/dist/templates/workers/package.json +4 -4
- package/dist/templates/workers/vault/src/worker.ts +8 -7
- package/dist/templates/workers/vault/wrangler.jsonc +8 -6
- package/package.json +7 -4
|
@@ -9,7 +9,7 @@ Two processes, configured in code:
|
|
|
9
9
|
only the server, on a Unix socket.
|
|
10
10
|
|
|
11
11
|
Run them as two users that share a group, and the process facing the network
|
|
12
|
-
never holds the
|
|
12
|
+
never holds the vault key. For local development, `server.ts` can run the vault
|
|
13
13
|
in its own process instead; see the comment there. Everything below runs
|
|
14
14
|
from this directory, on Node 24 or later.
|
|
15
15
|
|
|
@@ -19,63 +19,56 @@ from this directory, on Node 24 or later.
|
|
|
19
19
|
pnpm install
|
|
20
20
|
cp server.env.example server.env
|
|
21
21
|
cp vault.env.example vault.env
|
|
22
|
+
chmod 600 server.env vault.env
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
Fill
|
|
25
|
-
`<PUBLIC_URL>/auth/callback/github`, `ROOT_ADMINS
|
|
25
|
+
Fill in `PUBLIC_URL`, a GitHub OAuth app whose callback is
|
|
26
|
+
`<PUBLIC_URL>/auth/callback/github`, and `ROOT_ADMINS`. Keep each env file
|
|
27
|
+
readable only by its process's user.
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
coffre keys
|
|
29
|
-
```
|
|
29
|
+
## 2. The database and keys
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
`npx @coffre/cli keys`. It prints three keys and the KEK's id, once, and
|
|
33
|
-
keeps no copy. Save its output in your password manager, with the OAuth
|
|
34
|
-
client secret, before anything else. `KEK_ID`, `KEK` and `SIGNING_KEY` go in
|
|
35
|
-
`vault.env`, and `AUDIT_CHAIN_KEY` in `server.env`, so that the server,
|
|
36
|
-
which faces the network, never holds what decrypts a value:
|
|
37
|
-
|
|
38
|
-
- `KEK` decrypts every value. Lose it, and every value is lost.
|
|
39
|
-
- `SIGNING_KEY` signs the vault's log entries and member rows.
|
|
40
|
-
- `AUDIT_CHAIN_KEY` signs the server's log entries, sessions and tokens.
|
|
41
|
-
|
|
42
|
-
Lose either of the last two, and the log stops verifying and everyone is
|
|
43
|
-
locked out.
|
|
44
|
-
|
|
45
|
-
## 2. The database
|
|
46
|
-
|
|
47
|
-
Use one Postgres database with three logins: its owner for migrations,
|
|
48
|
-
`coffre_runtime` for the server, and `coffre_vault_runtime` for the vault.
|
|
49
|
-
As an administrator, connect to the database with `psql` and create the
|
|
50
|
-
runtime logins. `\password` prompts for each password without putting it
|
|
51
|
-
in a SQL statement or shell history:
|
|
52
|
-
|
|
53
|
-
```sql
|
|
54
|
-
CREATE ROLE coffre_runtime LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
|
|
55
|
-
CREATE ROLE coffre_vault_runtime LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
|
|
56
|
-
\password coffre_runtime
|
|
57
|
-
\password coffre_vault_runtime
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Migrate as the owner, who must also be able to create and grant roles:
|
|
31
|
+
Make a Postgres database, then:
|
|
61
32
|
|
|
62
33
|
```sh
|
|
63
|
-
|
|
34
|
+
npx @coffre/cli setup
|
|
64
35
|
```
|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
37
|
+
Run it with the CLI you ran `coffre init` with. It asks for the database
|
|
38
|
+
administrator's connection string at a hidden prompt. A script can pipe it
|
|
39
|
+
in, or set `COFFRE_SETUP_DATABASE_URL`; never pass it as an argument. It
|
|
40
|
+
makes the two logins coffre runs as, `coffre_runtime` for the server and
|
|
41
|
+
`coffre_vault_runtime` for the vault, migrates the database, and checks that
|
|
42
|
+
each login holds only its rights.
|
|
43
|
+
|
|
44
|
+
Then it shows five values on a screen of their own, which leaves nothing
|
|
45
|
+
behind in your scrollback. Copy each with `c` into your password manager,
|
|
46
|
+
beside the OAuth client secret, then into its file; `w` shows where each
|
|
47
|
+
goes. Nothing keeps a copy. There is one key for each process, so that the
|
|
48
|
+
server, which faces the network, never holds what decrypts a value.
|
|
49
|
+
|
|
50
|
+
- `server.env` takes the app key, `APP_KEY`, and the app's database URL, as
|
|
51
|
+
`DATABASE_URL`. The app key signs the server's log entries, sessions and
|
|
52
|
+
tokens. Lose it, and everyone is signed out and the log stops verifying.
|
|
53
|
+
- `vault.env` takes the vault ID, `VAULT_KEY_ID`, the vault key, `VAULT_KEY`,
|
|
54
|
+
and the vault's database URL, as `DATABASE_URL`. The vault key decrypts
|
|
55
|
+
every value, and the vault derives from it the key it signs its records
|
|
56
|
+
with. Lose it, and every value is lost. The vault ID only names it.
|
|
57
|
+
- Both URLs are the same database, each through its process's own login.
|
|
58
|
+
Neither process gets the administrator's URL.
|
|
59
|
+
|
|
60
|
+
With AWS KMS instead of a key of your own, the vault also needs a
|
|
61
|
+
`SIGNING_KEY` ([keys](https://github.com/erwinkn/coffre/blob/main/docs/keys.md#aws-kms)).
|
|
62
|
+
To do the same by hand, see
|
|
63
|
+
[deploy.md](https://github.com/erwinkn/coffre/blob/main/docs/deploy.md#appendix-the-database-by-hand).
|
|
64
|
+
|
|
65
|
+
Run `pnpm migrate`, with the administrator's URL in `DATABASE_URL`, after
|
|
66
|
+
every package upgrade, before starting either process.
|
|
75
67
|
|
|
76
68
|
For tests and local development only, both URLs may instead name the same
|
|
77
|
-
absolute SQLite file, e.g. `file:/tmp/coffre-local.db`; migrate that URL once
|
|
78
|
-
|
|
69
|
+
absolute SQLite file, e.g. `file:/tmp/coffre-local.db`; migrate that URL once
|
|
70
|
+
with `pnpm migrate`, and make the keys with `npx @coffre/cli keys`. SQLite
|
|
71
|
+
has no database logins or separation of privileges.
|
|
79
72
|
|
|
80
73
|
## 3. Run
|
|
81
74
|
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"conformance": "coffre-conformance node"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@coffre/server": "0.1.
|
|
17
|
-
"@coffre/vault": "0.1.
|
|
16
|
+
"@coffre/server": "0.1.3",
|
|
17
|
+
"@coffre/vault": "0.1.3"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@coffre/conformance": "0.1.
|
|
20
|
+
"@coffre/conformance": "0.1.3",
|
|
21
21
|
"@types/node": "26.1.1",
|
|
22
22
|
"typescript": "5.9.3"
|
|
23
23
|
}
|
|
@@ -4,7 +4,8 @@ PORT=3000
|
|
|
4
4
|
# Where people reach coffre, through the proxy that terminates TLS in front
|
|
5
5
|
# of it: an origin, no path.
|
|
6
6
|
PUBLIC_URL=https://secrets.example.com
|
|
7
|
-
#
|
|
7
|
+
# From `coffre setup`: the database, as the server's own login. The vault
|
|
8
|
+
# uses it through another.
|
|
8
9
|
DATABASE_URL=postgres://coffre_runtime:CHANGE_ME@127.0.0.1:5432/coffre
|
|
9
10
|
# The vault's socket, as in vault.env.
|
|
10
11
|
VAULT_SOCKET=vault.sock
|
|
@@ -13,6 +14,6 @@ VAULT_SOCKET=vault.sock
|
|
|
13
14
|
GITHUB_CLIENT_ID=
|
|
14
15
|
GITHUB_CLIENT_SECRET=
|
|
15
16
|
|
|
16
|
-
# From `coffre
|
|
17
|
-
# server's log entries, sessions and tokens.
|
|
18
|
-
|
|
17
|
+
# From `coffre setup`, saved in your password manager first: the app key,
|
|
18
|
+
# which signs the server's log entries, sessions and tokens.
|
|
19
|
+
APP_KEY=
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// coffre's server: the API, sign-in, pages and scheduled job, in one
|
|
2
|
-
// process. It holds no
|
|
2
|
+
// process. It holds no vault key: it asks the vault, a process of its own
|
|
3
3
|
// (src/vault.ts), over a Unix socket. Settings come from server.env.
|
|
4
4
|
import { github, serve, signin } from '@coffre/server/node';
|
|
5
5
|
import { connectVault } from '@coffre/vault/node';
|
|
@@ -16,7 +16,7 @@ const server = await serve({
|
|
|
16
16
|
database: env('DATABASE_URL'),
|
|
17
17
|
vault: connectVault(env('VAULT_SOCKET')),
|
|
18
18
|
// For tests or local SQLite development only, the vault in this process:
|
|
19
|
-
// vault: await localVault({ database: env('DATABASE_URL'), kek: …, rootAdmins: […]
|
|
19
|
+
// vault: await localVault({ database: env('DATABASE_URL'), kek: …, rootAdmins: […] }),
|
|
20
20
|
auth: signin({
|
|
21
21
|
providers: [
|
|
22
22
|
github({
|
|
@@ -28,7 +28,7 @@ const server = await serve({
|
|
|
28
28
|
}),
|
|
29
29
|
],
|
|
30
30
|
}),
|
|
31
|
-
auditChainKey: env('
|
|
31
|
+
auditChainKey: env('APP_KEY'),
|
|
32
32
|
});
|
|
33
33
|
console.log(`coffre is listening on ${server.url}`);
|
|
34
34
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The vault: the keys, and the members and grants, which it keeps in the
|
|
2
2
|
// server's database through a login of its own. It answers only on a Unix
|
|
3
3
|
// socket, which it makes 0660: run it as its own user, sharing a group with
|
|
4
|
-
// the server's, and nothing that faces the network can read the
|
|
4
|
+
// the server's, and nothing that faces the network can read the vault key.
|
|
5
5
|
// Settings come from vault.env.
|
|
6
6
|
import { serveVault } from '@coffre/vault/node';
|
|
7
7
|
|
|
@@ -14,11 +14,13 @@ function env(name: string): string {
|
|
|
14
14
|
const vault = await serveVault({
|
|
15
15
|
socket: env('VAULT_SOCKET'),
|
|
16
16
|
database: env('DATABASE_URL'),
|
|
17
|
-
kek: { id: env('
|
|
18
|
-
// After a rotation, the
|
|
19
|
-
// still open
|
|
17
|
+
kek: { id: env('VAULT_KEY_ID'), key: env('VAULT_KEY') },
|
|
18
|
+
// After a rotation, the vault keys before it, so the data keys they wrapped
|
|
19
|
+
// still open and the records signed under them before it still verify:
|
|
20
|
+
// previousKeks: [{ id: 'vault-2026-04-01-k7q2xm', key: env('OLD_VAULT_KEY') }],
|
|
20
21
|
rootAdmins: env('ROOT_ADMINS').split(',').map((email) => email.trim()),
|
|
21
|
-
|
|
22
|
+
// The vault derives its signing key from the vault key. With a key a key
|
|
23
|
+
// service 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,15 @@
|
|
|
2
2
|
# the vault's user alone.
|
|
3
3
|
|
|
4
4
|
VAULT_SOCKET=vault.sock
|
|
5
|
-
#
|
|
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
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
# From `coffre setup`, saved in your password manager first: the vault key,
|
|
9
|
+
# which decrypts every value, and its ID, which is not secret. A rotation
|
|
10
|
+
# gives the new key a new ID. The vault derives the key it signs its
|
|
11
|
+
# records with from it.
|
|
12
|
+
VAULT_KEY_ID=vault-1
|
|
13
|
+
VAULT_KEY=
|
|
14
14
|
|
|
15
15
|
# Comma-separated emails: the first people in, whom nobody can remove.
|
|
16
16
|
ROOT_ADMINS=you@example.com
|
|
@@ -17,76 +17,66 @@ 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 `
|
|
20
|
+
and `VAULT_KEY_ID`, which `coffre setup` gives you below.
|
|
21
21
|
|
|
22
|
-
## 2. The database
|
|
22
|
+
## 2. The database and keys
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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, and checks that
|
|
35
|
+
each login holds only its rights.
|
|
36
|
+
|
|
37
|
+
Then it shows five values on a screen of their own, which leaves nothing
|
|
38
|
+
behind in your scrollback: the app key and the app's database URL, and the
|
|
39
|
+
vault ID, the vault key and the vault's database URL. Copy each into your
|
|
40
|
+
password manager with `c`, beside the GitHub client secret; `w` shows where
|
|
41
|
+
each one goes. Nothing keeps a copy. There is one key for each Worker, so
|
|
42
|
+
that the app, which faces the network, never holds what decrypts a value:
|
|
43
|
+
|
|
44
|
+
- `VAULT_KEY`, the vault key, decrypts every value, and the vault derives
|
|
45
|
+
from it the key it signs its records with. Lose it, and every value is
|
|
46
|
+
lost.
|
|
47
|
+
- `APP_KEY`, the app key, signs the app's log entries, sessions and tokens.
|
|
48
|
+
Lose it, and everyone is signed out and the log stops verifying.
|
|
49
|
+
- `VAULT_KEY_ID`, the vault ID, names the vault key. It is not secret.
|
|
50
|
+
|
|
51
|
+
To do the same by hand, see
|
|
52
|
+
[deploy.md](https://github.com/erwinkn/coffre/blob/main/docs/deploy.md#appendix-the-database-by-hand).
|
|
53
|
+
|
|
54
|
+
## 3. Hyperdrive and secrets
|
|
55
|
+
|
|
56
|
+
Run the two `wrangler hyperdrive create` commands from setup's screen: one
|
|
57
|
+
config per login, each `--caching-disabled`. Hyperdrive otherwise caches
|
|
58
|
+
reads for up to a minute, and a revoked token or a signed-out session could
|
|
59
|
+
keep working that long. `wrangler.jsonc` cannot set it, so for a config made
|
|
60
|
+
another way, check `caching` in `wrangler hyperdrive get <id>`.
|
|
55
61
|
|
|
56
62
|
Put the ids they print under `hyperdrive`, the first in
|
|
57
|
-
`app/wrangler.jsonc` and the second in `vault/wrangler.jsonc
|
|
58
|
-
`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
## 3. Keys
|
|
63
|
+
`app/wrangler.jsonc` and the second in `vault/wrangler.jsonc`, and
|
|
64
|
+
`VAULT_KEY_ID` in `vault/wrangler.jsonc`. Then set the secrets; each command
|
|
65
|
+
prompts for the saved value:
|
|
62
66
|
|
|
63
67
|
```sh
|
|
64
|
-
|
|
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:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
pnpm exec wrangler secret put KEK -c vault/wrangler.jsonc
|
|
84
|
-
pnpm exec wrangler secret put SIGNING_KEY -c vault/wrangler.jsonc
|
|
85
|
-
pnpm exec wrangler secret put AUDIT_CHAIN_KEY -c app/wrangler.jsonc
|
|
68
|
+
pnpm exec wrangler secret put VAULT_KEY -c vault/wrangler.jsonc
|
|
69
|
+
pnpm exec wrangler secret put APP_KEY -c app/wrangler.jsonc
|
|
86
70
|
pnpm exec wrangler secret put GITHUB_CLIENT_SECRET -c app/wrangler.jsonc
|
|
87
71
|
```
|
|
88
72
|
|
|
89
|
-
|
|
73
|
+
Run `pnpm migrate`, with the administrator's URL in `DATABASE_URL`, after
|
|
74
|
+
every upgrade of `@coffre/server`, before deploying it.
|
|
75
|
+
|
|
76
|
+
Keep older vault keys after a rotation, for good: what they wrapped still
|
|
77
|
+
needs them, and so does what the vault signed under them before it. With AWS KMS
|
|
78
|
+
instead of a key of your own, the vault also needs a `SIGNING_KEY`
|
|
79
|
+
([keys](https://github.com/erwinkn/coffre/blob/main/docs/keys.md#aws-kms)).
|
|
90
80
|
|
|
91
81
|
## 4. Deploy
|
|
92
82
|
|
|
@@ -12,7 +12,7 @@ type Env = {
|
|
|
12
12
|
/** Set for GitHub Enterprise Server; github.com otherwise. */
|
|
13
13
|
GITHUB_URL?: string;
|
|
14
14
|
GITHUB_API_URL?: string;
|
|
15
|
-
|
|
15
|
+
APP_KEY: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export default coffre((env: Env) => ({
|
|
@@ -29,5 +29,5 @@ export default coffre((env: Env) => ({
|
|
|
29
29
|
}),
|
|
30
30
|
],
|
|
31
31
|
}),
|
|
32
|
-
auditChainKey: env.
|
|
32
|
+
auditChainKey: env.APP_KEY,
|
|
33
33
|
}));
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"GITHUB_CLIENT_ID": "replace-with-your-client-id"
|
|
18
18
|
},
|
|
19
19
|
"secrets": {
|
|
20
|
-
//
|
|
21
|
-
"required": ["GITHUB_CLIENT_SECRET", "
|
|
20
|
+
// APP_KEY is from `coffre setup`; the app never gets the vault's keys.
|
|
21
|
+
"required": ["GITHUB_CLIENT_SECRET", "APP_KEY"]
|
|
22
22
|
},
|
|
23
|
-
// `wrangler hyperdrive create coffre --caching-disabled
|
|
24
|
-
// prints the id. Caching must stay off, or a revoked session keeps working
|
|
23
|
+
// `wrangler hyperdrive create coffre --caching-disabled`, as `coffre setup`
|
|
24
|
+
// shows it, prints the id. Caching must stay off, or a revoked session keeps working
|
|
25
25
|
// for about a minute, and this file cannot set it: see README.md. Locally,
|
|
26
26
|
// CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE stands in for
|
|
27
27
|
// the config.
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"conformance": "coffre-conformance workers"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@coffre/server": "0.1.
|
|
18
|
-
"@coffre/ui": "0.1.
|
|
19
|
-
"@coffre/vault": "0.1.
|
|
17
|
+
"@coffre/server": "0.1.3",
|
|
18
|
+
"@coffre/ui": "0.1.3",
|
|
19
|
+
"@coffre/vault": "0.1.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@cloudflare/workers-types": "5.20260920.1",
|
|
23
|
-
"@coffre/conformance": "0.1.
|
|
23
|
+
"@coffre/conformance": "0.1.3",
|
|
24
24
|
"typescript": "5.9.3",
|
|
25
25
|
"wrangler": "4.118.0"
|
|
26
26
|
}
|
|
@@ -5,17 +5,18 @@ import { postgres, vault } from '@coffre/vault/cloudflare';
|
|
|
5
5
|
|
|
6
6
|
type Env = {
|
|
7
7
|
VAULT_HYPERDRIVE: Hyperdrive;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
SIGNING_KEY: string;
|
|
8
|
+
VAULT_KEY_ID: string;
|
|
9
|
+
VAULT_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
|
-
kek: { id: env.
|
|
17
|
-
// After a rotation, the
|
|
18
|
-
// still open
|
|
15
|
+
kek: { id: env.VAULT_KEY_ID, key: env.VAULT_KEY },
|
|
16
|
+
// After a rotation, the vault keys before it, so the data keys they wrapped
|
|
17
|
+
// still open and the records signed under them before it still verify:
|
|
18
|
+
// previousKeks: [{ id: 'vault-2026-04-01-k7q2xm', key: env.OLD_VAULT_KEY }],
|
|
19
19
|
rootAdmins: env.ROOT_ADMINS.split(',').map((email) => email.trim()),
|
|
20
|
-
|
|
20
|
+
// The vault derives its signing key from the vault key. With a key a key
|
|
21
|
+
// service holds, such as awsKms(…), it needs one of its own: signingKey: env.SIGNING_KEY.
|
|
21
22
|
}));
|
|
@@ -11,18 +11,20 @@
|
|
|
11
11
|
"workers_dev": false,
|
|
12
12
|
"observability": { "enabled": true },
|
|
13
13
|
"vars": {
|
|
14
|
-
// Names the
|
|
15
|
-
|
|
14
|
+
// Names the vault key below, and is not secret: from `coffre setup`. A
|
|
15
|
+
// rotation gives the new key a new ID.
|
|
16
|
+
"VAULT_KEY_ID": "vault-1",
|
|
16
17
|
// Comma-separated emails: the first people in, whom nobody can remove.
|
|
17
18
|
"ROOT_ADMINS": "you@example.com"
|
|
18
19
|
},
|
|
19
20
|
"secrets": {
|
|
20
|
-
//
|
|
21
|
-
|
|
21
|
+
// The vault key, from `coffre setup`: wrangler secret put VAULT_KEY. The
|
|
22
|
+
// vault derives the key it signs its records with from it.
|
|
23
|
+
"required": ["VAULT_KEY"]
|
|
22
24
|
},
|
|
23
25
|
// The app's database, as the vault's own login: `wrangler hyperdrive
|
|
24
|
-
// create coffre-vault --caching-disabled
|
|
25
|
-
//
|
|
26
|
+
// create coffre-vault --caching-disabled`, as `coffre setup` shows it,
|
|
27
|
+
// prints the id. Its own config, not the app's: the vault writes
|
|
26
28
|
// members and grants, which the app's login may only read. Locally,
|
|
27
29
|
// CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_VAULT_HYPERDRIVE stands in.
|
|
28
30
|
"hyperdrive": [{ "binding": "VAULT_HYPERDRIVE", "id": "replace-with-your-vault-hyperdrive-id" }]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffre/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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/
|
|
29
|
-
"@coffre/
|
|
30
|
+
"@coffre/client": "0.1.3",
|
|
31
|
+
"@coffre/core": "0.1.3",
|
|
32
|
+
"@coffre/db": "0.1.3"
|
|
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
|
}
|