Volver a la wiki

Guía de Seguridad — CreaRack Pro

Guía de Seguridad — CreaRack Pro

Versión: v1.0.51 Última actualización: 07-04-2026 Audiencia: Desarrolladores, administradores, agentes IA


1. Arquitectura de Seguridad

Internet
    │
    ▼
┌─────────────────────┐
│  Hetzner Firewall   │  Solo 22 (SSH restringido), 80, 443, ICMP
└─────────────────────┘
    │
    ▼
┌─────────────────────┐
│  Traefik (Reverse   │  HTTPS termination, certificados Let's Encrypt
│  Proxy)             │  Headers: HSTS, COOP, X-Frame-Options
└─────────────────────┘
    │
    ▼
┌─────────────────────┐
│  Django (Daphne)    │  CSP, Permissions-Policy, Rate Limiting
│  Puerto 8000        │  JWT Auth (Agent), Session Auth (Users)
└─────────────────────┘
    │
    ├── PostgreSQL 18 (solo red Docker interna)
    ├── Valkey (Redis-compatible, con password)
    └── VictoriaMetrics (solo red Docker interna)

2. Capas de Protección

2.1 Red (Hetzner Cloud Firewall)

ReglaPuertoOrigenDescripción
SSH22IP trabajo (fija) + rango casa (/16)Acceso administrativo restringido
HTTP800.0.0.0/0Redirect a HTTPS
HTTPS4430.0.0.0/0Tráfico web
ICMP—0.0.0.0/0Ping para monitoreo
Default**DENY

Todo lo demás queda bloqueado a nivel de cloud provider — ni siquiera llega al VPS.

Dokploy (puerto 3000): Acceso temporal añadiendo regla por IP en Hetzner Firewall. Túnel SSH no funciona (Dokploy valida origin).

GitHub Webhooks (auto-deploy): Para que Dokploy reciba webhooks de GitHub en el puerto 3000, es necesario permitir las 4 subredes de GitHub en iptables DOCKER-USER. Estas reglas se añaden en /root/restore-firewall.sh:

iptables -I DOCKER-USER -s 192.30.252.0/22 -p tcp --dport 3000 -j ACCEPT
iptables -I DOCKER-USER -s 185.199.108.0/22 -p tcp --dport 3000 -j ACCEPT
iptables -I DOCKER-USER -s 140.82.112.0/20 -p tcp --dport 3000 -j ACCEPT
iptables -I DOCKER-USER -s 143.55.64.0/20 -p tcp --dport 3000 -j ACCEPT

Sin estas reglas, el firewall Hetzner puede bloquear el webhook → Dokploy no recibe el push → auto-deploy no se dispara.

2.2 Transporte (HTTPS + HSTS)

# config/settings/production.py
SECURE_HSTS_SECONDS = 31536000        # 1 año
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

SECURE_COOKIES — Variable de entorno para staging: El servidor de staging corre en HTTP (sin SSL), por lo que las cookies seguras romperían la sesión. La variable de entorno SECURE_COOKIES controla este comportamiento:

EntornoValorResultado
ProducciónSECURE_COOKIES=true (default)SESSION_COOKIE_SECURE=True, CSRF_COOKIE_SECURE=True
StagingSECURE_COOKIES=falseSESSION_COOKIE_SECURE=False, CSRF_COOKIE_SECURE=False

Configurado en config/settings/production.py y pasado vía panel Dokploy. Nunca usar SECURE_COOKIES=false en producción.

2.3 HTTP Headers

HeaderValorPropósito
Content-Security-Policydefault-src 'none'; script-src 'self' 'nonce-{random}'; ...Previene XSS, controla recursos
Permissions-Policycamera=(), microphone=(), geolocation=(), payment=(), usb=(), ...Bloquea APIs del browser no usadas
Cross-Origin-Opener-Policysame-originAísla window context
X-Frame-OptionsDENYPreviene clickjacking
X-Content-Type-OptionsnosniffPreviene MIME sniffing
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadFuerza HTTPS

Implementación: core/middleware/csp.py (CSP + Permissions-Policy), Django SecurityMiddleware (el resto).

CSP Architecture:

2.4 Rate Limiting

# config/settings/base.py
RATE_LIMIT_REQUESTS = 100   # Max requests por ventana
RATE_LIMIT_WINDOW = 60      # Ventana en segundos

RATE_LIMIT_ENDPOINTS = {
    '/accounts/login/': {'requests': 5, 'window': 60},
    '/api/auth/passkey-options/': {'requests': 10, 'window': 60},
    '/api/auth/validate-and-setup-biometric/': {'requests': 5, 'window': 60},
    '/admin/login/': {'requests': 5, 'window': 60},
    '/api/blueprints/auto-plan': {'requests': 10, 'window': 60},
}

IP Spoofing Protection: X-Forwarded-For solo se acepta desde proxies confiados (Traefik gateway 127.0.0.1, 172.17.0.1). Peticiones directas usan REMOTE_ADDR.

Implementación: core/middleware/rate_limit.py

2.5 Endpoint Protection

EndpointProtecciónArchivo
/metricsSolo IPs internas (127.x, 172.16.x, 10.x, 192.168.x)core/middleware/metrics_restrict.py
/api/agent/diag/oids/JWT Agent auth obligatoriaterminal/api/sentinel.py
/healthSin auth, errores sanitizados (no leaks)core/views.py
/ready, /aliveSin auth, sin CSRF (probes de orchestrador)core/views.py

3. Autenticación

3.1 Sesiones (Usuarios Web)

SettingValorRazón
Enginedjango.contrib.sessions.backends.cacheValkey-backed, rápido
HttpOnlyTrueJavaScript no puede leer la cookie
SameSiteStrictMáxima protección CSRF
SecureTrue (producción)Solo por HTTPS
Edad2 semanasBalance seguridad/comodidad

3.2 JWT (Agent Local)

AspectoValor
AlgoritmoHS256 (explícito en encode y decode)
Access token72 horas
Refresh token180 días
AlmacenamientoWindows DPAPI (cifrado en disco)
VerificaciónServer-side en cada WebSocket connect

3.3 Passkeys/WebAuthn

3.4 Passwords

4 validadores activos: similitud con username, longitud mínima 8, diccionario de comunes, solo numéricos.


4. Multi-Tenancy (Aislamiento de Datos)

CreaRack Pro usa dos capas de aislamiento que trabajan conjuntamente:

  1. Capa aplicación (Django ORM) — filtros organization=org en todos los queries
  2. Capa base de datos (PostgreSQL RLS) — políticas Row-Level Security como red de seguridad

4.1 Patrón Estándar (Capa ORM)

Todos los queries deben filtrar por organización:

# CORRECTO — filtra por org del usuario
device = get_object_or_404(Device, id=device_id, rack__organization=org)

# INCORRECTO — acceso a cualquier recurso por ID
device = get_object_or_404(Device, id=device_id)

4.2 get_current_org()

# core/utils/organization.py
def get_current_org(request):
    if request.user.is_authenticated and hasattr(request.user, 'organization'):
        return request.user.organization
    return None  # Callers DEBEN manejar None

IMPORTANTE: No hay fallback. Si el usuario no tiene organización, retorna None. Todos los endpoints deben verificar:

org = get_current_org(request)
if not org:
    return 400, {"error": "No organization context"}

4.3 Stencils (caso especial)

Los stencils de sistema (organization=NULL) son compartidos. Usar:

from django.db.models import Q
stencil = Stencil.objects.get(
    Q(organization=org) | Q(organization__isnull=True),
    id=stencil_id,
)

4.4 PostgreSQL Row-Level Security (Capa DB)

Implementado en v1.0.50 (03-04-2026). Migration core/0017_rls_policies.

RLS actúa como red de seguridad: si un bug en el ORM omite el filtro por organización, PostgreSQL bloquea el acceso igualmente.

Mecanismo:

Cobertura: 35 tablas con políticas RLS:

Bypass:

Verificación:

-- Comprobar que RLS está activo en una tabla
SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relname = 'racks_rack';

-- Comprobar política
SELECT * FROM pg_policies WHERE tablename = 'racks_rack';

4.5 Ciclo de Vida del Tenant

FaseProtección
OnboardingPOST /api/signup — transaction atómica, plan starter, módulos auto-sync
OperaciónORM filters + RLS policies + Module Gating middleware
Soft-deletedeleted_at set → org inaccesible pero datos preservados
PurgeHuey task purge_deleted_organizations — hard-delete tras 90 días + backup files
IntegridadHuey task check_tenant_integrity — detecta anomalías cross-tenant diariamente

4.6 Backups Per-Tenant


5. SSRF Protection (Webhooks)

Los Notification Channels de ITSM permiten al usuario configurar URLs de webhook. El servidor hace HTTP POST a esas URLs, creando un vector SSRF.

5.1 Validación de URLs

# monitoring/services/notification_service.py
def validate_webhook_url(url):
    # 1. Solo esquemas http/https
    # 2. Resolver DNS del hostname
    # 3. Verificar que la IP resuelta NO esté en redes bloqueadas:
    #    - 127.0.0.0/8 (loopback)
    #    - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (privadas)
    #    - 169.254.0.0/16 (link-local + cloud metadata)
    #    - ::1, fc00::/7, fe80::/10 (IPv6 privadas)
    # 4. Bloquear hostnames conocidos: localhost, metadata.google.internal

5.2 Protecciones Adicionales

MedidaValor
Timeout3 segundos
RedirectsDeshabilitados (allow_redirects=False)
Máx response1 MB (configurado, no implementado en stream)

6. Mass Assignment Protection

6.1 Patrón Seguro (Whitelist)

# CORRECTO — whitelist explícito
UPDATABLE_FIELDS = {'display_name', 'aliases', 'priority', ...}
for key, value in data.items():
    if key in UPDATABLE_FIELDS:
        setattr(vendor, key, value)

# INCORRECTO — blacklist (frágil)
data.pop('slug', None)
for key, value in data.items():
    setattr(vendor, key, value)

# INCORRECTO — pass-through directo
Model.objects.create(**payload.dict())

6.2 Defensa en Profundidad

Aunque el Schema de Ninja filtra campos, siempre hacer data.pop('id', None) antes de create(**data) como defensa adicional.


7. CSRF Protection


8. XSS Prevention


9. SQL Injection Prevention


10. Infraestructura Docker

10.1 Valkey (Cache)

# compose.prod.yml
cache:
  command: >
    valkey-server
      --requirepass ${REDIS_PASSWORD}
      --maxmemory 512mb
      --maxmemory-policy allkeys-lru

Password: Generada con openssl rand -hex 32 (solo 0-9a-f). NUNCA usar base64 — los caracteres /+= rompen URLs de Redis.

10.2 PostgreSQL

Solo accesible dentro de la red Docker interna. Sin puerto expuesto al host.

10.3 Django SECRET_KEY

# config/settings/production.py
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "")
if not SECRET_KEY or "insecure" in SECRET_KEY:
    raise ImproperlyConfigured(
        "DJANGO_SECRET_KEY must be set to a secure random value in production"
    )

11. VibeSec Pro (AI Security Skill)

Ubicación: .claude/skills/SKILL.md (2,401 líneas, excluido de git)

Archivo de instrucciones que enseña a los agentes IA (Claude, Cursor, Copilot) a escribir código seguro por defecto. Cubre:

Licencia: Comercial (lifetime). No se sube al repositorio público.


12. Auditorías Realizadas

11-03-2026 — VibeSec Audit (10 hallazgos)

#TipoSeveridadHallazgoRemediación
1SSRFCríticoWebhooks ITSM sin validación de URLvalidate_webhook_url() + IP blocking + no redirects
2IDORCríticoDevice details sin filtro orgrack__organization=org en query
3IDORCríticoDevice status sin filtro orgrack__organization=org en query
4Mass AssignmentAltoVendorProfile.create(**dict())data.pop('id', None)
5Mass AssignmentAltoSetattr con blacklistWhitelist UPDATABLE_FIELDS
6IDORAltoStencils con post-lookup checkPre-filtro con Q(org) | Q(null)
7SSRFAltoRedirects habilitados en webhooksallow_redirects=False
8LogicMedioget_current_org() fallback a first orgRetorna None
9Info LeakMedioError interno en test webhookMensaje genérico + log
10DoSMedioTimeout 10s en webhooksReducido a 3s

11-03-2026 — Security Hardening (12 remediaciones)

#SeveridadHallazgoRemediación
1CríticoAPI key en .envConfirmado falso positivo (no en git)
2Crítico/diag/oids sin authJWT obligatorio
3CríticoSECRET_KEY no validadoImproperlyConfigured si falta
4Crítico/metrics públicoMiddleware IP restriction
5AltoCSP unsafe-eval✅ Eliminado — Alpine CSP build + nonces
6AltoCSP localhost✅ Path-based — solo en Agent pages
7Alto/health leak erroresSanitizado + logger
8AltoRate limit IP spoofingTrusted proxies pattern
9AltoValkey sin password--requirepass + env var
10MedioSin Permissions-PolicyHeader añadido en CSP middleware
11MedioSameSite=LaxActualizado a Strict
12MedioALLOWED_HOSTS ["*"] en devRestringido a localhost

13. Pruebas de Seguridad (External Scan)

13.1 Metodología

Scan externo desde fuera de la red (misma perspectiva que un atacante) contra https://crearack.com. Equivalente a lo que ejecutan herramientas como Nikto, OWASP ZAP, Nuclei y SimpleSecCheck.

Categorías probadas:

  1. HTTP Security Headers (8 headers)
  2. TLS/SSL (versiones, certificado)
  3. Rutas sensibles (9 paths comunes)
  4. Host header injection
  5. CORS misconfiguration
  6. HTTP methods (OPTIONS, TRACE, PUT)
  7. Rate limiting (decremento verificado)
  8. Error pages (info leakage)
  9. Endpoints internos (/health, /metrics, /diag/oids)

13.2 Resultados (11-03-2026)

Headers HTTP — 8/8 presentes

HeaderValorEstado
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadPASS
X-Frame-OptionsDENYPASS
X-Content-Type-OptionsnosniffPASS
Content-Security-Policy11 directivasPASS
Referrer-Policystrict-origin-when-cross-originPASS
Permissions-Policy8 APIs bloqueadasPASS
Cross-Origin-Opener-Policysame-originPASS
X-Ratelimit-*300 req/ventanaPASS

TLS/SSL

CheckResultado
TLS 1.3Soportado
TLS 1.2Soportado
TLS 1.1 / 1.0Bloqueado (Cloudflare upgrade a 1.3)
CertificadoLet’s Encrypt R13, válido 3 meses

Rutas sensibles — Todas bloqueadas

/.env           → 404    /.git/config     → 404
/wp-admin       → 404    /debug           → 404
/server-status  → 404    /phpinfo.php     → 404
/robots.txt     → 404    /sitemap.xml     → 404
/.well-known/security.txt → 404
/metrics        → 403 Forbidden (IP restriction)
/api/diag/oids  → 404 (requires JWT)

Protecciones activas

TestComandoResultado
Host injectioncurl -H "Host: evil.com"404 (Traefik bloquea)
CORS reflectioncurl -H "Origin: https://evil.com"Sin Access-Control headers
HTTP PUTcurl -X PUT /403 CSRF
HTTP TRACEcurl -X TRACE /302 redirect (no ejecuta)
Error page leakcurl /nonexistent-pageSolo “Not Found” (sin stack trace)
Admin exposurecurl /admin/302 → login (no expone panel)
Rate limiting5 requests consecutivos300→294 (decrementa correctamente)

Endpoint /health (corregido 11-03-2026)

# ANTES (información sensible expuesta):
# {"status":"healthy","version":"1.0.33",
#  "checks":{"database":{"status":"healthy","type":"postgresql"},
#            "cache":{"status":"healthy","type":"valkey"}},
#  "response_time_ms":18.1}

# DESPUÉS (sanitizado):
# {"status":"healthy","checks":{"database":"ok","cache":"ok"}}

Eliminado: versión, tipo de base de datos, tipo de cache, tiempo de respuesta.

13.3 Mozilla Observatory (11-03-2026) — Score: 125/100 (A+)

Scan via API: https://observatory-api.mdn.mozilla.net/api/v2/analyze?host=crearack.com

#TestEstadoPuntosDetalle
1CookiesPASS+5Secure + HttpOnly + SameSite
2CORSPASS0No implementado (correcto)
3RedirectionPASS0HTTP → HTTPS correcto
4Referrer-PolicyPASS+5strict-origin-when-cross-origin
5HSTSPASS01 año + includeSubDomains + preload
6Subresource IntegrityN/A0Scripts from same origin
7X-Content-Type-OptionsPASS0nosniff
8X-Frame-OptionsPASS+5DENY via CSP frame-ancestors
9Cross-Origin Resource PolicyN/A0No implementado
10Content-Security-PolicyPASS0Nonce-based, default-src 'none', no unsafe-inline in script-src

Cálculo: 100 (base) + 10 (no unsafe-inline in style-src) + 15 (bonus, base ≥ 90) = 125 (A+)

CSP Migration (completada 11-03-2026): 7 fases — Alpine CSP build, nonces en 25 scripts, ~230 inline handlers convertidos a data-action, default-src 'none', path-based CSP para Agent pages. Ver Documentation/archive/plans/CSP_MIGRATION_PLAN.md.

13.4 Hallazgos abiertos (trade-offs aceptados)

#HallazgoSeveridadMotivo
1style-src-elem/attr 'unsafe-inline'BajaRequerido por HTMX (<style> dinámicos), ECharts/Konva (atributos style=""). No permite ejecución de código. Granular CSP L3 — Observatory no lo detecta (125/100)
2Server header daphneBajaInformación menor, Daphne es estándar
3HSTS preload no registradoBajaDirective presente pero no enviado a hstspreload.org

13.5 Cómo reproducir el scan

# 0. Mozilla Observatory (API directa — resultado JSON completo)
curl -s "https://observatory-api.mdn.mozilla.net/api/v2/analyze?host=crearack.com"

# 1. Headers HTTP (todos los headers de seguridad)
curl -sI https://crearack.com

# 2. TLS versions
for ver in tls1 tls1_1 tls1_2 tls1_3; do
  echo | openssl s_client -connect crearack.com:443 -$ver 2>&1 | grep "Protocol"
done

# 3. Rutas sensibles
for path in ".env" ".git/config" "wp-admin" "debug" "server-status" "metrics"; do
  echo "$path → $(curl -sI -o /dev/null -w '%{http_code}' https://crearack.com/$path)"
done

# 4. Host injection
curl -sI https://crearack.com -H "Host: evil.com"

# 5. CORS
curl -sI https://crearack.com -H "Origin: https://evil.com" | grep "access-control"

# 6. HTTP methods
curl -sI -X PUT https://crearack.com/
curl -sI -X TRACE https://crearack.com/

# 7. Rate limiting
for i in $(seq 1 5); do
  curl -sI https://crearack.com/login | grep "x-ratelimit-remaining"
done

# 8. Info leak en /health
curl -s https://crearack.com/health

# 9. Error pages
curl -s https://crearack.com/nonexistent-12345

13.6 Herramientas recomendadas

HerramientaTipoUso
curl + scriptsCLIScan rápido de headers, rutas, métodos
Mozilla ObservatoryOnline/API gratis10 tests HTTP, scoring oficial. API: observatory-api.mdn.mozilla.net/api/v2/analyze?host=
testssl.shCLIAnálisis TLS/SSL exhaustivo
NucleiCLITemplates de vulnerabilidades conocidas
SimpleSecCheckDocker31 scanners integrados (build pesado, Node 18 deprecado)

Nota: Estas herramientas comprueban la superficie externa. No detectan IDOR, SSRF, mass assignment ni lógica de negocio — eso requiere auditoría manual del código (ver sección 12).


14. Checklist para Nuevos Endpoints

Al crear un nuevo endpoint API, verificar:


15. Passwords y Secretos

Generación

# Para Redis/Valkey URLs (solo hex, sin caracteres especiales)
openssl rand -hex 32

# Para Django SECRET_KEY
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

NUNCA usar openssl rand -base64 para passwords que se embeben en URLs — los caracteres /+= rompen el parser de URLs de Python.

Almacenamiento

SecretoDóndeCómo
DJANGO_SECRET_KEYDokploy env varsVariable de entorno
REDIS_PASSWORDDokploy env varsVariable de entorno
GEMINI_API_KEYDokploy env varsVariable de entorno
Agent JWT tokensWindows DPAPICifrado en disco del usuario

Rotación

SecretoFrecuencia
DJANGO_SECRET_KEYAnual o tras incidente
REDIS_PASSWORDAnual o tras incidente
GEMINI_API_KEYTrimestral
Agent access tokens72 horas (automático)

16. Respuesta a Incidentes

  1. Revocar acceso: Cambiar passwords comprometidas en Dokploy env vars
  2. Redeploy: Push vacío o redeploy manual en Dokploy
  3. Verificar: Ejecutar checklist de Fase 5 (ver archive/plans/SECURITY_HARDENING_PLAN.md)
  4. Auditar logs: docker compose logs web | grep ERROR
  5. Documentar: Actualizar esta guía con lecciones aprendidas

Referencias


Mantenido por: Equipo CreaRack + Claude (Anthropic)

Véase también

Subir