ARCH-VAULT-SYSTEM: Vault system — especificación normativa (boundary, contrato, identidades, operación)¶
Especificación técnica del sistema de secrets/vault del harness (humanos + perfiles Hermes). Contenido normativo del handoff THINK (ADR-0008, rol architect); la decisión formal y sus alternativas viven en ADR-0009. No contiene valores secretos.
Boundary y topología¶
flowchart LR
H[Operador] -->|TLS + auth nativa| E[Edge LE production]
E -->|UI humana SOLAMENTE; DENY de API| I[Infisical in-cluster ns vault]
P[Perfil Hermes] -->|vault.*| A[Adaptador ai-hub local]
A -->|Universal Auth, service interno| I
OP[Operator K8s v0.11.8] -->|Universal Auth, service interno| I
OP -->|sync v1beta1| KS[K8s Secrets workloads]
I --> D[(PostgreSQL + Redis in-cluster)]
D --> B[Backup cifrado off-host]
I --> L[Audit sink append-only off-host]
R[SOPS+age recovery bundle] -. sólo desastre declarado .-> I
AJUSTE 2026-08-31 — decisión del operador (ADR-0009 proposed → accepted
in-PR): el Infisical corre IN-CLUSTER (namespace vault de wetopi3) con
PostgreSQL + Redis persistentes en el mismo namespace y el Infisical Secrets
Operator v0.11.8 instalado en el clúster; la versión proposed de este pack
colocaba el servicio en una VM dedicada fuera del clúster. Universal Auth es
sólo alcanzable por el service interno vault.vault.svc.cluster.local; la
ruta pública vault.wetopi.net publica exclusivamente la UI humana con DENY
explícito de los endpoints de API. El trade-off de fallo correlacionado con
wetopi3 es aceptado y compensado por el contrato de recovery (backup
cifrado off-host + restore drill ensayado). El despliegue se fija por
versión/digest y documenta upgrade/rollback (materialización: infra PR #27,
feat/vault-infra).
Namespaces y referencias¶
Referencia lógica: vault://<project>/<environment>/<path>#<key>. project,
environment y path provienen de allowlists; se normalizan y no admiten
traversal, comodines aportados por el llamador ni selección arbitraria de
organización. Los valores nunca aparecen en URI, argumentos CLI, Git, Kanban,
métricas o logs. La configuración de un consumidor contiene sólo la
referencia.
Contrato vault.*¶
Respuesta común: request_id, operation, referencia canónica redacted,
versión/etag cuando exista y resultado tipado; jamás eco del valor salvo el
campo de retorno de get al proceso autorizado. Errores estables:
INVALID_REFERENCE, FORBIDDEN, NOT_FOUND, ALREADY_EXISTS,
VERSION_CONFLICT, UPSTREAM_UNAVAILABLE, RATE_LIMITED. No se traduce
FORBIDDEN en detalle que permita enumerar nombres.
| Operación | Semántica obligatoria | Efectos / protección |
|---|---|---|
get(ref) |
lectura exacta de un valor; no fallback entre environments | devuelve el valor sólo al proceso llamador y metadatos mínimos; redacción integral en trazas |
exists(ref) |
comprobación exacta | devuelve booleano sólo dentro del scope; fuera de scope responde FORBIDDEN, no revela existencia |
list(prefix, cursor, limit) |
lista nombres/metadatos, nunca valores | paginada, prefix allowlisted, sin búsqueda global ni recursive implícito |
put(ref, value, metadata) |
create-only | falla ALREADY_EXISTS; requiere permiso write y límites de payload; valor por stdin/memoria, no argv |
update(ref, value, expected_version) |
reemplazo de existente | falla NOT_FOUND o VERSION_CONFLICT; precondición obligatoria evita lost update |
delete(ref, expected_version) |
elimina una versión lógica existente | deshabilitado por defecto; scope específico, precondición y gate humano/approver; recovery probado antes |
Todo intento permitido o denegado emite audit event sin valor. Retries sólo
para operaciones idempotentes; put/update/delete usan request-id/idempotency
y no repiten a ciegas. Timeouts y circuit breaker evitan bloquear Hermes. El
adaptador nunca mantiene una supercredencial compartida: resuelve la identidad
del perfil autenticado a una credencial local fija; el perfil no pasa
identity=.
Matriz de identidades y permisos máximos¶
Estos son techos, no grants automáticos. Cada identity además queda limitada
por proyecto/environment/path. Todo perfil no listado o nuevo nace deny-all
hasta PR de policy y aprobación.
| Identidad / actor | get | list | exists | put | update | delete | audit |
|---|---|---|---|---|---|---|---|
operador secrets-admin (humano, MFA) |
sí | sí | sí | sí | sí | gate doble | sí |
wetopi-architect |
no | metadatos de esquema | sí | no | no | no | sólo su actividad |
wetopi-discovery |
no | no | no | no | no | no | no |
wetopi-approver |
no | metadatos/evidencia | sí | no | no | no | evidencia de control, sin valores |
wetopi-lead |
sólo namespaces de coordinación aprobados | sí, acotado | sí | no por defecto | no por defecto | no | su actividad |
| perfil executor/deployer registrado | sólo refs requeridas por su función | acotado | sí | no por defecto | no por defecto | no | su actividad |
identidad secrets-rotator dedicada, no perfil conversacional |
sí acotado | sí acotado | sí | sí | sí | no por defecto | su actividad |
| identidad break-glass/recovery humana | durante incidente declarado | sí | sí | restore controlado | restore controlado | no por defecto | sí |
OWN debe reemplazar la fila genérica executor/dedeployer por una fila por cada perfil activo del registro canónico antes del gate S2; no se infieren nombres. Es inaceptable usar una identidad wildcard para cerrar ese inventario.
flowchart TD
N[Perfil nuevo] --> X[deny-all]
X --> R[PR policy: project/env/path + operaciones]
R --> J{approval}
J -->|rechazado| X
J -->|aceptado| M[Machine identity exclusiva]
M --> B[Bootstrap local owner-only]
B --> T[Token corto en memoria]
T --> V[vault.* dentro de scope]
Bootstrap y ciclo de credenciales¶
- Operador crea identity y policy fuera del canal del agente; registra sólo IDs/referencias no secretas.
- La credencial bootstrap se genera/entrega por canal separado y se instala
directamente como fichero owner-only fuera del repo y de
.env; no se pega en tickets, shell history o logs. - El adaptador valida owner/mode, realiza Universal Auth y mantiene el token corto sólo en memoria. Si el fichero falta o es inseguro, fail closed.
- Se ejecutan pruebas positiva y negativa por identidad con secretos sintéticos; después se elimina el fixture y se conserva sólo evidencia redacted.
- La renovación rota primero la credencial bootstrap, verifica la nueva, invalida la vieja y registra actor/fecha/resultado. Exposición, cambio de rol o baja fuerzan revocación inmediata.
Rotación de secretos administrados¶
Cada entrada tiene owner, consumidores, scope, método de rotación, plazo, última rotación y próximo vencimiento como metadatos sin valor. Cuando el proveedor soporte API: crear replacement mínimo, almacenar nueva versión, actualizar consumidor por referencia, probar, revocar anterior y verificar rechazo. Sin API: runbook humano con los mismos gates. Nunca se marca completada sólo por cambiar el valor en Infisical. Alarmas cubren expiración próxima y rotaciones fallidas; no incluyen valores.
Backup y recovery¶
Se requieren dos mecanismos complementarios: backup consistente y cifrado de todos los datos/configuración criptográfica necesarios para restaurar la instancia, guardado off-host; y export lógico de los namespaces aprobados transformado en streaming a SOPS+age, sin plaintext persistente. Recipients age separados (operador/Lead + recuperación), custodia fuera de la VM y prueba periódica en entorno aislado. El runbook: declarar incidente, aislar origen, restaurar versión compatible, comprobar integridad con secretos sintéticos/checksums no sensibles, reemitir machine credentials, rotar credenciales posiblemente expuestas, cerrar break-glass y adjuntar evidencia. Si la edición/API no permite export fiable o audit suficiente, S1 es NO-GO y no se promueve.
Migración del estado vivo¶
M0: inventario metadata-only de los ocho ficheros de ~/secrets/ y de K8s
Secrets: identificador redacted, owner, consumidor, origen, alcance,
exposición, rotabilidad y estado; nunca valores ni dump de Secret.
M1: cuarentena y prioridad para las dos credenciales expuestas
(t_b904ba3f), GHCR broad-scope y argocd admin estático.
M2: emitir replacements mínimos y únicos; no importar lo expuesto como
destino final.
M3: cargar por canal seguro, cambiar consumidores a referencias vault://,
prueba funcional y observación.
M4: revocar credencial anterior y demostrar que falla; eliminar copias
plaintext y .env sólo tras backup/rollback y evidencia de consumidor sano.
M5: reconciliar inventario: cero credencial sin owner, cero broad/shared no
justificada, cero copia local salvo bootstrap por perfil.
Los K8s Secrets de destino se inventarían y clasifican; su SINCRONIZACIÓN es
función del Operator instalado en esta fase (decisión del operador
2026-08-31, vía infra PR #27) y no se borra un Secret del clúster bajo
OBJ-VAULT-001 salvo el garbage-collection Owner de los CRs de sync revisados.
Auditoría¶
Evento mínimo: timestamp UTC, request-id, actor humano o machine identity, operación, proyecto/environment/path redacted (sin valor), decisión allow/deny, policy/version, resultado, origen y correlación Objective/task. No se registran valores, tokens, cuerpos ni query strings sensibles. Retención propuesta: 365 días, con copia append-only off-host y acceso restringido; S1 debe confirmar que la edición elegida lo soporta. Alertas: denegaciones repetidas, acceso fuera de patrón, uso break-glass, cambios de policy/identity, export, delete y audit sink caído. La caída del sink bloquea operaciones destructivas y administrativas; las lecturas siguen sólo si la política de continuidad aprobada lo permite.
Riesgos y mitigaciones¶
| Riesgo | Mitigación / gate |
|---|---|
fallo correlacionado con wetopi3 (vault in-cluster) |
riesgo ACEPTADO por decisión del operador 2026-08-31; backup cifrado off-host, health monitoring, restore drill antes de migrar |
| licencia self-hosted no cubre audit/RBAC/identities | S1 GO/NO-GO; no producción sin evidencia y coste aceptado |
| bootstrap local robado | uno por perfil, filesystem owner-only, token corto en memoria, rotación/revocación |
| ai-hub se convierte en confused deputy | binding perfil→identity inmutable, allowlists, no identity= del caller, tests negativos |
| logs/CLI filtran valores | stdin/memoria, redaction tests, no argv/env, scanning de logs |
| política excesiva o drift | default deny, policy-as-code sin valores, review y reconciliación periódica |
| migración rompe consumidores | replacement/dual-run, prueba, rollback y revocación sólo después |
| credenciales viejas sobreviven | verificación explícita de rechazo y reconciliación final |
| Basic Auth rompe API o da falsa seguridad | auth nativa obligatoria; validar separación UI/API; omitir Basic Auth si interfiere |
| recovery bundle incompleto | restore drill aislado incluyendo claves/configuración de cifrado |
| audit tampering | sink append-only off-host, acceso separado, alertas de ausencia |
| operator/CRDs amplían superficie K8s | instalados en esta fase por decisión del operador 2026-08-31 (ADR-0009 decisión 8); revisados en infra PR #27, RBAC mínima upstream verbatim, CRDs v1beta1 con checksum verificado |
Diagramas de permisos¶
flowchart LR
A[architect] -->|list schema / exists| M[metadata only]
D[discovery] -->|deny| Z[no vault access]
J[approver] -->|evidence| M
L[lead] -->|scoped get/list/exists| S[approved namespace]
E[executor] -->|scoped get| S
Q[rotator] -->|put/update| S
O[operator MFA] -->|admin; delete double gate| S
Related documents¶
- ADR-0009 — la decisión formal (accepted 2026-08-31) y sus alternativas/consecuencias.
- OBJ-VAULT-001 — el Objective con ACs, etapas S0-S10, gates y promotion matrix.
Repositorio: platform-infisical — manifests: infisical-server, infisical-operator.