CreaRack-SL

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:

  1. Rotación de claves sin cascada: Rotar SECRET_KEY (p.ej. por fuga, cambio de hosting) no invalida cada token del Agent vivo
  2. Seguridad en profundidad: Si SECRET_KEY se filtra, un atacante no puede forjar tokens del Agent (usa clave distinta)
  3. Fallback inerte: Si AGENT_JWT_SECRET no está en env, usa SECRET_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 JWT
  • generate_refresh_token() — refresh token
  • verify_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.api como desde terminal.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]]