@better-forge/vault 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/createVault.d.ts +70 -0
- package/dist/createVault.d.ts.map +1 -0
- package/dist/createVault.js +28 -0
- package/dist/createVault.js.map +1 -0
- package/dist/hashicorp.d.ts +92 -0
- package/dist/hashicorp.d.ts.map +1 -0
- package/dist/hashicorp.js +436 -0
- package/dist/hashicorp.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +28 -0
- package/dist/migrations.js.map +1 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +5 -0
- package/dist/module.js.map +1 -0
- package/dist/resolveSecret.d.ts +17 -0
- package/dist/resolveSecret.d.ts.map +1 -0
- package/dist/resolveSecret.js +80 -0
- package/dist/resolveSecret.js.map +1 -0
- package/dist/sealed.d.ts +45 -0
- package/dist/sealed.d.ts.map +1 -0
- package/dist/sealed.js +20 -0
- package/dist/sealed.js.map +1 -0
- package/dist/secretBox.d.ts +17 -0
- package/dist/secretBox.d.ts.map +1 -0
- package/dist/secretBox.js +63 -0
- package/dist/secretBox.js.map +1 -0
- package/dist/secretFields.d.ts +48 -0
- package/dist/secretFields.d.ts.map +1 -0
- package/dist/secretFields.js +159 -0
- package/dist/secretFields.js.map +1 -0
- package/dist/tables.d.ts +267 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +22 -0
- package/dist/tables.js.map +1 -0
- package/dist/vaultRoutes.d.ts +42 -0
- package/dist/vaultRoutes.d.ts.map +1 -0
- package/dist/vaultRoutes.js +408 -0
- package/dist/vaultRoutes.js.map +1 -0
- package/dist/vaultUse.d.ts +15 -0
- package/dist/vaultUse.d.ts.map +1 -0
- package/dist/vaultUse.js +39 -0
- package/dist/vaultUse.js.map +1 -0
- package/package.json +54 -4
- package/src/createVault.ts +45 -0
- package/src/hashicorp.ts +466 -0
- package/src/index.ts +9 -0
- package/src/migrations.ts +29 -0
- package/src/module.ts +6 -0
- package/src/resolveSecret.ts +96 -0
- package/src/sealed.ts +38 -0
- package/src/secretBox.ts +72 -0
- package/src/secretFields.ts +177 -0
- package/src/tables.ts +23 -0
- package/src/vaultRoutes.ts +422 -0
- package/src/vaultUse.ts +41 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { vaults, vaultSecrets } from './tables.js';
|
|
3
|
+
import { HashicorpVaultError, encodePath, parseConnection, publicConnection } from './hashicorp.js';
|
|
4
|
+
const VALID_SECRET_TYPES = ['userpass', 'value', 'json'];
|
|
5
|
+
const DUPLICATE_SECRET_ERROR = 'A secret with this name already exists in the vault';
|
|
6
|
+
/** Drizzle wraps driver errors ("Failed query: …"); the SQLite constraint text lives on `cause`. */
|
|
7
|
+
function isUniqueViolation(error) {
|
|
8
|
+
for (let current = error; current instanceof Error; current = current.cause) {
|
|
9
|
+
if (current.message.includes('UNIQUE constraint failed'))
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
export function createVaultRoutes(ctx, parts) {
|
|
15
|
+
const { db, audit } = ctx;
|
|
16
|
+
const { encrypt, decrypt } = ctx.secretBox;
|
|
17
|
+
const requestIdentity = ctx.identity;
|
|
18
|
+
const { loadSecretPayload } = parts.resolver;
|
|
19
|
+
const { forgetHashicorpVault, openConnection, readKvSecret, sealConnection, testConnection, testDraftConnection, verifyCredentialsRef, forgetCredentialDependents } = parts.hashicorp;
|
|
20
|
+
function serializePayload(type, payload) {
|
|
21
|
+
if (type === 'userpass') {
|
|
22
|
+
if (!payload.userpass)
|
|
23
|
+
throw new Error('userpass requires username and password');
|
|
24
|
+
return JSON.stringify(payload.userpass);
|
|
25
|
+
}
|
|
26
|
+
if (type === 'value') {
|
|
27
|
+
if (payload.value === undefined)
|
|
28
|
+
throw new Error('value is required');
|
|
29
|
+
return JSON.stringify({ value: payload.value });
|
|
30
|
+
}
|
|
31
|
+
if (type === 'json') {
|
|
32
|
+
if (payload.json === undefined)
|
|
33
|
+
throw new Error('json is required');
|
|
34
|
+
// Validate it's actually JSON
|
|
35
|
+
JSON.parse(payload.json);
|
|
36
|
+
return JSON.stringify({ value: payload.json });
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Unknown type');
|
|
39
|
+
}
|
|
40
|
+
/** Validates a HashiCorp secret reference from the request body. */
|
|
41
|
+
function parseReference(type, body) {
|
|
42
|
+
if (typeof body.path !== 'string' || !body.path.trim())
|
|
43
|
+
throw new HashicorpVaultError('Path is required');
|
|
44
|
+
const path = body.path.trim().replace(/^\/+|\/+$/g, '');
|
|
45
|
+
encodePath(path, 'Path');
|
|
46
|
+
if (type !== 'value')
|
|
47
|
+
return { path };
|
|
48
|
+
if (typeof body.key !== 'string' || !body.key.trim())
|
|
49
|
+
throw new HashicorpVaultError('Key is required for a Secure Value');
|
|
50
|
+
return { path, key: body.key.trim() };
|
|
51
|
+
}
|
|
52
|
+
/** Fails when Vault cannot serve the reference, so typos surface while the admin is still in the form. */
|
|
53
|
+
async function verifyReference(vault, type, ref) {
|
|
54
|
+
const kv = await readKvSecret(vault, ref.path);
|
|
55
|
+
if (type === 'userpass' && (typeof kv['username'] !== 'string' || typeof kv['password'] !== 'string')) {
|
|
56
|
+
throw new HashicorpVaultError('keys "username" and "password" are required at the path');
|
|
57
|
+
}
|
|
58
|
+
if (type === 'value' && (kv[ref.key] === undefined || kv[ref.key] === null)) {
|
|
59
|
+
throw new HashicorpVaultError(`key "${ref.key}" not found`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Vault columns safe to return: never the encrypted connection. */
|
|
63
|
+
function publicVault(row) {
|
|
64
|
+
const { connectionConfig: _cc, ...safe } = row;
|
|
65
|
+
return safe;
|
|
66
|
+
}
|
|
67
|
+
/** Audit view of a HashiCorp connection; credentials are never included. */
|
|
68
|
+
function connectionAudit(row) {
|
|
69
|
+
if (row.type !== 'hashicorp')
|
|
70
|
+
return { name: row.name, type: row.type };
|
|
71
|
+
let c;
|
|
72
|
+
try {
|
|
73
|
+
c = publicConnection(openConnection(row.connectionConfig));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return { name: row.name, type: row.type }; // connection undecryptable (key changed): still allow repair and delete
|
|
77
|
+
}
|
|
78
|
+
return { name: row.name, type: row.type, address: c.address, namespace: c.namespace, mount: c.mount, authMethod: c.authMethod, approleMount: c.approleMount, credentialsRef: c.credentialsRef };
|
|
79
|
+
}
|
|
80
|
+
function safeDecrypt(encryptedValue) {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(decrypt(encryptedValue));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read-only vault catalogue: vault and secret names/types, never values. Whoever may configure things that use secrets gets it too so
|
|
90
|
+
* they can pick a secret; creating, editing and revealing secrets stays
|
|
91
|
+
* in {@link vaultRoutes} (admin only).
|
|
92
|
+
*/
|
|
93
|
+
async function vaultCatalogRoutes(app) {
|
|
94
|
+
app.get('/', async () => {
|
|
95
|
+
const rows = await db.select().from(vaults);
|
|
96
|
+
return rows.map(publicVault);
|
|
97
|
+
});
|
|
98
|
+
app.get('/:id/secrets', async (req, reply) => {
|
|
99
|
+
const vaultId = Number(req.params.id);
|
|
100
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
101
|
+
if (!vault)
|
|
102
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
103
|
+
const secrets = await db
|
|
104
|
+
.select({ id: vaultSecrets.id, vaultId: vaultSecrets.vaultId, name: vaultSecrets.name, type: vaultSecrets.type, createdAt: vaultSecrets.createdAt, updatedAt: vaultSecrets.updatedAt })
|
|
105
|
+
.from(vaultSecrets)
|
|
106
|
+
.where(eq(vaultSecrets.vaultId, vaultId));
|
|
107
|
+
return secrets;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function vaultRoutes(app) {
|
|
111
|
+
// ── Vaults ──────────────────────────────────────────────────────────────────
|
|
112
|
+
app.post('/', async (req, reply) => {
|
|
113
|
+
if (!req.body.name?.trim())
|
|
114
|
+
return reply.code(400).send({ error: 'Name is required' });
|
|
115
|
+
const type = req.body.type ?? 'local';
|
|
116
|
+
if (type !== 'local' && type !== 'hashicorp')
|
|
117
|
+
return reply.code(400).send({ error: 'Type must be local or hashicorp' });
|
|
118
|
+
let connectionConfig = null;
|
|
119
|
+
if (type === 'hashicorp') {
|
|
120
|
+
try {
|
|
121
|
+
const cfg = parseConnection(req.body.connection);
|
|
122
|
+
await verifyCredentialsRef(cfg);
|
|
123
|
+
connectionConfig = sealConnection(cfg);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
if (e instanceof HashicorpVaultError)
|
|
127
|
+
return reply.code(400).send({ error: e.message });
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
const [row] = await db.insert(vaults).values({
|
|
133
|
+
name: req.body.name.trim(),
|
|
134
|
+
type,
|
|
135
|
+
description: req.body.description?.trim() ?? null,
|
|
136
|
+
connectionConfig,
|
|
137
|
+
createdAt: now,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
}).returning();
|
|
140
|
+
const actor = requestIdentity(req);
|
|
141
|
+
audit.write(audit.actor(actor), 'create', 'vault', row.id, row.name, audit.snapshot(connectionAudit(row)));
|
|
142
|
+
return publicVault(row);
|
|
143
|
+
});
|
|
144
|
+
app.get('/:id', async (req, reply) => {
|
|
145
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, Number(req.params.id))))[0];
|
|
146
|
+
if (!vault)
|
|
147
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
148
|
+
if (vault.type !== 'hashicorp')
|
|
149
|
+
return publicVault(vault);
|
|
150
|
+
try {
|
|
151
|
+
return { ...publicVault(vault), connection: publicConnection(openConnection(vault.connectionConfig)) };
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
if (e instanceof HashicorpVaultError)
|
|
155
|
+
return reply.code(500).send({ error: e.message });
|
|
156
|
+
throw e;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// Tests settings from the form before they are saved. With vaultId, blank credentials fall back to the saved ones
|
|
160
|
+
// (parseConnection refuses that fallback when the connection target changed, so a saved secret never reaches a new server).
|
|
161
|
+
app.post('/test-connection', async (req, reply) => {
|
|
162
|
+
let cfg;
|
|
163
|
+
try {
|
|
164
|
+
let existing;
|
|
165
|
+
if (req.body.vaultId !== undefined) {
|
|
166
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, Number(req.body.vaultId))))[0];
|
|
167
|
+
if (!vault)
|
|
168
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
169
|
+
if (vault.type !== 'hashicorp')
|
|
170
|
+
return reply.code(400).send({ error: 'Only HashiCorp vaults have a connection to test' });
|
|
171
|
+
try {
|
|
172
|
+
existing = openConnection(vault.connectionConfig);
|
|
173
|
+
}
|
|
174
|
+
catch { /* undecryptable: full credentials required */ }
|
|
175
|
+
}
|
|
176
|
+
cfg = parseConnection(req.body.connection, existing);
|
|
177
|
+
await verifyCredentialsRef(cfg);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
if (e instanceof HashicorpVaultError)
|
|
181
|
+
return reply.code(400).send({ error: e.message });
|
|
182
|
+
throw e;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return { ok: true, ...(await testDraftConnection(cfg)) };
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
if (e instanceof HashicorpVaultError)
|
|
189
|
+
return reply.code(502).send({ error: e.message });
|
|
190
|
+
throw e;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
app.post('/:id/test', async (req, reply) => {
|
|
194
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, Number(req.params.id))))[0];
|
|
195
|
+
if (!vault)
|
|
196
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
197
|
+
if (vault.type !== 'hashicorp')
|
|
198
|
+
return reply.code(400).send({ error: 'Only HashiCorp vaults have a connection to test' });
|
|
199
|
+
try {
|
|
200
|
+
return { ok: true, ...(await testConnection(vault)) };
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (e instanceof HashicorpVaultError)
|
|
204
|
+
return reply.code(502).send({ error: e.message });
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
app.patch('/:id', async (req, reply) => {
|
|
209
|
+
const id = Number(req.params.id);
|
|
210
|
+
const existing = (await db.select().from(vaults).where(eq(vaults.id, id)))[0];
|
|
211
|
+
if (!existing)
|
|
212
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
213
|
+
const updates = { updatedAt: Date.now() };
|
|
214
|
+
if (req.body.name !== undefined)
|
|
215
|
+
updates['name'] = req.body.name.trim();
|
|
216
|
+
if (req.body.description !== undefined)
|
|
217
|
+
updates['description'] = req.body.description.trim() || null;
|
|
218
|
+
let credentialsChanged = false;
|
|
219
|
+
if (req.body.connection !== undefined) {
|
|
220
|
+
if (existing.type !== 'hashicorp')
|
|
221
|
+
return reply.code(400).send({ error: 'Only HashiCorp vaults have connection settings' });
|
|
222
|
+
try {
|
|
223
|
+
let old;
|
|
224
|
+
try {
|
|
225
|
+
old = openConnection(existing.connectionConfig);
|
|
226
|
+
}
|
|
227
|
+
catch { /* undecryptable: full credentials required */ }
|
|
228
|
+
const next = parseConnection(req.body.connection, old);
|
|
229
|
+
await verifyCredentialsRef(next);
|
|
230
|
+
credentialsChanged = old?.token !== next.token || old?.roleId !== next.roleId || old?.secretId !== next.secretId;
|
|
231
|
+
updates['connectionConfig'] = sealConnection(next);
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
if (e instanceof HashicorpVaultError)
|
|
235
|
+
return reply.code(400).send({ error: e.message });
|
|
236
|
+
throw e;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const [row] = await db.update(vaults).set(updates).where(eq(vaults.id, id)).returning();
|
|
240
|
+
forgetHashicorpVault(id);
|
|
241
|
+
const actor = requestIdentity(req);
|
|
242
|
+
const before = { ...connectionAudit(existing), description: existing.description };
|
|
243
|
+
const after = { ...connectionAudit(row), description: row.description };
|
|
244
|
+
const diff = audit.diffObjects(before, after);
|
|
245
|
+
if (credentialsChanged)
|
|
246
|
+
diff['credentials'] = { from: '[redacted]', to: '[redacted]' };
|
|
247
|
+
if (Object.keys(diff).length)
|
|
248
|
+
audit.write(audit.actor(actor), 'update', 'vault', id, existing.name, diff);
|
|
249
|
+
return publicVault(row);
|
|
250
|
+
});
|
|
251
|
+
app.delete('/:id', async (req, reply) => {
|
|
252
|
+
const id = Number(req.params.id);
|
|
253
|
+
const existing = (await db.select().from(vaults).where(eq(vaults.id, id)))[0];
|
|
254
|
+
if (!existing)
|
|
255
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
256
|
+
await db.delete(vaults).where(eq(vaults.id, id));
|
|
257
|
+
forgetHashicorpVault(id);
|
|
258
|
+
forgetCredentialDependents(id);
|
|
259
|
+
const actor = requestIdentity(req);
|
|
260
|
+
audit.write(audit.actor(actor), 'delete', 'vault', id, existing.name, audit.snapshot(connectionAudit(existing)));
|
|
261
|
+
return reply.code(204).send();
|
|
262
|
+
});
|
|
263
|
+
// ── Secrets ─────────────────────────────────────────────────────────────────
|
|
264
|
+
app.post('/:id/secrets', async (req, reply) => {
|
|
265
|
+
const vaultId = Number(req.params.id);
|
|
266
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
267
|
+
if (!vault)
|
|
268
|
+
return reply.code(404).send({ error: 'Vault not found' });
|
|
269
|
+
if (!req.body.name?.trim())
|
|
270
|
+
return reply.code(400).send({ error: 'Name is required' });
|
|
271
|
+
if (!VALID_SECRET_TYPES.includes(req.body.type)) {
|
|
272
|
+
return reply.code(400).send({ error: `Type must be one of: ${VALID_SECRET_TYPES.join(', ')}` });
|
|
273
|
+
}
|
|
274
|
+
const type = req.body.type;
|
|
275
|
+
let plaintext;
|
|
276
|
+
let reference;
|
|
277
|
+
try {
|
|
278
|
+
if (vault.type === 'hashicorp') {
|
|
279
|
+
reference = parseReference(type, req.body);
|
|
280
|
+
await verifyReference(vault, type, reference);
|
|
281
|
+
plaintext = JSON.stringify(reference);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
plaintext = serializePayload(type, req.body);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
return reply.code(400).send({ error: e.message });
|
|
289
|
+
}
|
|
290
|
+
const now = Date.now();
|
|
291
|
+
try {
|
|
292
|
+
const [row] = await db.insert(vaultSecrets).values({
|
|
293
|
+
vaultId,
|
|
294
|
+
name: req.body.name.trim(),
|
|
295
|
+
type,
|
|
296
|
+
encryptedValue: encrypt(plaintext),
|
|
297
|
+
createdAt: now,
|
|
298
|
+
updatedAt: now,
|
|
299
|
+
}).returning();
|
|
300
|
+
const actor = requestIdentity(req);
|
|
301
|
+
audit.write(audit.actor(actor), 'create', 'vault_secret', row.id, `${vault.name} / ${row.name}`, audit.snapshot({ name: row.name, type: row.type, vault: vault.name, ...(reference && { path: reference.path, key: reference.key }) }));
|
|
302
|
+
// Return without encrypted value
|
|
303
|
+
const { encryptedValue: _ev, ...safe } = row;
|
|
304
|
+
return safe;
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
if (isUniqueViolation(e))
|
|
308
|
+
return reply.code(409).send({ error: DUPLICATE_SECRET_ERROR });
|
|
309
|
+
throw e;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
app.patch('/:id/secrets/:secretId', async (req, reply) => {
|
|
313
|
+
const vaultId = Number(req.params.id);
|
|
314
|
+
const secretId = Number(req.params.secretId);
|
|
315
|
+
const secret = (await db.select().from(vaultSecrets).where(and(eq(vaultSecrets.id, secretId), eq(vaultSecrets.vaultId, vaultId))))[0];
|
|
316
|
+
if (!secret)
|
|
317
|
+
return reply.code(404).send({ error: 'Secret not found' });
|
|
318
|
+
const updates = { updatedAt: Date.now() };
|
|
319
|
+
if (req.body.name !== undefined)
|
|
320
|
+
updates['name'] = req.body.name.trim();
|
|
321
|
+
// Re-encrypt if any secret field provided
|
|
322
|
+
const vaultForSecret = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
323
|
+
const isReference = vaultForSecret.type === 'hashicorp';
|
|
324
|
+
const hasNewValue = isReference
|
|
325
|
+
? req.body.path !== undefined || req.body.key !== undefined
|
|
326
|
+
: req.body.userpass !== undefined || req.body.value !== undefined || req.body.json !== undefined;
|
|
327
|
+
if (hasNewValue) {
|
|
328
|
+
try {
|
|
329
|
+
if (isReference) {
|
|
330
|
+
const old = JSON.parse(decrypt(secret.encryptedValue));
|
|
331
|
+
const ref = parseReference(secret.type, { path: req.body.path ?? old.path, key: req.body.key ?? old.key });
|
|
332
|
+
await verifyReference(vaultForSecret, secret.type, ref);
|
|
333
|
+
updates['encryptedValue'] = encrypt(JSON.stringify(ref));
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
updates['encryptedValue'] = encrypt(serializePayload(secret.type, req.body));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
return reply.code(400).send({ error: e.message });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const [row] = await db.update(vaultSecrets).set(updates).where(eq(vaultSecrets.id, secretId)).returning();
|
|
345
|
+
forgetCredentialDependents(vaultId, secretId);
|
|
346
|
+
const actor = requestIdentity(req);
|
|
347
|
+
const vaultRow = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
348
|
+
const diff = {};
|
|
349
|
+
if (req.body.name !== undefined && req.body.name !== secret.name)
|
|
350
|
+
diff['name'] = { from: secret.name, to: req.body.name };
|
|
351
|
+
if (hasNewValue)
|
|
352
|
+
diff[isReference ? 'reference' : 'value'] = { from: '[redacted]', to: '[redacted]' };
|
|
353
|
+
if (Object.keys(diff).length)
|
|
354
|
+
audit.write(audit.actor(actor), 'update', 'vault_secret', secretId, `${vaultRow?.name ?? vaultId} / ${secret.name}`, diff);
|
|
355
|
+
const { encryptedValue: _ev, ...safe } = row;
|
|
356
|
+
return safe;
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
if (isUniqueViolation(e))
|
|
360
|
+
return reply.code(409).send({ error: DUPLICATE_SECRET_ERROR });
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
app.delete('/:id/secrets/:secretId', async (req, reply) => {
|
|
365
|
+
const vaultId = Number(req.params.id);
|
|
366
|
+
const secretId = Number(req.params.secretId);
|
|
367
|
+
const secret = (await db.select().from(vaultSecrets).where(and(eq(vaultSecrets.id, secretId), eq(vaultSecrets.vaultId, vaultId))))[0];
|
|
368
|
+
if (!secret)
|
|
369
|
+
return reply.code(404).send({ error: 'Secret not found' });
|
|
370
|
+
await db.delete(vaultSecrets).where(eq(vaultSecrets.id, secretId));
|
|
371
|
+
forgetCredentialDependents(vaultId, secretId);
|
|
372
|
+
const actor = requestIdentity(req);
|
|
373
|
+
const vaultRow = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
374
|
+
audit.write(audit.actor(actor), 'delete', 'vault_secret', secretId, `${vaultRow?.name ?? vaultId} / ${secret.name}`, audit.snapshot({ name: secret.name, type: secret.type }));
|
|
375
|
+
return reply.code(204).send();
|
|
376
|
+
});
|
|
377
|
+
app.get('/:id/secrets/:secretId/reveal', async (req, reply) => {
|
|
378
|
+
const vaultId = Number(req.params.id);
|
|
379
|
+
const secretId = Number(req.params.secretId);
|
|
380
|
+
const secret = (await db.select().from(vaultSecrets).where(and(eq(vaultSecrets.id, secretId), eq(vaultSecrets.vaultId, vaultId))))[0];
|
|
381
|
+
if (!secret)
|
|
382
|
+
return reply.code(404).send({ error: 'Secret not found' });
|
|
383
|
+
const vault = (await db.select().from(vaults).where(eq(vaults.id, vaultId)))[0];
|
|
384
|
+
// The value itself never goes into the log, only the fact that it was shown and to whom.
|
|
385
|
+
const recordReveal = () => audit.write(audit.actor(requestIdentity(req)), 'allow', 'vault_secret', secretId, `${vault.name} / ${secret.name}`, { access: 'revealed' });
|
|
386
|
+
if (vault.type === 'hashicorp') {
|
|
387
|
+
const ref = safeDecrypt(secret.encryptedValue);
|
|
388
|
+
if (!ref)
|
|
389
|
+
return reply.code(500).send({ error: 'Failed to decrypt secret' });
|
|
390
|
+
try {
|
|
391
|
+
const value = await loadSecretPayload(secret, vault);
|
|
392
|
+
await recordReveal();
|
|
393
|
+
return { id: secret.id, name: secret.name, type: secret.type, value, source: ref };
|
|
394
|
+
}
|
|
395
|
+
catch (e) {
|
|
396
|
+
return reply.code(502).send({ error: e.message });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const value = safeDecrypt(secret.encryptedValue);
|
|
400
|
+
if (value === null)
|
|
401
|
+
return reply.code(500).send({ error: 'Failed to decrypt secret' });
|
|
402
|
+
await recordReveal();
|
|
403
|
+
return { id: secret.id, name: secret.name, type: secret.type, value };
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return { vaultCatalogRoutes, vaultRoutes };
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=vaultRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vaultRoutes.js","sourceRoot":"","sources":["../src/vaultRoutes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The same text for the same value, whatever the order of keys. */
|
|
2
|
+
export declare function canonical(value: unknown): string;
|
|
3
|
+
/**
|
|
4
|
+
* The check that keeps a token without the permission to use vault secrets away from them. `target` names what a
|
|
5
|
+
* vault reference sits in, in the application's words (for example "job or channel"), for the refusal message.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createVaultUseGuard(options: {
|
|
8
|
+
target: string;
|
|
9
|
+
}): {
|
|
10
|
+
refused: string;
|
|
11
|
+
vaultUseProblem: (token: {
|
|
12
|
+
scopes: readonly string[];
|
|
13
|
+
} | undefined, incoming: unknown, stored: unknown) => string | null;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=vaultUse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vaultUse.d.ts","sourceRoot":"","sources":["../src/vaultUse.ts"],"names":[],"mappings":"AAYA,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIhD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;6BAW7B;QAAE,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,YAAY,OAAO,UAAU,OAAO,KAAG,MAAM,GAAG,IAAI;EAM9H"}
|
package/dist/vaultUse.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { tokenAllows, VAULT_USE_SCOPE } from '@better-forge/contracts';
|
|
2
|
+
const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
/** Whether anything under a `vault` key is set, whatever its shape: a reference that is malformed is still treated as one. */
|
|
4
|
+
function holdsVault(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.some(holdsVault);
|
|
7
|
+
if (!isObject(value))
|
|
8
|
+
return false;
|
|
9
|
+
return Object.entries(value).some(([key, inner]) => (key === 'vault' && inner !== undefined && inner !== null && inner !== false) || holdsVault(inner));
|
|
10
|
+
}
|
|
11
|
+
/** The same text for the same value, whatever the order of keys. */
|
|
12
|
+
export function canonical(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
15
|
+
if (isObject(value))
|
|
16
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
17
|
+
return JSON.stringify(value) ?? 'null';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The check that keeps a token without the permission to use vault secrets away from them. `target` names what a
|
|
21
|
+
* vault reference sits in, in the application's words (for example "job or channel"), for the refusal message.
|
|
22
|
+
*/
|
|
23
|
+
export function createVaultUseGuard(options) {
|
|
24
|
+
const refused = `This token does not have the "${VAULT_USE_SCOPE}" permission, which it needs to add a vault reference or to change the configuration of a ${options.target} that has one`;
|
|
25
|
+
/**
|
|
26
|
+
* A vault secret is sent wherever the configuration that references it points, and what the target answers (a query
|
|
27
|
+
* result, an error message) comes back to the caller. A token that may edit such a configuration but may not use vault
|
|
28
|
+
* secrets therefore can neither add a reference nor change anything in a configuration that has one: it can edit the
|
|
29
|
+
* rest or remove the reference. `stored` is the configuration as it is saved now, or undefined for a new one.
|
|
30
|
+
* Returns a message, or null if fine.
|
|
31
|
+
*/
|
|
32
|
+
function vaultUseProblem(token, incoming, stored) {
|
|
33
|
+
if (!token || tokenAllows(token.scopes, VAULT_USE_SCOPE) || !holdsVault(incoming))
|
|
34
|
+
return null;
|
|
35
|
+
return stored !== undefined && canonical(incoming) === canonical(stored) ? null : refused;
|
|
36
|
+
}
|
|
37
|
+
return { refused, vaultUseProblem };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=vaultUse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vaultUse.js","sourceRoot":"","sources":["../src/vaultUse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGtE,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAiB,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAEjH,8HAA8H;AAC9H,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAClC,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;AACzJ,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACtE,IAAI,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACtI,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAA;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA2B;IAC7D,MAAM,OAAO,GACX,iCAAiC,eAAe,6FAA6F,OAAO,CAAC,MAAM,eAAe,CAAA;IAE5K;;;;;;OAMG;IACH,SAAS,eAAe,CAAC,KAAgD,EAAE,QAAiB,EAAE,MAAe;QAC3G,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAA;QAC9F,OAAO,MAAM,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAA;IAC3F,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAA;AACrC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-forge/vault",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Local vault crypto, HashiCorp client, secret resolution and vault routes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/BElluu/Better-Forge.git",
|
|
9
|
+
"directory": "packages/vault"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/BElluu/Better-Forge/tree/main/packages/vault#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/BElluu/Better-Forge/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"!src/**/*.test.*"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"drizzle-orm": "^0.45.2",
|
|
39
|
+
"fastify": "^5.3.2",
|
|
40
|
+
"undici": "^7.4.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^26.6.2",
|
|
44
|
+
"drizzle-orm": "^0.45.2",
|
|
45
|
+
"fastify": "^5.3.2",
|
|
46
|
+
"typescript": "^5.7.3",
|
|
47
|
+
"undici": "^7.4.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@better-forge/contracts": "0.1.1",
|
|
51
|
+
"@better-forge/db": "0.1.1"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=26.10.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify'
|
|
2
|
+
import type { SqliteDatabase } from '@better-forge/db'
|
|
3
|
+
import { createHashicorp } from './hashicorp.js'
|
|
4
|
+
import { createSecretBox, type SecretBox } from './secretBox.js'
|
|
5
|
+
import { createSecretResolver } from './resolveSecret.js'
|
|
6
|
+
import { createVaultRoutes, type VaultAudit } from './vaultRoutes.js'
|
|
7
|
+
|
|
8
|
+
export interface VaultOptions {
|
|
9
|
+
/** The application's database handle. It is used lazily, so it may be opened after this is created. */
|
|
10
|
+
db: SqliteDatabase
|
|
11
|
+
audit: VaultAudit
|
|
12
|
+
/** The signed-in caller of a request; the routes sit behind the application's own guards. */
|
|
13
|
+
identity: (req: FastifyRequest) => { userId: number; email: string }
|
|
14
|
+
/** Where the encryption key comes from. Defaults to the process environment. */
|
|
15
|
+
env?: NodeJS.ProcessEnv
|
|
16
|
+
/** A box the application already created, for the tables that seal their own columns; it is made from `env` when absent. */
|
|
17
|
+
secretBox?: SecretBox
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Vaults of secrets (kept encrypted here, or read live from HashiCorp Vault), the lookup that turns a reference into
|
|
22
|
+
* its value, and the routes that manage them. Everything is built from `options` and kept in the returned closures.
|
|
23
|
+
*/
|
|
24
|
+
export function createVault(options: VaultOptions) {
|
|
25
|
+
const secretBox = options.secretBox ?? createSecretBox(options.env ? { env: options.env } : {})
|
|
26
|
+
const ctx = { db: options.db, secretBox }
|
|
27
|
+
const hashicorp = createHashicorp(ctx)
|
|
28
|
+
const resolver = createSecretResolver(ctx, hashicorp)
|
|
29
|
+
const routes = createVaultRoutes({ ...ctx, audit: options.audit, identity: options.identity }, { hashicorp, resolver })
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
secretBox,
|
|
33
|
+
hashicorp,
|
|
34
|
+
resolver,
|
|
35
|
+
/** Plain plugins, so the hooks of the scope they are registered in apply to them. */
|
|
36
|
+
routes: {
|
|
37
|
+
/** Read-only catalogue of vault and secret names and types, never values. */
|
|
38
|
+
catalogue: routes.vaultCatalogRoutes,
|
|
39
|
+
/** Create, change, reveal and delete vaults and secrets. */
|
|
40
|
+
manage: routes.vaultRoutes,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type Vault = ReturnType<typeof createVault>
|