@sharedenv/cli 1.1.0 → 2.0.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 +121 -56
- package/bin/sharedenv.js +711 -213
- package/lib/api.js +20 -0
- package/lib/auth.js +12 -1
- package/lib/crypto/kt.js +127 -2
- package/lib/crypto/safeKey.js +13 -0
- package/lib/crypto.js +3 -2
- package/lib/link.js +46 -0
- package/lib/safes.js +188 -42
- package/lib/shares.js +42 -15
- package/lib/sharing.js +112 -0
- package/lib/transparency.js +116 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ machine: sharedenv itself never sees a single secret.
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -g @sharedenv/cli
|
|
15
15
|
sharedenv login
|
|
16
|
-
sharedenv
|
|
16
|
+
sharedenv link # once per project: pick its vault
|
|
17
|
+
sharedenv run -- npm start # your app, with the vault's secrets
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
## Why teams use it
|
|
@@ -31,8 +32,8 @@ sharedenv run prod -- npm start
|
|
|
31
32
|
|
|
32
33
|
## Get started in two minutes
|
|
33
34
|
|
|
34
|
-
1. **Create your account** on [sharedenv.com](https://sharedenv.com)
|
|
35
|
-
|
|
35
|
+
1. **Create your account** on [sharedenv.com](https://sharedenv.com). It is free to start,
|
|
36
|
+
no credit card.
|
|
36
37
|
2. **Install the CLI** (Node.js 20.19 or later):
|
|
37
38
|
```bash
|
|
38
39
|
npm install -g @sharedenv/cli
|
|
@@ -42,20 +43,61 @@ sharedenv run prod -- npm start
|
|
|
42
43
|
```bash
|
|
43
44
|
sharedenv login
|
|
44
45
|
```
|
|
45
|
-
4. **
|
|
46
|
+
4. **Link your project to a vault**, from the project's directory:
|
|
46
47
|
```bash
|
|
47
|
-
sharedenv
|
|
48
|
-
sharedenv
|
|
48
|
+
sharedenv create my-app-dev # a new vault, or pick one you already have:
|
|
49
|
+
sharedenv link # lists your vaults, you choose
|
|
49
50
|
```
|
|
51
|
+
5. **Use it:**
|
|
52
|
+
```bash
|
|
53
|
+
sharedenv set DATABASE_URL=postgres://localhost/app
|
|
54
|
+
sharedenv run -- npm start
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Which vault a command works on
|
|
58
|
+
|
|
59
|
+
You may have many vaults: your own, the ones people shared with you, the ones your teams
|
|
60
|
+
reach. A command never guesses which one you mean.
|
|
61
|
+
|
|
62
|
+
- **In a project**, `sharedenv link` writes `.sharedenv.json` in its directory. It holds the
|
|
63
|
+
vault's id and nothing else, no secret: commit it, and your whole team works on the same
|
|
64
|
+
vault. Every command run in that directory, or below it, uses that vault.
|
|
65
|
+
- **Anywhere**, `-v <name or id>` names the vault: `sharedenv get API_KEY -v staging`.
|
|
66
|
+
- **A name two of your vaults share is refused**, with both listed (id, owner, teams): give
|
|
67
|
+
the id instead. `sharedenv create` refuses a name you already have.
|
|
68
|
+
- **Every command says which vault it used**, on stderr: a link in a repository you cloned
|
|
69
|
+
might not name the vault you expect.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sharedenv vaults # every vault you can open: id, your access, owner, teams
|
|
73
|
+
sharedenv show # this vault: members, teams, names of its keys
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`vaults` and `show` never ask for your master password: vault and key names are stored in
|
|
77
|
+
clear, only values are encrypted.
|
|
50
78
|
|
|
51
79
|
## Everyday use
|
|
52
80
|
|
|
81
|
+
### Keys
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
sharedenv set API_KEY=sk_test_1 DEBUG=1 # add or replace
|
|
85
|
+
sharedenv set TLS_KEY < key.pem # value from stdin
|
|
86
|
+
sharedenv set STRIPE_KEY # value asked, not echoed
|
|
87
|
+
sharedenv get API_KEY # one value, on stdout
|
|
88
|
+
sharedenv unset DEBUG # remove
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A key given without a value has its value asked without echo, or read from stdin: it lands
|
|
92
|
+
neither in your shell history nor in the process list. `get` prints the value as is, with
|
|
93
|
+
nothing added when the output is a pipe or a file (`export API_KEY=$(sharedenv get API_KEY)`).
|
|
94
|
+
|
|
53
95
|
### Run anything with its secrets
|
|
54
96
|
|
|
55
97
|
```bash
|
|
56
|
-
sharedenv run
|
|
57
|
-
sharedenv run staging -- docker compose up
|
|
58
|
-
sharedenv run prod -- python manage.py migrate
|
|
98
|
+
sharedenv run -- npm start
|
|
99
|
+
sharedenv run -v staging -- docker compose up
|
|
100
|
+
sharedenv run -v prod -- python manage.py migrate
|
|
59
101
|
```
|
|
60
102
|
|
|
61
103
|
The secrets reach the command as environment variables, in memory. The command's exit code
|
|
@@ -67,9 +109,9 @@ replaced behind your back: add `--override-env` when you want the vault's value.
|
|
|
67
109
|
### Get your secrets as a file, or as JSON
|
|
68
110
|
|
|
69
111
|
```bash
|
|
70
|
-
sharedenv pull
|
|
71
|
-
sharedenv pull
|
|
72
|
-
sharedenv pull
|
|
112
|
+
sharedenv pull # dotenv, on stdout
|
|
113
|
+
sharedenv pull --out .env # the same, in a file only you can read
|
|
114
|
+
sharedenv pull --format json # JSON, for scripts and programs
|
|
73
115
|
```
|
|
74
116
|
|
|
75
117
|
stdout carries the secrets and nothing else (questions and messages go to stderr), so
|
|
@@ -78,7 +120,7 @@ ask for your master password: set `SHAREDENV_PASSWORD`.
|
|
|
78
120
|
|
|
79
121
|
#### dotenv (the default)
|
|
80
122
|
|
|
81
|
-
For a vault holding five secrets, `sharedenv pull
|
|
123
|
+
For a vault holding five secrets, `sharedenv pull` prints:
|
|
82
124
|
|
|
83
125
|
```dotenv
|
|
84
126
|
APP_NAME='Acme billing'
|
|
@@ -119,12 +161,12 @@ One exception: `docker run --env-file` reads values literally (it would keep the
|
|
|
119
161
|
and refuses multi-line values. Hand the variables to Docker through `run` instead:
|
|
120
162
|
|
|
121
163
|
```bash
|
|
122
|
-
sharedenv run
|
|
164
|
+
sharedenv run -- docker run -e DATABASE_URL -e STRIPE_KEY my-app
|
|
123
165
|
```
|
|
124
166
|
|
|
125
167
|
#### JSON
|
|
126
168
|
|
|
127
|
-
`sharedenv pull
|
|
169
|
+
`sharedenv pull --format json` prints one object, names sorted, every value a string:
|
|
128
170
|
|
|
129
171
|
```json
|
|
130
172
|
{
|
|
@@ -136,12 +178,6 @@ sharedenv run prod -- docker run -e DATABASE_URL -e STRIPE_KEY my-app
|
|
|
136
178
|
}
|
|
137
179
|
```
|
|
138
180
|
|
|
139
|
-
One value, from a shell:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
sharedenv pull prod --format json | jq -r .DATABASE_URL
|
|
143
|
-
```
|
|
144
|
-
|
|
145
181
|
From a program:
|
|
146
182
|
|
|
147
183
|
```js
|
|
@@ -149,7 +185,7 @@ From a program:
|
|
|
149
185
|
import { execFileSync } from 'node:child_process';
|
|
150
186
|
|
|
151
187
|
const secrets = JSON.parse(
|
|
152
|
-
execFileSync('sharedenv', ['pull', 'prod', '--format', 'json'], { encoding: 'utf8' })
|
|
188
|
+
execFileSync('sharedenv', ['pull', '-v', 'prod', '--format', 'json'], { encoding: 'utf8' })
|
|
153
189
|
);
|
|
154
190
|
console.log(secrets.DATABASE_URL);
|
|
155
191
|
```
|
|
@@ -161,7 +197,7 @@ import subprocess
|
|
|
161
197
|
|
|
162
198
|
secrets = json.loads(
|
|
163
199
|
subprocess.run(
|
|
164
|
-
["sharedenv", "pull", "prod", "--format", "json"],
|
|
200
|
+
["sharedenv", "pull", "-v", "prod", "--format", "json"],
|
|
165
201
|
check=True, capture_output=True, text=True,
|
|
166
202
|
).stdout
|
|
167
203
|
)
|
|
@@ -171,19 +207,33 @@ print(secrets["DATABASE_URL"])
|
|
|
171
207
|
If the command fails (wrong password, unknown vault, no network), it exits with a non-zero
|
|
172
208
|
code and says why on stderr: `execFileSync` throws, and `check=True` raises.
|
|
173
209
|
|
|
210
|
+
### Share a vault
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
sharedenv share alice@example.com # read access
|
|
214
|
+
sharedenv share alice@example.com --write # or --admin
|
|
215
|
+
sharedenv share --team backend # a team you are a member of
|
|
216
|
+
sharedenv teams # your organizations and their teams
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Sharing with a person shows the fingerprint of their key: compare it with them over another
|
|
220
|
+
channel (a call, a chat) before you say yes, they read theirs with `sharedenv fingerprint`.
|
|
221
|
+
The share then waits for their answer. Removing someone's access means changing the vault's
|
|
222
|
+
key and re-encrypting every secret: that is done in the web app, as is managing teams and
|
|
223
|
+
organizations.
|
|
224
|
+
|
|
174
225
|
### Accept what your team shares with you
|
|
175
226
|
|
|
176
227
|
When a teammate shares a vault with you, it waits for your answer:
|
|
177
228
|
|
|
178
229
|
```bash
|
|
179
|
-
sharedenv
|
|
230
|
+
sharedenv vaults # the shares waiting for you, with who sent them
|
|
180
231
|
sharedenv accept staging # shows the sender's key fingerprint, then asks
|
|
181
232
|
sharedenv decline staging
|
|
182
233
|
```
|
|
183
234
|
|
|
184
|
-
Before you accept, compare the fingerprint with the sender over another channel
|
|
185
|
-
|
|
186
|
-
`sharedenv fingerprint`.
|
|
235
|
+
Before you accept, compare the fingerprint with the sender over another channel. It proves
|
|
236
|
+
the share really comes from them.
|
|
187
237
|
|
|
188
238
|
### Sign out
|
|
189
239
|
|
|
@@ -194,7 +244,8 @@ sharedenv logout # closes the session on our side too
|
|
|
194
244
|
## In CI/CD
|
|
195
245
|
|
|
196
246
|
The CLI asks nothing when there is no terminal: give it what it would ask for, from your
|
|
197
|
-
CI's secret store.
|
|
247
|
+
CI's secret store. Name the vault by its id (`sharedenv vaults` shows it), or commit the
|
|
248
|
+
project's `.sharedenv.json`.
|
|
198
249
|
|
|
199
250
|
**GitHub Actions**
|
|
200
251
|
|
|
@@ -204,7 +255,7 @@ CI's secret store.
|
|
|
204
255
|
env:
|
|
205
256
|
SHAREDENV_IDENTIFIER: ${{ secrets.SHAREDENV_IDENTIFIER }}
|
|
206
257
|
SHAREDENV_PASSWORD: ${{ secrets.SHAREDENV_PASSWORD }}
|
|
207
|
-
- run: sharedenv run
|
|
258
|
+
- run: sharedenv run -v 6721f0c2a8e4b1d3c5f70912 -- ./deploy.sh
|
|
208
259
|
env:
|
|
209
260
|
SHAREDENV_PASSWORD: ${{ secrets.SHAREDENV_PASSWORD }}
|
|
210
261
|
```
|
|
@@ -216,19 +267,19 @@ deploy:
|
|
|
216
267
|
script:
|
|
217
268
|
- npm install -g @sharedenv/cli
|
|
218
269
|
- sharedenv login
|
|
219
|
-
- sharedenv run
|
|
220
|
-
# SHAREDENV_IDENTIFIER and
|
|
270
|
+
- sharedenv run -v "$SHAREDENV_VAULT" -- ./deploy.sh
|
|
271
|
+
# SHAREDENV_IDENTIFIER, SHAREDENV_PASSWORD and SHAREDENV_VAULT: masked CI/CD variables
|
|
221
272
|
```
|
|
222
273
|
|
|
223
274
|
**Docker** (the secrets are fetched when the container starts, never baked into the image)
|
|
224
275
|
|
|
225
276
|
```dockerfile
|
|
226
277
|
RUN npm install -g @sharedenv/cli
|
|
227
|
-
CMD ["sh", "-c", "sharedenv login && exec sharedenv run
|
|
278
|
+
CMD ["sh", "-c", "sharedenv login && exec sharedenv run -v \"$SHAREDENV_VAULT\" -- node server.js"]
|
|
228
279
|
```
|
|
229
280
|
|
|
230
281
|
```bash
|
|
231
|
-
docker run -e SHAREDENV_IDENTIFIER -e SHAREDENV_PASSWORD my-app
|
|
282
|
+
docker run -e SHAREDENV_IDENTIFIER -e SHAREDENV_PASSWORD -e SHAREDENV_VAULT my-app
|
|
232
283
|
```
|
|
233
284
|
|
|
234
285
|
Good practice for pipelines:
|
|
@@ -246,13 +297,16 @@ Good practice for pipelines:
|
|
|
246
297
|
- **Your password and your private key never leave your machine.** Signing in sends a
|
|
247
298
|
verifier derived from your password, never the password. Your private key is unlocked in
|
|
248
299
|
memory for the command, and never written anywhere.
|
|
249
|
-
- **You know who shares with you.** A share opens only if it was
|
|
250
|
-
the CLI remembers the keys it has seen: if someone's key ever
|
|
300
|
+
- **You know who shares with you, and whom you share with.** A share opens only if it was
|
|
301
|
+
sealed by its sender, and the CLI remembers the keys it has seen: if someone's key ever
|
|
302
|
+
changes, it stops and asks.
|
|
251
303
|
- **A key cannot be swapped in our database without a trace.** Every public key the server
|
|
252
304
|
hands out is first written to a log that only grows, each entry chained to the previous
|
|
253
|
-
one by its hash. The CLI
|
|
254
|
-
warns you when a key that is not yours is logged under your
|
|
255
|
-
|
|
305
|
+
one by its hash. The CLI seals to a key, or trusts its seal, only if it is the last one
|
|
306
|
+
logged for its owner; it warns you when a key that is not yours is logged under your
|
|
307
|
+
name, or under the name of a team whose key you hold, and refuses a log that rewrites
|
|
308
|
+
what it already checked. A warning is repeated at every unlock until you acknowledge it
|
|
309
|
+
with `sharedenv log --ack`.
|
|
256
310
|
- **A vault cannot take over your machine.** Anyone who can edit a vault chooses its
|
|
257
311
|
values, so `run` holds back variables that would make a program run code or reroute its
|
|
258
312
|
traffic (`LD_*`, `PATH`, `NODE_OPTIONS`, proxies, custom certificates...) unless you pass
|
|
@@ -263,8 +317,7 @@ Good practice for pipelines:
|
|
|
263
317
|
What it does not protect against, said plainly:
|
|
264
318
|
|
|
265
319
|
- **Names.** Vault names and secret names (like `DATABASE_URL`) are stored in clear, so
|
|
266
|
-
that they can be listed and organized; values
|
|
267
|
-
them, are encrypted. Never put a secret in a name.
|
|
320
|
+
that they can be listed and organized; values are encrypted. Never put a secret in a name.
|
|
268
321
|
- **A swap is caught, not prevented.** Someone who can write to our database could still
|
|
269
322
|
log a key of their own: the log keeps the trace, and its owner is warned the next time
|
|
270
323
|
they unlock. Someone who controls our servers themselves could show you a different log.
|
|
@@ -277,18 +330,29 @@ What it does not protect against, said plainly:
|
|
|
277
330
|
|
|
278
331
|
## Reference
|
|
279
332
|
|
|
280
|
-
| Command
|
|
281
|
-
|
|
|
282
|
-
| `sharedenv login [--api <url>] [--identifier <id>]`
|
|
283
|
-
| `sharedenv
|
|
284
|
-
| `sharedenv
|
|
285
|
-
| `sharedenv
|
|
286
|
-
| `sharedenv
|
|
287
|
-
| `sharedenv
|
|
288
|
-
| `sharedenv
|
|
289
|
-
| `sharedenv
|
|
290
|
-
|
|
291
|
-
|
|
333
|
+
| Command | What it does |
|
|
334
|
+
| ------------------------------------------------------- | ----------------------------------------------------------- |
|
|
335
|
+
| `sharedenv login [--api <url>] [--identifier <id>]` | Sign in. Keeps the session, nothing else. |
|
|
336
|
+
| `sharedenv logout` | Close the session, on our side and on this machine. |
|
|
337
|
+
| `sharedenv vaults` | Your vaults, then the shares waiting for your answer. |
|
|
338
|
+
| `sharedenv create <name>` | Create a vault (prints its id). |
|
|
339
|
+
| `sharedenv link [<vault>]` | Link this directory to a vault (`.sharedenv.json`). |
|
|
340
|
+
| `sharedenv show` | The vault: owner, members, teams, names of its keys. |
|
|
341
|
+
| `sharedenv get <KEY>` | Print one value. |
|
|
342
|
+
| `sharedenv set <KEY>=<value>... [<KEY>]` | Add or replace keys. |
|
|
343
|
+
| `sharedenv unset <KEY>...` | Remove keys. |
|
|
344
|
+
| `sharedenv run -- <command>` | Run a command with the vault's secrets in its environment. |
|
|
345
|
+
| `sharedenv pull [--out <file>] [--format dotenv\|json]` | Print or write every secret. |
|
|
346
|
+
| `sharedenv share <email> [--write\|--admin]` | Share the vault with a person. |
|
|
347
|
+
| `sharedenv share --team <team> [--write\|--admin]` | Share the vault with one of your teams. |
|
|
348
|
+
| `sharedenv accept <vault> [--team <team>]` | Accept a share after checking who sealed it. |
|
|
349
|
+
| `sharedenv decline <vault> [--team <team>]` | Decline a share. |
|
|
350
|
+
| `sharedenv teams` | Your organizations and their teams. |
|
|
351
|
+
| `sharedenv fingerprint` | Your key fingerprint, to read to a teammate. |
|
|
352
|
+
| `sharedenv log [--ack]` | The public key log warnings kept here, or acknowledge them. |
|
|
353
|
+
|
|
354
|
+
Commands that work on a vault take `-v <name or id>`, or use the vault the current
|
|
355
|
+
directory is linked to. `sharedenv help <command>` lists every option.
|
|
292
356
|
|
|
293
357
|
| Environment variable | Use |
|
|
294
358
|
| ---------------------- | ------------------------------------------------------- |
|
|
@@ -300,11 +364,12 @@ What it does not protect against, said plainly:
|
|
|
300
364
|
|
|
301
365
|
The CLI keeps three files there, all readable by you only: `session.json` (your session),
|
|
302
366
|
`pins.json` (the public keys this machine has checked) and `log.json` (how far it checked
|
|
303
|
-
the public key log). Never a secret, never your
|
|
367
|
+
the public key log, and the warnings not acknowledged yet). Never a secret, never your
|
|
368
|
+
password, never your private key.
|
|
304
369
|
|
|
305
|
-
Exit codes: `0` success, `1` error, `2` wrong usage
|
|
306
|
-
command's own code, `128 + n` when signal `n` ended it,
|
|
307
|
-
exist.
|
|
370
|
+
Exit codes: `0` success, `1` error, `2` wrong usage (an ambiguous vault name included),
|
|
371
|
+
`130` cancelled. With `run`: the command's own code, `128 + n` when signal `n` ended it,
|
|
372
|
+
`127` when the command does not exist.
|
|
308
373
|
|
|
309
374
|
## License
|
|
310
375
|
|