Volver a la wiki

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:

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

Detalles técnicos

Véase también

Subir