Clave JWT dedicada (AGENT_JWT_SECRET) separada de SECRET_KEY
Decisión
Introducir una clave de firma dedicada (AGENT_JWT_SECRET) para los JWT del Local Agent, separada del SECRET_KEY global de Django.
Rationale:
- Rotación de claves sin cascada: Rotar
SECRET_KEY(p.ej. por fuga, cambio de hosting) no invalida cada token del Agent vivo - Seguridad en profundidad: Si
SECRET_KEYse filtra, un atacante no puede forjar tokens del Agent (usa clave distinta) - Fallback inerte: Si
AGENT_JWT_SECRETno está en env, usaSECRET_KEY(comportamiento actual) — sin breaking change hasta activarla en Dokploy
Implementación
Configuración (config/settings/base.py)
AGENT_JWT_SECRET = os.getenv("AGENT_JWT_SECRET", "")
# Empty → fallback inerte a SECRET_KEY en terminal/api/auth.py
Helper (terminal/api/auth.py)
def _agent_jwt_secret() -> str:
"""Signing key for the Agent JWT: dedicated AGENT_JWT_SECRET if set,
else the Django SECRET_KEY (inert fallback)."""
return getattr(settings, "AGENT_JWT_SECRET", "") or settings.SECRET_KEY
Usado en:
generate_agent_token()— emisión JWTgenerate_refresh_token()— refresh tokenverify_agent_token()— verificación REST
WebSocket Consumer (terminal/consumers.py)
El connect() method ahora importa _agent_jwt_secret() y verifica con ella:
from terminal.api.auth import _agent_jwt_secret
payload = jwt.decode(token, _agent_jwt_secret(), algorithms=["HS256"])
Sincronía: Activar AGENT_JWT_SECRET en Dokploy force un re-auth de la flota (comportamiento esperado en toda rotación de clave).
Datetime aware (terminal/api/auth.py)
Los campos iat / exp del JWT ahora usan datetime.now(UTC) en vez del deprecado datetime.utcnow():
payload = {
"agent_id": agent_id,
"iat": datetime.now(UTC),
"exp": datetime.now(UTC) + timedelta(hours=expiry_hours),
...
}
Impacto
- Actual: Fallback a
SECRET_KEY, sin cambio visible - Al activar en Dokploy: Tokens antigüos invalidan (esperado), flota re-autentica, a partir de ahí clave dedicada
- Seguridad: SPOF reducido (2 claves en lugar de 1)
Detalles técnicos
- Helper sin imports circulares (importado tanto desde
terminal.apicomo desdeterminal.consumers) - Clave fallback inerte: no rompe hasta que se declare env var en Dokploy
- RFC 5234 JWT: HS256, mismo algoritmo que antes
Véase también
- [[feature—config-ia—auditoria-suprema-etapa-3]]
- [[incident—20260611—fuga-cross-tenant-llm]]
- [[entity—terminal—function—agent-jwt-secret]]
- [[concept—security—key-management]]
- [[entity—terminal—endpoint—agent-auth]]