@dotrino/vaultd 0.74.0 → 0.75.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/lib/src/admin.js +2 -6
- package/package.json +1 -1
- package/src/vault.js +33 -7
package/lib/src/admin.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
export const ADMIN_OPS = Object.freeze([
|
|
47
47
|
// `pending`, `pair`, `approve` y `reject` NO están, y no es un olvido: agregar aparatos
|
|
48
48
|
// a distancia se quitó (ver la cabecera). Lo que queda es mirar, quitar y configurar.
|
|
49
|
-
'
|
|
49
|
+
'revoke', 'audit',
|
|
50
50
|
// Variables de entorno: verlas (nombres siempre; valor solo de las públicas) y
|
|
51
51
|
// ponerles valor (de las dos). Borrar NO está, y es a propósito: un aparato robado
|
|
52
52
|
// no debe poder dejar sin configuración a los servicios.
|
|
@@ -72,7 +72,7 @@ export const ADMIN_OPS = Object.freeze([
|
|
|
72
72
|
* Se dejan las de LEER a propósito: si se cortaran todas, la consola no podría ni
|
|
73
73
|
* enterarse de por qué no funciona y parecería rota. Así puede decir «está cerrada».
|
|
74
74
|
*/
|
|
75
|
-
export const ADMIN_OPS_WHILE_LOCKED = Object.freeze(['
|
|
75
|
+
export const ADMIN_OPS_WHILE_LOCKED = Object.freeze(['audit', 'vars'])
|
|
76
76
|
|
|
77
77
|
/** Cuánto se recuerda un nonce ya usado (el doble de la ventana de frescura). */
|
|
78
78
|
export const ADMIN_NONCE_TTL_MS = 10 * 60 * 1000
|
|
@@ -159,10 +159,6 @@ export function createAdminDesk ({
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
try {
|
|
162
|
-
// Lo mínimo que la consola necesita saber de la bóveda para explicarse: si está
|
|
163
|
-
// cerrada, lo que queda (revocar, configurar) no se puede, y hay que decir por qué en
|
|
164
|
-
// vez de dejar pulsar un botón para recibir un error.
|
|
165
|
-
if (data.op === 'status') return { ok: true, result: { locked: !!isLocked() } }
|
|
166
162
|
|
|
167
163
|
if (data.op === 'audit') {
|
|
168
164
|
const limit = Math.min(Math.max(Number(data.limit) || 100, 1), AUDIT_MAX)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
|
|
6
6
|
"bin": {
|
package/src/vault.js
CHANGED
|
@@ -228,7 +228,11 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
228
228
|
try {
|
|
229
229
|
const info = await identity.profileActa?.()
|
|
230
230
|
const acta = info?.acta
|
|
231
|
-
|
|
231
|
+
// CON EL PERFIL ABIERTO. Estrenar la llave de sellado SELLA EL ACTA, y una bóveda
|
|
232
|
+
// cerrada no sella nada — la misma regla que el bloque de abajo. Cerrada se queda sin
|
|
233
|
+
// llave y los sobres salen sin firma (lo que ya pasaba antes de §8.8), y se arregla sola
|
|
234
|
+
// la primera vez que alguien abra el perfil.
|
|
235
|
+
if (info?.isMaster && acta && !isLocked() && (!acta.sealPub || !sealKeys.has(acta.sealPub))) {
|
|
232
236
|
const r = await identity.rotateSealKey()
|
|
233
237
|
// Sin `%s`: este `log` va con un prefijo por delante, así que el formato no es lo
|
|
234
238
|
// primero y `console.log` no lo sustituye (salía «record #%s 2»).
|
|
@@ -252,7 +256,14 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
252
256
|
try {
|
|
253
257
|
const info = await identity.profileActa?.()
|
|
254
258
|
const acta = info?.acta
|
|
255
|
-
|
|
259
|
+
// CON EL PERFIL ABIERTO, y `isLocked()` es la pregunta correcta — no `masterLocked`.
|
|
260
|
+
//
|
|
261
|
+
// Se coló así: en la PRIMERA arrancada tras actualizar, la maestra todavía está guardada
|
|
262
|
+
// en claro (se sella al abrir el perfil), o sea que `masterLocked` es `false` aunque el
|
|
263
|
+
// perfil esté cerrado. Con ese guardián, la bóveda de producción admitió su llave de
|
|
264
|
+
// comunicación y SELLÓ UN ACTA NUEVA (#76 → #77) estando cerrada — justo lo que la regla
|
|
265
|
+
// prohíbe, y encima rotando de paso la llave de sellado sin que nadie lo pidiera.
|
|
266
|
+
if (acta && !isLocked() && !identity.masterLocked) {
|
|
256
267
|
const pub = await commKey.ensure()
|
|
257
268
|
const yaEsta = (acta.members || []).some((m) => m?.pub === pub)
|
|
258
269
|
if (!yaEsta && info?.isMaster) {
|
|
@@ -604,14 +615,29 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
604
615
|
if (!isFresh(p.data)) return staleReply(from)
|
|
605
616
|
const chk = await verifyChain({ data: p.data, signature: p.signature, cert: p.cert, ...(await contextoActa()), revoked: await revocationSet() })
|
|
606
617
|
if (!chk.ok) return denyChain(from, chk, p, null)
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
618
|
+
// DOS RESPUESTAS, PORQUE SON DOS PREGUNTAS DISTINTAS.
|
|
619
|
+
//
|
|
620
|
+
// Un SERVICIO pregunta aquí para enterarse de dos cosas suyas: si le revocaron el papel
|
|
621
|
+
// y cuál es el acta vigente. Eso no es tu inventario y no puede exigir `lee` — se lo
|
|
622
|
+
// exigí, y en producción dejó ciegos a los servicios (`rejected devices/acta` en la
|
|
623
|
+
// bitácora del VPS, dos veces, antes de que lo viera).
|
|
624
|
+
//
|
|
625
|
+
// La LISTA DE APARATOS sí es el perfil —cómo se llama cada máquina y qué puede— y esa
|
|
626
|
+
// sigue pidiendo `lee`. Antes no preguntaba nada, así que un aparato al que le quitaste
|
|
627
|
+
// el permiso seguía viéndolo todo hasta que su papel caducara.
|
|
628
|
+
const record = (await identity.profileActa?.().catch(() => null))?.acta || null
|
|
629
|
+
const puedeVerTodo = !!record && Acta.memberCanScope(record, chk.device, SCOPE.READ)
|
|
611
630
|
const { issued, revoked } = await identity.listDelegations()
|
|
631
|
+
if (!puedeVerTodo) {
|
|
632
|
+
if (!record) {
|
|
633
|
+
audit('rejected', { what: 'devices', reason: 'sin-acta' })
|
|
634
|
+
return reply(from, { type: MSG.ERROR, error: 'unauthorized: this vault has no record to decide with' })
|
|
635
|
+
}
|
|
636
|
+
audit('devices', { device: await deviceIdOf(chk.device).catch(() => null), solo: 'revocaciones' })
|
|
637
|
+
return reply(from, { type: MSG.DEVICES_RESULT, devices: [], revoked, acta: record })
|
|
638
|
+
}
|
|
612
639
|
// El acta viaja con la lista: así cada dispositivo se entera de los cambios de
|
|
613
640
|
// política (quién manda, quién puede qué) sin un canal aparte.
|
|
614
|
-
const record = (await identity.profileActa?.().catch(() => null))?.acta || null
|
|
615
641
|
// Si el dispositivo estuvo apagado y viene con un `seq` viejo, se le manda la CADENA
|
|
616
642
|
// que falta (ventana de retención, §1.3) para que compruebe el encadenamiento en vez
|
|
617
643
|
// de tragarse un salto a ciegas. Si se salió de la ventana, llega vacía y toca
|