Files
system-docs/v4-archive/contratos-comunes/docs/KEY_VAULT.md

596 lines
28 KiB
Markdown
Raw Normal View History

# 07c. KEY VAULT Y CRIPTOGRAFÍA
**Manual de Arquitectura Técnica — Documento 07c**
**Versión:** 1.2
**Dependencia:** `S-CONTRACT.md`
**Estado:** Enterprise Standard
---
# C.1 Introducción
El Key Vault es el **tercer espacio de almacenamiento** del ecosistema, separado de:
- **Libro Mayor (SFE)**: Datos de negocio
- **Almacenamiento de archivos**: Hostinger/S3
- **Key Vault**: Llaves, credenciales y secretos
```
┌─────────────────────────────────────────────────────────────────┐
│ PRINCIPIO DE SEPARACIÓN │
│ │
│ "Las llaves nunca viajan con los datos que protegen" │
└─────────────────────────────────────────────────────────────────┘
```
---
# C.2 Arquitectura del Key Vault
## C.2.1 Componentes
```
┌─────────────────────────────────────────────────────────────────┐
│ KEY VAULT │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ SECRETS │ │ KEYS │ │ CERTS │ │
│ │ STORE │ │ STORE │ │ STORE │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ ACCESS │ │
│ │ CONTROL │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ AUDIT LOG │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## C.2.2 Tipos de Secretos
| Tipo | Descripción | Ejemplo | Rotación |
|------|-------------|---------|----------|
| **API_KEY** | Llaves de APIs externas | OpenRouter, Groq | 90 días |
| **ENCRYPTION_KEY** | Llaves de cifrado de datos | AES-256 keys | 365 días |
| **DB_CREDENTIAL** | Credenciales de bases de datos | NocoDB, PostgreSQL | 30 días |
| **SERVICE_TOKEN** | Tokens de servicios internos | n8n, Nextcloud | 7 días |
| **USER_SECRET** | Secretos específicos de usuario | OAuth tokens | Variable |
| **CERTIFICATE** | Certificados TLS/mTLS | SSL certs | 90 días |
| **SIGNING_KEY** | Llaves de firma digital | JWT signing | 180 días |
---
# C.3 Modelo de Datos
## C.3.1 Tabla VAULT_SECRETS
```sql
CREATE TABLE VAULT_SECRETS (
-- ═══════════════════════════════════════════════════════════
-- IDENTIFICACIÓN
-- ═══════════════════════════════════════════════════════════
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
secret_id VARCHAR(100) UNIQUE NOT NULL,
version INTEGER DEFAULT 1,
-- ═══════════════════════════════════════════════════════════
-- CLASIFICACIÓN
-- ═══════════════════════════════════════════════════════════
secret_type VARCHAR(50) NOT NULL,
category VARCHAR(50),
tags VARCHAR(50)[],
-- ═══════════════════════════════════════════════════════════
-- VALOR (CIFRADO)
-- ═══════════════════════════════════════════════════════════
encrypted_value BYTEA NOT NULL,
encryption_algorithm VARCHAR(50) DEFAULT 'AES-256-GCM',
key_encryption_key_id VARCHAR(100),
nonce BYTEA,
-- ═══════════════════════════════════════════════════════════
-- METADATA
-- ═══════════════════════════════════════════════════════════
description TEXT,
owner_id UUID,
owner_type VARCHAR(50),
-- ═══════════════════════════════════════════════════════════
-- CICLO DE VIDA
-- ═══════════════════════════════════════════════════════════
status VARCHAR(20) DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
last_rotated_at TIMESTAMPTZ,
rotation_interval_days INTEGER,
-- ═══════════════════════════════════════════════════════════
-- ACCESO
-- ═══════════════════════════════════════════════════════════
access_policy JSONB DEFAULT '{}',
allowed_modules VARCHAR(50)[],
allowed_players UUID[],
-- ═══════════════════════════════════════════════════════════
-- AUDITORÍA
-- ═══════════════════════════════════════════════════════════
access_count INTEGER DEFAULT 0,
last_accessed_at TIMESTAMPTZ,
last_accessed_by VARCHAR(100),
-- ═══════════════════════════════════════════════════════════
-- CONSTRAINTS
-- ═══════════════════════════════════════════════════════════
CONSTRAINT valid_status CHECK (
status IN ('ACTIVE', 'DISABLED', 'EXPIRED', 'ROTATING', 'DELETED')
),
CONSTRAINT valid_type CHECK (
secret_type IN ('API_KEY', 'ENCRYPTION_KEY', 'DB_CREDENTIAL',
'SERVICE_TOKEN', 'USER_SECRET', 'CERTIFICATE', 'SIGNING_KEY')
)
);
-- Índices
CREATE INDEX idx_vault_secret_id ON VAULT_SECRETS(secret_id);
CREATE INDEX idx_vault_type ON VAULT_SECRETS(secret_type);
CREATE INDEX idx_vault_status ON VAULT_SECRETS(status);
CREATE INDEX idx_vault_expires ON VAULT_SECRETS(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX idx_vault_owner ON VAULT_SECRETS(owner_id);
```
## C.3.2 Tabla VAULT_ACCESS_LOG
```sql
CREATE TABLE VAULT_ACCESS_LOG (
-- ═══════════════════════════════════════════════════════════
-- IDENTIFICACIÓN
-- ═══════════════════════════════════════════════════════════
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- ═══════════════════════════════════════════════════════════
-- SECRETO ACCEDIDO
-- ═══════════════════════════════════════════════════════════
secret_id VARCHAR(100) NOT NULL,
secret_version INTEGER,
-- ═══════════════════════════════════════════════════════════
-- CONTEXTO DE ACCESO
-- ═══════════════════════════════════════════════════════════
trace_id UUID,
step_id UUID,
module_name VARCHAR(50),
operation VARCHAR(50) NOT NULL,
-- ═══════════════════════════════════════════════════════════
-- ACTOR
-- ═══════════════════════════════════════════════════════════
accessor_type VARCHAR(50) NOT NULL,
accessor_id VARCHAR(100) NOT NULL,
accessor_ip INET,
-- ═══════════════════════════════════════════════════════════
-- RESULTADO
-- ═══════════════════════════════════════════════════════════
status VARCHAR(20) NOT NULL,
error_code VARCHAR(50),
error_message TEXT,
-- ═══════════════════════════════════════════════════════════
-- TEMPORALIDAD
-- ═══════════════════════════════════════════════════════════
timestamp TIMESTAMPTZ DEFAULT NOW(),
duration_ms INTEGER,
-- ═══════════════════════════════════════════════════════════
-- SEGURIDAD
-- ═══════════════════════════════════════════════════════════
encryption_profile VARCHAR(20),
purpose TEXT,
-- ═══════════════════════════════════════════════════════════
-- CONSTRAINTS
-- ═══════════════════════════════════════════════════════════
CONSTRAINT valid_operation CHECK (
operation IN ('READ', 'CREATE', 'UPDATE', 'DELETE', 'ROTATE',
'ENABLE', 'DISABLE', 'LIST', 'DECRYPT', 'ENCRYPT')
),
CONSTRAINT valid_access_status CHECK (
status IN ('SUCCESS', 'DENIED', 'ERROR', 'EXPIRED', 'NOT_FOUND')
),
CONSTRAINT valid_accessor CHECK (
accessor_type IN ('MODULE', 'PLAYER', 'SYSTEM', 'ADMIN', 'SERVICE')
)
);
-- Índices para auditoría eficiente
CREATE INDEX idx_vault_log_secret ON VAULT_ACCESS_LOG(secret_id);
CREATE INDEX idx_vault_log_trace ON VAULT_ACCESS_LOG(trace_id) WHERE trace_id IS NOT NULL;
CREATE INDEX idx_vault_log_timestamp ON VAULT_ACCESS_LOG(timestamp);
CREATE INDEX idx_vault_log_accessor ON VAULT_ACCESS_LOG(accessor_type, accessor_id);
CREATE INDEX idx_vault_log_denied ON VAULT_ACCESS_LOG(status, timestamp)
WHERE status = 'DENIED';
```
## C.3.3 Tabla VAULT_ENCRYPTION_KEYS (KEK)
```sql
CREATE TABLE VAULT_ENCRYPTION_KEYS (
-- ═══════════════════════════════════════════════════════════
-- IDENTIFICACIÓN
-- ═══════════════════════════════════════════════════════════
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key_id VARCHAR(100) UNIQUE NOT NULL,
version INTEGER DEFAULT 1,
-- ═══════════════════════════════════════════════════════════
-- PROPIEDADES
-- ═══════════════════════════════════════════════════════════
algorithm VARCHAR(50) NOT NULL DEFAULT 'AES-256-GCM',
key_size_bits INTEGER NOT NULL DEFAULT 256,
purpose VARCHAR(50) NOT NULL,
-- ═══════════════════════════════════════════════════════════
-- VALOR (protegido por Master Key externa)
-- ═══════════════════════════════════════════════════════════
encrypted_key_material BYTEA NOT NULL,
master_key_ref VARCHAR(100),
-- ═══════════════════════════════════════════════════════════
-- CICLO DE VIDA
-- ═══════════════════════════════════════════════════════════
status VARCHAR(20) DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ DEFAULT NOW(),
activated_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
deactivated_at TIMESTAMPTZ,
-- ═══════════════════════════════════════════════════════════
-- CONSTRAINTS
-- ═══════════════════════════════════════════════════════════
CONSTRAINT valid_kek_status CHECK (
status IN ('PENDING', 'ACTIVE', 'DEACTIVATED', 'DESTROYED')
),
CONSTRAINT valid_purpose CHECK (
purpose IN ('DATA_ENCRYPTION', 'SECRET_ENCRYPTION', 'SIGNING', 'KEY_WRAPPING')
)
);
```
---
# C.4 Perfiles de Cifrado
## C.4.1 Perfil NONE
```yaml
profile: NONE
description: "Sin cifrado gestionado por el sistema"
transport: TLS 1.3 (obligatorio)
at_rest: Sin cifrar
use_cases:
- Datos públicos
- Logs técnicos no sensibles
- Métricas de rendimiento
key_vault: No requerido
```
## C.4.2 Perfil E2E_BASIC
```yaml
profile: E2E_BASIC
description: "Cifrado estándar para datos internos"
transport: TLS 1.3
at_rest:
algorithm: AES-256-GCM
key_derivation: PBKDF2-SHA256
key_rotation: Anual
use_cases:
- Documentos internos
- Emails corporativos
- Datos de configuración
key_vault:
required: true
key_type: ENCRYPTION_KEY
access_policy: module_based
```
## C.4.3 Perfil E2E_STRICT
```yaml
profile: E2E_STRICT
description: "Cifrado fuerte para datos sensibles"
transport: mTLS (mutual TLS)
at_rest:
algorithm: AES-256-GCM
key_derivation: Argon2id
key_rotation: Trimestral
envelope_encryption: true
additional:
- PII masking antes de procesar
- Audit logging obligatorio
- Zero-knowledge donde sea posible
use_cases:
- PII (datos personales)
- Datos financieros
- Información médica
- Biometría
key_vault:
required: true
key_type: ENCRYPTION_KEY
access_policy: per_record
key_isolation: true
```
---
# C.5 Operaciones del Key Vault
## C.5.1 URIs del Key Vault
El contrato común usa referencias URI al Key Vault:
```
kv://production/encryption/player_abc123
kv://production/api-keys/openrouter
kv://staging/certificates/mtls-client
```
Formato:
```
kv://{environment}/{category}/{secret_id}
```
## C.5.2 Integración con el Contrato
En el request:
```json
"security": {
"encryption_profile": "E2E_STRICT",
"data_sensitivity": "HIGH",
"key_vault_ref": "kv://production/encryption/player_abc123"
}
```
Alfred al procesar:
1. Extrae `key_vault_ref`
2. Solicita la llave al Key Vault (pasando `trace_id`)
3. Descifra el payload si viene cifrado
4. Procesa con GRACE
5. Cifra el resultado con la misma llave (o una derivada)
6. Almacena resultado cifrado
---
# C.6 Flujo de Cifrado End-to-End
## C.6.1 Envelope Encryption
```
┌─────────────────────────────────────────────────────────────────┐
│ ENVELOPE ENCRYPTION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Plaintext │ │
│ │ Data │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Data │◄───│ DEK │ (Data Encryption Key) │
│ │ Encrypted │ │ (efímera) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────┐ ┌─────────────┐ │
│ │ │ DEK │◄───│ KEK │ │
│ │ │ Encrypted │ │ (Key Vault) │ │
│ │ └──────┬──────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ Stored Package: │ │
│ │ - encrypted_data │ │
│ │ - encrypted_dek │ │
│ │ - kek_id │ │
│ │ - nonce │ │
│ │ - algorithm │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## C.6.2 Implementación Python
```python
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
class EnvelopeEncryption:
"""Implementa envelope encryption para E2E_STRICT."""
def __init__(self, key_vault_client):
self.vault = key_vault_client
async def encrypt(self, plaintext: bytes, kek_id: str) -> dict:
"""Cifra datos usando envelope encryption."""
# 1. Generar DEK efímera
dek = os.urandom(32)
data_nonce = os.urandom(12)
# 2. Cifrar datos con DEK
aesgcm = AESGCM(dek)
encrypted_data = aesgcm.encrypt(data_nonce, plaintext, None)
# 3. Obtener KEK y cifrar DEK
kek = await self.vault.get_secret(kek_id)
dek_nonce = os.urandom(12)
kek_aesgcm = AESGCM(kek)
encrypted_dek = kek_aesgcm.encrypt(dek_nonce, dek, kek_id.encode())
return {
"encrypted_data": base64.b64encode(encrypted_data).decode(),
"encrypted_dek": base64.b64encode(encrypted_dek).decode(),
"data_nonce": base64.b64encode(data_nonce).decode(),
"dek_nonce": base64.b64encode(dek_nonce).decode(),
"kek_id": kek_id,
"algorithm": "AES-256-GCM"
}
async def decrypt(self, package: dict) -> bytes:
"""Descifra datos usando envelope encryption."""
# 1. Obtener KEK
kek = await self.vault.get_secret(package["kek_id"])
# 2. Descifrar DEK
kek_aesgcm = AESGCM(kek)
dek = kek_aesgcm.decrypt(
base64.b64decode(package["dek_nonce"]),
base64.b64decode(package["encrypted_dek"]),
package["kek_id"].encode()
)
# 3. Descifrar datos
aesgcm = AESGCM(dek)
return aesgcm.decrypt(
base64.b64decode(package["data_nonce"]),
base64.b64decode(package["encrypted_data"]),
None
)
```
---
# C.7 Gestión de Llaves
## C.7.1 Jerarquía de Llaves
```
┌─────────────────────────────────────────────────────────────────┐
│ JERARQUÍA DE LLAVES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MASTER KEY (MK) │ │
│ │ Almacenada fuera del sistema │ │
│ │ (HSM / KMS externo / Variable de entorno) │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ KEK-DATA │ │ KEK-SECRETS │ │ KEK-SIGNING │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ DEK-1..n │ │ API Keys │ │ JWT Keys │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## C.7.2 Política de Rotación
| Tipo de Llave | Rotación | Período de Gracia | Automatizable |
|---------------|----------|-------------------|---------------|
| Master Key | Anual | 30 días | No (manual) |
| KEK | Semestral | 14 días | Sí |
| DEK | Por sesión | N/A | Automático |
| API Keys | 90 días | 7 días | Sí |
---
# C.8 Control de Acceso
## C.8.1 Políticas de Acceso
```json
{
"secret_id": "openrouter-api-key",
"access_policy": {
"type": "MODULE_BASED",
"rules": [
{
"principal_type": "MODULE",
"principals": ["CLASSIFIER", "SUMMARIZER", "OCR_CORE"],
"operations": ["READ"],
"conditions": {
"require_trace_id": true
}
},
{
"principal_type": "ADMIN",
"principals": ["admin@tzzr.pro"],
"operations": ["READ", "UPDATE", "ROTATE", "DELETE"],
"conditions": {
"require_mfa": true
}
}
],
"default_deny": true
}
}
```
---
# C.9 Integración con SENTINEL
## C.9.1 Reglas de Auditoría
```yaml
rules:
KV-001:
name: "Accesos denegados excesivos"
severity: HIGH
condition: "denied_count_1h > 5 per accessor"
action: ALERT_SECURITY
KV-002:
name: "Secreto próximo a expirar"
severity: MEDIUM
condition: "expires_at < NOW() + 7 days"
action: NOTIFY_ADMIN
KV-003:
name: "Rotación pendiente"
severity: MEDIUM
condition: "last_rotated_at < NOW() - rotation_interval"
action: SCHEDULE_ROTATION
```
---
# C.10 Checklist de Implementación
## Infraestructura:
- [ ] Crear tablas VAULT_SECRETS, VAULT_ACCESS_LOG, VAULT_ENCRYPTION_KEYS
- [ ] Configurar Master Key (variable de entorno o KMS)
- [ ] Generar KEKs iniciales
- [ ] Configurar backup automático
## Integración:
- [ ] Implementar cliente Key Vault
- [ ] Integrar con ContractBuilder
- [ ] Implementar envelope encryption
## Seguridad:
- [ ] Configurar políticas de acceso
- [ ] Habilitar logging de accesos
- [ ] Configurar alertas SENTINEL
- [ ] Establecer rotación automática
---
**Fin del Documento 07c — Key Vault y Criptografía**
*Referencia: `S-CONTRACT.md` v1.2*