TrafficServerLocal/README.md
merios c324093c0a docs: autoria institucional del laboratorio
Agrega el credito "Laboratorio de investigacion en Software Libre
Gugler - FCyT" en el encabezado y la seccion Licencia del README, y
como pie en ambos documentos de docs/.

Se documenta la exencion institucional en la politica de publicacion
anonima: el nombre del laboratorio y git.gugler.com.ar son publicos por
decision propia y no se anonimizan. El verificador recibe una lista
blanca que separa autores institucionales de los que hay que revisar, y
excluye el dominio del laboratorio del filtro de emails.
2026-10-15 11:59:23 -03:00

16 KiB

TrafficServerLocal

Autoría: Laboratorio de investigacion en Software Libre Gugler - FCyT.

Infraestructura base para alojar múltiples proyectos Docker detrás de un único proxy inverso Traefik v3, con soporte para HTTP y HTTPS:

  • Dominios públicos → certificados de Let's Encrypt (automático).
  • Dominios locales .local → certificados auto-firmados montados desde el filesystem.

Este repositorio contiene solo la infraestructura reutilizable. El código, las bases de datos y los datos persistentes de cada proyecto viven en ~/srv/projects/<proyecto>/ y se publican en sus propios repositorios.


1. Estructura

~/srv/
├── core/                         # Reverse proxy Traefik
│   ├── docker-compose.yml
│   └── data/
│       ├── traefik.yml           # Configuración estática
│       ├── dynamic_tls.yml       # Certificados estáticos (.local)
│       ├── acme.json             # Clave ACME (NO versionado)
│       └── certs/                # .crt / .key locales (NO versionados)
├── projects/                     # Un directorio por proyecto
│   └── <proyecto>/
│       ├── app/                  # Código fuente / Dockerfile
│       ├── data/{db,storage}/    # Persistencia
│       ├── .env                  # Secretos (chmod 600)
│       └── docker-compose.yml    # Etiquetas de Traefik
└── backups/                      # Respaldos (fuera de Git)

En este repositorio (lo publicable):

TrafficServerLocal/
├── README.md
├── LICENSE                      # GPL-3.0
├── .gitignore
├── .env.global
├── init_project.sh              # Generador de proyectos base
├── docs/
│   ├── arquitectura.md          # Guía de instalación y gestión de URLs
│   ├── http-https.md            # Referencia de etiquetas HTTP/HTTPS y TLS
│   └── publicacion-anonima.md   # Política de publicación anonimizada
├── tools/
│   └── verificar-anonimato.sh   # Chequea PII antes de publicar
└── core/
    ├── docker-compose.yml
    └── data/
        ├── traefik.yml
        ├── dynamic_tls.yml
        └── certs/README.md      # Cómo generar certificados .local

2. Requisitos

  • Docker Engine y Docker Compose v2 (docker compose)

  • El usuario debe pertenecer al grupo docker:

    sudo usermod -aG docker "$USER"
    newgrp docker      # o cerrá y volvé a iniciar sesión
    
  • Puertos 80/443 libres en el host

  • Para HTTPS público: el dominio debe apuntar por DNS a la IP del servidor


3. Puesta en marcha

3.1 Estructura y red compartida

mkdir -p ~/srv/core ~/srv/projects
docker network create proxy_network     # red externa compartida

proxy_network es externa: la crea una vez y la reutilizan el proxy y todos los proyectos.

3.2 Almacenamiento de certificados ACME

mkdir -p ~/srv/core/data
touch ~/srv/core/data/acme.json
chmod 600 ~/srv/core/data/acme.json

acme.json contiene la clave privada de la cuenta de Let's Encrypt. Nunca se versiona (ya está en .gitignore).

3.3 Levantar el proxy

cd ~/srv/core
docker compose up -d
docker ps | grep core-proxy

Traefik queda escuchando en :80 (HTTP) y :443 (HTTPS).


4. Cómo funciona el proxy

Concepto Detalle
Provider Docker Traefik lee /var/run/docker.sock (solo lectura) y detecta contenedores con traefik.enable=true
exposedByDefault: false Solo se publican los contenedores etiquetados
Provider File Carga certificados estáticos desde dynamic_tls.yml con watch: true
proxy_network Red externa por la que Traefik alcanza a los servicios
Red default Red bridge aislada del proxy para bases de datos, colas, etc.
no-new-privileges Endurecimiento básico del contenedor del proxy
Dashboard Habilitado en api.dashboard (ver Seguridad)

Puertos expuestos por el proxy

ports:
  - "80:80"     # entrypoint: web      → HTTP
  - "443:443"   # entrypoint: websecure → HTTPS

5. Crear un proyecto base

cd ~/srv
./init_project.sh api-ventas api.midominio.com 80

Genera:

~/srv/projects/api-ventas/
├── app/                # Código fuente, repo o Dockerfile
├── data/
│   ├── db/             # Persistencia de base de datos
│   └── storage/        # Archivos del usuario
├── .env                # Variables (chmod 600)
└── docker-compose.yml  # Conectado a proxy_network

El directorio del proyecto queda con permisos 700.

Pasos siguientes

# 1. Colocar el código
git clone https://github.com/tu-usuario/api-ventas.git \
  ~/srv/projects/api-ventas/app/
# o bien:
cp -r /ruta/a/tus/archivos/* ~/srv/projects/api-ventas/app/

# 2. Ajustar docker-compose.yml
#    - Opción A: descomentar  build: { context: ./app, dockerfile: Dockerfile }
#    - Opción B: cambiar   image: api-ventas:latest  por tu imagen
#    - Servicios extra (Postgres, Redis) → red `default` (aislados del proxy)

# 3. Levantar
cd ~/srv/projects/api-ventas
docker compose up -d

Variables generadas en .env

APP_NAME=api-ventas
APP_DOMAIN=api.midominio.com
PORT=80

Agregá ahí tus credenciales. Nunca subas ese archivo a Git.


6. HTTP / HTTPS

Detalle completo en docs/http-https.md.

A) Dominio público + Let's Encrypt

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=proxy_network"

  # Middleware de redirección HTTP → HTTPS
  - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
  - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"

  # Router HTTP (redirige)
  - "traefik.http.routers.miapp-http.rule=Host(`api.midominio.com`)"
  - "traefik.http.routers.miapp-http.entrypoints=web"
  - "traefik.http.routers.miapp-http.middlewares=redirect-to-https"

  # Router HTTPS (emite el certificado)
  - "traefik.http.routers.miapp-https.rule=Host(`api.midominio.com`)"
  - "traefik.http.routers.miapp-https.entrypoints=websecure"
  - "traefik.http.routers.miapp-https.tls=true"
  - "traefik.http.routers.miapp-https.tls.certresolver=letsencrypt"

  # Servicio interno
  - "traefik.http.services.miapp.loadbalancer.server.port=80"

B) Dominio local .local + certificado auto-firmado

  1. Generar el certificado:

    cd ~/srv/core/data/certs
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
      -keyout miapp.local.key -out miapp.local.crt \
      -subj "/CN=miapp.local" -addext "subjectAltName=DNS:miapp.local"
    
  2. Declararlo en core/data/dynamic_tls.yml:

    tls:
      certificates:
        - certFile: /etc/traefik/certs/miapp.local.crt
          keyFile:  /etc/traefik/certs/miapp.local.key
    
  3. Etiquetar el proyecto sin certresolver:

    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy_network"
      - "traefik.http.routers.miapp.rule=Host(`miapp.local`)"
      - "traefik.http.routers.miapp.entrypoints=websecure"
      - "traefik.http.routers.miapp.tls=true"
      - "traefik.http.services.miapp.loadbalancer.server.port=80"
    
  4. Reiniciar el proxy y resolver el nombre en el cliente:

    cd ~/srv/core && docker compose restart traefik
    echo "192.168.1.50  miapp.local" | sudo tee -a /etc/hosts
    

Instrucciones completas (SAN múltiple, instalación del CA en cada SO) en core/data/certs/README.md.

C) Sin redirección (solo HTTP)

Para entornos internos donde no interese HTTPS:

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=proxy_network"
  - "traefik.http.routers.miapp.rule=Host(`miapp.local`)"
  - "traefik.http.routers.miapp.entrypoints=web"
  - "traefik.http.services.miapp.loadbalancer.server.port=80"

7. Comandos útiles

# Estado y logs del proxy
docker ps | grep core-proxy
docker logs -f core-proxy

# Recargar tras editar dynamic_tls.yml
cd ~/srv/core && docker compose restart traefik

# Proyecto: ciclo de vida
cd ~/srv/projects/miapp
docker compose up -d          # levantar
docker compose up -d --build  # reconstruir imagen
docker compose restart        # reiniciar
docker compose down           # detener (conserva datos)
docker compose logs -f web

# Entrar al contenedor
docker exec -it miapp-web bash

# Ver redes de un contenedor
docker network inspect proxy_network --format '{{.Containers}}'

8. Ejemplo: proyecto Laravel + PostgreSQL

Ejemplo anonimizado con nombres genéricos. Los proyectos reales se publican siempre sin nombres, dominios ni credenciales del servidor real: ver docs/publicacion-anonima.md.

services:
  web:
    build:
      context: ./app
      dockerfile: Dockerfile
    container_name: miapp-web
    restart: unless-stopped
    env_file: [ .env ]
    volumes:
      - ./app/src:/var/www/miapp
      - ./data/storage:/var/www/miapp/storage
    networks: [ default, proxy_network ]
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy_network"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.routers.miapp-http.rule=Host(`miapp.local`)"
      - "traefik.http.routers.miapp-http.entrypoints=web"
      - "traefik.http.routers.miapp-http.middlewares=redirect-to-https"
      - "traefik.http.routers.miapp-https.rule=Host(`miapp.local`)"
      - "traefik.http.routers.miapp-https.entrypoints=websecure"
      - "traefik.http.routers.miapp-https.tls=true"
      - "traefik.http.routers.miapp-https.service=miapp-svc"
      - "traefik.http.services.miapp-svc.loadbalancer.server.port=80"

  pg:
    image: postgres:14
    container_name: miapp-pg
    networks: [ default ]          # ← aislada: no accesible desde el proxy
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes: [ ./data/db:/var/lib/postgresql ]

networks:
  default:
    driver: bridge
  proxy_network:
    external: true

⚠️ Mové las credenciales de environment: al .env del proyecto. Las contraseñas escritas directamente en el docker-compose.yml terminan en el historial de Git.


9. Solución de problemas

Síntoma Causa probable Solución
404 Not Found Contenedor sin traefik.enable=true o fuera de proxy_network docker network inspect proxy_network
El router no aparece en el dashboard Servicio sin traefik.enable o red incorrecta Verificar etiquetas y redes del compose
502 Bad Gateway El servicio interno no escucha en el puerto declarado docker exec -it <cont> sh -c 'netstat -tlnp'
Certificado no se emite (Let's Encrypt) DNS no apunta al server, o 80/443 bloqueados dig +short dominio, revisar firewall
too many certificates / rate limit Se emitted de más en staging/producción Revisar acme.json y esperar la ventana de rate limit
port is already allocated Otro servicio ocupa 80/443 docker ps --filter publish=80
permission denied en Docker socket Usuario fuera del grupo docker sudo usermod -aG docker $USER y re-loguear
El .crt local no se aplica Ruta incorrecta en dynamic_tls.yml Usar la ruta dentro del contenedor (/etc/traefik/certs/...) y reiniciar
no such file or directory: /acme.json Falta el archivo o permisos touch acme.json && chmod 600 acme.json
Cambios en traefik.yml sin efecto El archivo está montado :ro docker compose restart traefik

Diagnóstico rápido:

docker logs -f core-proxy | grep -iE "error|warn"
curl -sI http://miapp.local            # debería devolver 301 → https
curl -sk https://miapp.local -I        # -k ignora el certificado auto-firmado

10. Seguridad

  • Nunca subas: .env, acme.json, *.key, *.crt, contraseñas de bases de datos, dumps SQL ni respaldos. El .gitignore de este repo lo cubre.
  • Los .env por proyecto se crean con chmod 600.
  • docker.sock se monta solo lectura (ro) en Traefik.
  • El dashboard está habilitado. En producción:
    • agregá basicAuth / forwardAuth a las etiquetas del dashboard, o
    • deshabilitalo con api.dashboard: false.
  • Traefik corre con no-new-privileges:true.
  • acme.json debe quedar en 600 o el proxy no managea los certificados.
  • Restringí los puertos publicados de las bases de datos (ports:) a 127.0.0.1 si solo los necesitás desde el host:
    ports: ["127.0.0.1:5432:5432"]
    

11. Publicación anónima

Todo lo que se sube a un repositorio del laboratorio se publica anonimizado. Este repositorio es el ejemplo canónico: no contiene nombres de personas, dominios reales, direcciones IP, nombres de proyectos internos ni credenciales.

Reglas:

  1. Nombres de proyecto → genéricos (miapp, ejemplo, demo).
  2. Dominios → *.local, example.com, midominio.com. Nunca los reales.
  3. Rutas y usuarios → ~/srv, $USER. Nunca /home/<usuario-real>/.
  4. Credenciales → placeholders ${DB_PASSWORD} y .env.example.
  5. IPs → RFC 5737 (192.0.2.0/24, 198.51.100.0/24) o 192.168.0.0/16.
  6. Nada de .env, acme.json, certificados, bases de datos ni respaldos.
  7. Metadatos de commit: revisar git log antes de publicar.

Procedimiento completo y checklist en docs/publicacion-anonima.md. Verificación automática:

./tools/verificar-anonimato.sh

12. Actualizar el proxy

Este repositorio es la capa de infraestructura. Cada proyecto se mantiene en su propio repositorio y se despliega en ~/srv/projects/<proyecto>/ siguiendo los pasos de este README.

cd ~/srv/core && docker compose pull && docker compose up -d   # actualizar proxy

Licencia

GPL-3.0 (GNU General Public License, versión 3) — texto completo en LICENSE.

Software libre bajo los principios del movimiento de Software Libre: podés usar, estudiar, copiar, modificar y redistribuir este software. Si lo distribuís o lo desplegás para terceros, debés publicar también las fuentes de tus modificaciones bajo la misma licencia.

Autoría: Laboratorio de investigacion en Software Libre Gugler - FCyT.

Racional de la licencia

GPL-3.0 es una decisión deliberada, no una escolha por omisión. No debe cambiarse sin revisar este apartado.

El criterio del laboratorio es proteger la libertad sin atar a nadie:

  • No impone condiciones de uso. Quien instala el proxy, lo corre en su servidor o lo modifica para uso interno no debe nada a nadie.
  • La obligación aparece solo al distribuir. Si entregás el código a terceros —incluido un servicio comercial—, debés publicar los cambios bajo la misma licencia. Nadie puede cerrar el proyecto sobre trabajo derivado de acá.
  • Se descartó la AGPL-3.0 (AGPL-3.0-or-later) porque su cláusula 13 extiende ese deber también al uso en red: obligaría a publicar los cambios incluso en un servicio interno no distribuido. El laboratorio lo considera más restrictivo de lo necesario y contrario al objetivo de no imponer condiciones más allá de la distribución.

En resumen: copyleft estricto en la distribución, libertad total en el uso.

Si en el futuro se evaluara adoptar una licencia más permisiva (MIT, BSD, Apache-2.0), debe resolverse en el mismo sentido: consultando a quienes participan y revisando cómo afecta a la distribución de versiones anteriores.