- LICENSE: texto integro de la GPL-3.0 - docs/publicacion-anonima.md: politica de publicacion sin datos reales (nombres, rutas con usuario, dominios, IPs, credenciales) y checklist - tools/verificar-anonimato.sh: chequeo automatico de PII y archivos sensibles trackeados, con salida por codigo (0 limpio / 1 con hallazgos) - README: seccion 'Publicacion anonima' y licencia GPL-3.0 - .env.global: ruta base sin nombre de usuario - README ejemplo: nombres de proyecto genericos (miapp) en lugar de reales
447 lines
14 KiB
Markdown
447 lines
14 KiB
Markdown
# TrafficServerLocal
|
|
|
|
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
|
|
|
|
```text
|
|
~/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):
|
|
|
|
```text
|
|
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`:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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](#10-seguridad)) |
|
|
|
|
### Puertos expuestos por el proxy
|
|
|
|
```yaml
|
|
ports:
|
|
- "80:80" # entrypoint: web → HTTP
|
|
- "443:443" # entrypoint: websecure → HTTPS
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Crear un proyecto base
|
|
|
|
```bash
|
|
cd ~/srv
|
|
./init_project.sh api-ventas api.midominio.com 80
|
|
```
|
|
|
|
Genera:
|
|
|
|
```text
|
|
~/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
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
```ini
|
|
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`](docs/http-https.md)**.
|
|
|
|
### A) Dominio público + Let's Encrypt
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```yaml
|
|
tls:
|
|
certificates:
|
|
- certFile: /etc/traefik/certs/miapp.local.crt
|
|
keyFile: /etc/traefik/certs/miapp.local.key
|
|
```
|
|
|
|
3. Etiquetar el proyecto **sin** certresolver:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```bash
|
|
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`](core/data/certs/README.md)**.
|
|
|
|
### C) Sin redirección (solo HTTP)
|
|
|
|
Para entornos internos donde no interese HTTPS:
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
# 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`](docs/publicacion-anonima.md).
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
```yaml
|
|
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`](docs/publicacion-anonima.md)**.
|
|
Verificación automática:
|
|
|
|
```bash
|
|
./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.
|
|
|
|
```bash
|
|
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`](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 Investigación en Software Libre.
|