37 Commits

Author SHA1 Message Date
2575026dc7 docs: agregar guía completa de testing para Ansible
Agrega TESTING.md con documentación exhaustiva sobre:
- Molecule (framework oficial de testing)
- Ansible Lint (análisis estático)
- Testinfra (testing de infraestructura con Python)
- YAML Lint (validación de sintaxis)
- Estrategias de CI/CD con GitHub Actions y GitLab CI
- Comandos útiles y mejores prácticas
- Estrategia de testing por fases para Abyaya.la

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:18:09 -03:00
edb3a22ec0 feat: renovación automática de certificados SSL con dns-standalone
- Activada renovación automática cada 4 y 18 de mes
- Usa dns-standalone sin conflictos con dnsmasq (IPs diferentes)
- Documentación completa de arquitectura DNS y validación ACME
2025-11-25 19:31:16 -03:00
6253223fdf fix: stream usa siempre dominio .abyaya.la por seguridad 2025-11-25 14:45:59 -03:00
06a513799e Merge branch 'master' into issue42 2025-11-25 14:35:52 -03:00
7f38023c7d feat: soporte para dominios FQDN con detección automática de zona DNS
Añade soporte completo para usar dominios FQDN externos (ejemplo.com,
kipu.latina.red, etc.) además de subdominios .abyaya.la.

Cambios principales:
- Generación automática de subdominio .abyaya.la como alias
- Detección automática de TLDs compuestos (.com.ar, .co.uk, etc.)
- Actualización DNS multi-zona en Knot
- Procesamiento de múltiples dominios por servicio
- Certificados SSL para todos los dominios + wildcards

La detección de tipo de dominio (FQDN vs subdominio) es completamente
automática basada en el sufijo .abyaya.la.

Ver FQDN_AUTHORITATIVE.md para documentación completa.
2025-11-25 14:30:07 -03:00
f2668d63ef closes #12
@f confirmas porfa si la url del sub-repo esta correcta?
2025-11-20 17:18:05 -03:00
29182e8eaf closes #23 2025-11-20 16:55:40 -03:00
fa59614c2a actualizo mi pubkey 2025-11-20 16:27:01 -03:00
94e71c7516 Revert "quite stream.yml"
This reverts commit b7139145dc.
2025-11-20 12:52:33 -03:00
65090c8c2c alta nodo llavero 2025-11-05 16:16:55 -06:00
b7139145dc quite stream.yml 2025-10-29 22:23:42 -06:00
8e17401f12 kaasavi 2025-10-29 16:43:30 -06:00
206f8adf60 samatuun 2025-10-28 13:34:09 -06:00
1f73abb74f Merge pull request 'feat: rate limits #39' (#65) from issue-39 into master
Reviewed-on: #65

Probaremos en una huerta primero, luego #72
2025-10-24 12:40:14 +00:00
f
2aa6ddcc4d fix: configuración de ssl #69
adaptado de https://ssl-config.mozilla.org/#server=nginx&version=1.29.2&config=intermediate&openssl=3.5.1&guideline=5.7
2025-10-23 17:18:44 -03:00
f
41b30d3a8d fix: headers de seguridad #68 2025-10-23 17:17:19 -03:00
f
4361acfde1 fix: ocultar versión de nginx #67 2025-10-23 17:13:10 -03:00
f
7b5669eae8 Merge branch 'master' into issue-39 2025-10-23 16:36:15 -03:00
f
d2569a7e29 fix: usar los limites recomendados 2025-10-23 15:45:16 -03:00
f
47759f5464 fix: typo 2025-10-23 15:02:27 -03:00
f
3c690dd1f5 feat: rate limits #39 2025-10-23 15:01:58 -03:00
3c30dc976d Merge pull request 'SSH' (#61) from SSH into master
Reviewed-on: #61
2025-10-23 14:36:32 +00:00
b369e9824b Merge branch 'master' into SSH 2025-10-23 11:35:35 -03:00
a75f06474c Merge pull request 'feat: firewall #8' (#63) from issue-8-firewall into master
Reviewed-on: #63

Cómo irán a jugar el firewall con los múltiples puertos distintos necesarios para SSH?
Va a haber que hacer que se abran puertos al vuelo y modificar el rules.v4 tambien
No  hay que reiniciar el firewall cada vez porque se pierden las reglas de docker

8-(
2025-10-23 14:27:47 +00:00
e1b4fb8ba1 hacer de firewall un rol Ansible 2025-10-23 11:10:13 -03:00
f0f0c688c9 muevo upgrades a rol Althost conteniendo instalaciones del Host
elimino unos comentarios
closes #35
2025-10-23 10:36:39 -03:00
f
0d48f04f15 feat: firewall #8 2025-10-01 08:57:52 -03:00
63a21e5145 probando multiples domains, deshabilitando momentaneamente https
debido a que para este hay que arreglar la obtencion de certificados de DNS externos,
es necesario en HTTP (80) pasar la variable  a proxy_ssl_name ya que  viene vacia y esto
genera el error 500 responde tlsv1 unrecognized name (alert 112)
2025-09-20 22:29:45 -03:00
bd07232180 no mezclar con rama fqdn 2025-08-29 12:23:38 -04:00
f5cd3f2e2e sintaxis puertos ssh
problemas con multiples dominios
2025-08-25 19:26:56 -04:00
d0beebfe12 puerto SSH como variable en la matriz, es ports [0]
en el service.yml del proxy recorre los servicios con el atributo 'ports' definido
2025-08-22 21:13:46 -04:00
299f970b95 naca 2025-08-22 20:14:48 -04:00
a581e89f22 eliminando stream de git
que es otra cosa mariposa
2025-08-22 20:05:05 -04:00
23ac2f0b64 proxy SSH en el 222 y git en el 2222 2025-08-22 19:57:58 -04:00
144f9c9d85 proxy SSH Nginx funcional 2025-08-22 19:57:58 -04:00
121c07f766 añadiendo stream {} en nginx.conf 2025-08-22 19:57:58 -04:00
7bc06903eb TODO 2025-08-22 19:26:27 -04:00
27 changed files with 999 additions and 87 deletions

162
DNS_ARCHITECTURE.md Normal file
View File

@ -0,0 +1,162 @@
# Arquitectura DNS y Validación ACME
Este documento explica cómo funciona el sistema DNS distribuido y la validación de certificados SSL para dominios FQDN.
## Componentes DNS
### 1. Servidores Knot (Autoritativos Remotos)
**Ubicación:** `anarres.sutty.nl`, `athshe.sutty.nl`, `gethen.sutty.nl`, `ganam.sutty.nl`
**Función:** DNS autoritativo real para:
- Zona `abyaya.la`
- Zonas FQDN (ej: `latina.red`, `example.com.ar`)
**Actualización:** Via `knsupdate` ejecutado desde el proxy
### 2. Proxy (Hetzner: 5.161.236.18)
**Servicios DNS en diferentes IPs:**
| Servicio | IP | Puerto | Función |
|----------|-------|--------|---------|
| **dnsmasq** | `10.13.12.1` + `127.0.0.1` | 53 | DNS cache/resolver para VPN interna |
| **certbot dns-standalone** | `5.161.236.18` | 53 | DNS temporal para validación ACME |
**No hay conflicto de puertos** porque usan IPs diferentes en la misma máquina.
## Flujo de Validación ACME para Dominios FQDN
### Ejemplo: Certificado para `kipu.latina.red`
#### Paso 1: knsupdate configura delegación en Knot
Cuando se despliega un servicio con FQDN, `knsupdate` crea estos registros en los servidores Knot:
```dns
; En zona latina.red
kipu.latina.red IN A 5.161.236.18
*.kipu.latina.red IN A 5.161.236.18
_acme-challenge.kipu.latina.red IN CNAME abyaya.la.
_acme-challenge.kipu.latina.red IN NS _acme-challenge.abyaya.la.
; En zona abyaya.la
_acme-challenge.abyaya.la IN A 5.161.236.18
_acme-challenge IN NS _acme-challenge.abyaya.la.
```
**Propósito de la delegación:**
- El CNAME redirige la validación a `abyaya.la`
- El NS delega la autoridad a `_acme-challenge.abyaya.la`
- Que apunta al proxy donde certbot corre
#### Paso 2: Certbot obtiene/renueva certificado
```bash
docker run --rm \
-v abyayala_certs_data:/etc/letsencrypt \
--network host \
numericalatina/certbot-wildcard \
certonly --dns-standalone-address=5.161.236.18 --dns-standalone-port=53 \
-d kipu.latina.red -d *.kipu.latina.red
```
**Certbot:**
1. Levanta un servidor DNS temporal en `5.161.236.18:53`
2. Crea el registro TXT con el token de validación
3. Notifica a Let's Encrypt que está listo
#### Paso 3: Let's Encrypt valida
```
Let's Encrypt consulta: _acme-challenge.kipu.latina.red
↓ Consulta a nameservers de latina.red (Knot)
↓ Knot responde con CNAME
Redirigido a: _acme-challenge.abyaya.la
↓ Consulta delegación NS
↓ Knot indica: usar nameserver _acme-challenge.abyaya.la
Consulta directa a: 5.161.236.18:53
↓ certbot dns-standalone responde
↓ Proporciona el token TXT
✓ Validación exitosa
```
## Configuración Requerida en DNS Externo
Para que un dominio FQDN externo (no `.abyaya.la`) pueda delegar la validación ACME al proxy, es necesario agregar estos registros en el DNS del dominio:
```dns
_acme-challenge IN CNAME abyaya.la.
_acme-challenge IN NS _acme-challenge.abyaya.la.
```
**Ejemplo para `kipu.latina.red`:**
```dns
; En el DNS de latina.red
_acme-challenge.kipu IN CNAME abyaya.la.
_acme-challenge.kipu IN NS _acme-challenge.abyaya.la.
```
Esto se hace **una sola vez por dominio** y permite que todos los subdominios deleguen automáticamente.
## Renovación Automática
La renovación de certificados funciona automáticamente porque:
1.**Delegación persistente**: Los registros `_acme-challenge` ya están en Knot
2.**Cron configurado**: Se ejecuta los días 4 y 18 de cada mes
3.**Sin conflictos**: dnsmasq y certbot usan IPs diferentes
4.**Mismo método**: `certbot renew` usa dns-standalone automáticamente
```yaml
# Configurado en roles/certbot/tasks/main.yml
- name: automatic letsencrypt certs renewal
cron:
name: certificate renewal
day: 4,18
hour: 0
minute: 0
job: "docker run --rm -v abyayala_certs_data:/etc/letsencrypt --network host numericalatina/certbot-wildcard renew --dns-standalone-address=5.161.236.18 --dns-standalone-port=53 >> /var/log/renewal.log 2>&1"
```
## Ventajas de esta Arquitectura
1. **Centralizada**: Un solo proxy maneja validación ACME para todos los dominios
2. **Segura**: No requiere credenciales API de proveedores DNS externos
3. **Flexible**: Soporta dominios `.abyaya.la` y FQDN externos
4. **Automatizada**: Renovación sin intervención manual
5. **Escalable**: Agregar nuevos dominios solo requiere actualizar `abyayala.yml`
## Troubleshooting
### Verificar delegación DNS
```bash
# Verificar CNAME
dig _acme-challenge.kipu.latina.red CNAME
# Verificar NS delegation
dig _acme-challenge.kipu.latina.red NS
# Probar resolución completa
dig @5.161.236.18 _acme-challenge.kipu.latina.red TXT
```
### Ver logs de renovación
```bash
tail -f /var/log/renewal.log
```
### Probar renovación manualmente
```bash
docker run --rm \
-v abyayala_certs_data:/etc/letsencrypt \
--network host \
numericalatina/certbot-wildcard \
renew --dns-standalone-address=5.161.236.18 --dns-standalone-port=53 --dry-run
```

133
FQDN_AUTHORITATIVE.md Normal file
View File

@ -0,0 +1,133 @@
# Soporte para Dominios FQDN Autoritativos
Esta feature añade soporte para usar dominios FQDN externos (ejemplo.com, kipu.latina.red, etc.) además de subdominios .abyaya.la.
## Cambios Implementados
### 1. Generación Automática de Subdominio Default
Cuando se define un dominio FQDN, el sistema genera automáticamente un subdominio `.abyaya.la` basado en el `service_name` que funciona como alias.
**Ejemplo:**
```yaml
- service_name: kipu
domains:
- kipu.latina.red
nodo: kipu.comun
force_https: yes
```
El sistema automáticamente añade `kipu.abyaya.la` a la lista de dominios, por lo que ambos dominios funcionarán y redirigirán al primero de la lista.
### 2. Soporte para TLDs Compuestos
El sistema detecta automáticamente TLDs compuestos como `.com.ar`, `.co.uk`, `.com.br`, etc., y extrae correctamente la zona DNS.
**TLDs soportados:**
- com.ar, gov.ar, org.ar, gob.ar, net.ar, mil.ar, edu.ar
- com.mx
- co.uk
- com.br
- co.nz, net.nz, org.nz
Para añadir más TLDs, editar `roles/knsupdate/vars/main.yml`.
### 3. Actualización DNS Multi-Zona
El sistema ahora actualiza correctamente el DNS en Knot para cada dominio según su tipo:
- **Subdominios .abyaya.la**: Se actualizan en la zona `abyaya.la.`
- **FQDN autoritativos**: Se actualizan en su zona correspondiente (ej: `latina.red.`, `example.com.ar.`)
La detección es **completamente automática** basada en el sufijo del dominio.
## Uso
### Caso Básico: Solo FQDN
```yaml
- service_name: ejemplo
domains:
- ejemplo.latina.red
nodo: ejemplo.comun
force_https: yes
```
**Resultado:**
- `ejemplo.latina.red` → Dominio principal (detectado como FQDN)
- `ejemplo.abyaya.la` → Generado automáticamente como alias
- Ambos tienen certificados SSL wildcard
- Ambos redirigen al primero (ejemplo.latina.red)
### Caso Avanzado: Múltiples Dominios
```yaml
- service_name: miapp
domains:
- miapp.com.ar
- miapp.latina.red
- miapp.abyaya.la
nodo: miapp.comun
force_https: yes
```
**Resultado:**
- Los tres dominios funcionan
- Todos redirigen al primero (miapp.com.ar)
- Certificados SSL para cada dominio + wildcards
- DNS actualizado en zonas: `com.ar.`, `latina.red.`, `abyaya.la.`
### Subdominios de FQDN
```yaml
- service_name: api
domains:
- api.ejemplo.com.ar
nodo: api.comun
force_https: yes
```
**Resultado:**
- `api.ejemplo.com.ar` → Dominio principal (hostname: api, zona: com.ar.)
- `api.abyaya.la` → Generado automáticamente
## Archivos Modificados
1. **roles/proxy/tasks/main.yml**: Añade dominio default .abyaya.la automáticamente
2. **roles/knsupdate/vars/main.yml**: Lista de TLDs compuestos
3. **roles/knsupdate/tasks/update.yml**: Procesa múltiples dominios
4. **roles/knsupdate/tasks/update_domain.yml**: Nuevo archivo que detecta tipo de dominio
5. **roles/knsupdate/tasks/templates/commands.j2**: Usa zona y hostname dinámicos
## Comportamiento de Certificados SSL
Certbot obtiene certificados para **todos** los dominios listados más sus wildcards:
```bash
certbot certonly -d ejemplo.com.ar -d *.ejemplo.com.ar -d ejemplo.abyaya.la -d *.ejemplo.abyaya.la
```
Usa el método `dns-standalone` que requiere que el proxy controle el DNS autoritativo. Esto funciona porque knsupdate actualiza Knot con todos los dominios.
## Migración desde Configuración Anterior
La configuración anterior sigue funcionando sin cambios:
```yaml
- service_name: viejo
domains:
- viejo.abyaya.la
nodo: viejo.comun
force_https: yes
```
Todo funciona exactamente igual para subdominios .abyaya.la existentes.
## Notas Técnicas
- La detección de tipo de dominio es **completamente automática** basada en el sufijo `.abyaya.la`
- Los subdominios .abyaya.la siempre se generan automáticamente si no están presentes
- La zona DNS se detecta automáticamente considerando TLDs simples y compuestos
- Todos los dominios apuntan al mismo nodo en la VPN
- El primer dominio en la lista es considerado el principal para certificados SSL
- No se requiere ningún flag especial en la configuración

401
TESTING.md Normal file
View File

@ -0,0 +1,401 @@
# Testing en Abyaya.la
Esta guía documenta las estrategias y herramientas de testing para infraestructura como código (IaC) basada en Ansible.
## Índice
1. [Testing de Ansible](#testing-de-ansible)
2. [Validación y Sintaxis](#validación-y-sintaxis)
3. [Testing de Infraestructura](#testing-de-infraestructura)
4. [CI/CD para Ansible](#cicd-para-ansible)
5. [Recursos Adicionales](#recursos-adicionales)
---
## Testing de Ansible
### Molecule (Framework oficial - RECOMENDADO)
Molecule es el framework oficial para testing de roles de Ansible. Permite ejecutar tests unitarios, de integración y verificar idempotencia.
**Documentación Oficial:**
- Documentación principal: https://ansible.readthedocs.io/projects/molecule/
- Getting Started: https://ansible.readthedocs.io/projects/molecule/getting-started/
- Guía de escenarios: https://ansible.readthedocs.io/projects/molecule/configuration/
**Características:**
- Tests unitarios de roles individuales
- Tests de integración
- Tests de idempotencia (verificar que ejecutar dos veces da el mismo resultado)
- Soporte para Docker/Podman para simular entornos
**Instalación:**
```bash
pip install molecule molecule-docker
```
**Uso básico:**
```bash
cd roles/rap
molecule init scenario
molecule test
```
---
### Ansible Lint
Herramienta de análisis estático que verifica mejores prácticas y patrones comunes en playbooks y roles de Ansible.
**Documentación:**
- Documentación principal: https://ansible-lint.readthedocs.io/
- Catálogo de reglas: https://ansible-lint.readthedocs.io/rules/
**Instalación:**
```bash
pip install ansible-lint
```
**Uso:**
```bash
# Verificar un playbook específico
ansible-lint deploy.yml
# Verificar todos los archivos del proyecto
ansible-lint
# Verificar un rol específico
ansible-lint roles/proxy/
```
---
### Testinfra
Framework de testing de infraestructura escrito en Python. Permite verificar el estado real de los servidores después del despliegue.
**Documentación:**
- Documentación principal: https://testinfra.readthedocs.io/
- Ejemplos: https://testinfra.readthedocs.io/en/latest/examples.html
**Instalación:**
```bash
pip install testinfra
```
**Ejemplo de test:**
```python
# tests/test_vpn.py
def test_tinc_is_running(host):
"""Verificar que el servicio Tinc está corriendo"""
tinc = host.service("tinc@comun")
assert tinc.is_running
assert tinc.is_enabled
def test_vpn_interface_exists(host):
"""Verificar que la interfaz VPN existe"""
assert host.interface("comun").exists
def test_vpn_ip_assigned(host):
"""Verificar que la IP VPN está asignada"""
comun = host.interface("comun")
assert comun.addresses[0].startswith("10.13.12.")
def test_nginx_is_running(host):
"""Verificar que Nginx está corriendo"""
nginx = host.service("nginx")
assert nginx.is_running
def test_port_443_listening(host):
"""Verificar que el puerto 443 está escuchando"""
assert host.socket("tcp://0.0.0.0:443").is_listening
```
**Ejecución:**
```bash
# Ejecutar tests contra un host
testinfra --hosts=ssh://root@hetzner tests/test_vpn.py
# Ejecutar con Ansible inventory
testinfra --ansible-inventory=hosts.production tests/
```
---
## Validación y Sintaxis
### YAML Lint
Validador de sintaxis YAML con verificación de estilos y buenas prácticas.
**Documentación:**
- Repositorio: https://github.com/adrienverge/yamllint
- Configuración: https://yamllint.readthedocs.io/en/stable/configuration.html
**Instalación:**
```bash
pip install yamllint
```
**Uso:**
```bash
# Validar archivos específicos
yamllint deploy.yml abyayala.yml
# Validar todos los YAML del proyecto
yamllint *.yml roles/*/tasks/*.yml roles/*/templates/*.yml
```
**Configuración (.yamllint):**
```yaml
extends: default
rules:
line-length:
max: 120
indentation:
spaces: 2
```
---
### Ansible Syntax Check (Built-in)
Verificación de sintaxis integrada en Ansible.
**Documentación:**
- Testing strategies: https://docs.ansible.com/ansible/latest/reference_appendices/test_strategies.html
- Syntax check: https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html#cmdoption-ansible-playbook-syntax-check
**Uso:**
```bash
# Verificar sintaxis de un playbook
ansible-playbook deploy.yml --syntax-check
# Dry-run (simular sin ejecutar)
ansible-playbook deploy.yml --check
# Dry-run con diff de cambios
ansible-playbook deploy.yml --check --diff
```
---
## Testing de Infraestructura
### InSpec (Chef)
Framework de testing de infraestructura enterprise. Alternativa más robusta a Testinfra.
**Documentación:**
- Documentación principal: https://docs.chef.io/inspec/
**Características:**
- Lenguaje DSL específico para infraestructura
- Compliance as Code
- Perfiles de seguridad predefinidos
---
## CI/CD para Ansible
### GitHub Actions
**Documentación:**
- GitHub Actions para Ansible: https://github.com/marketplace/actions/run-ansible-playbook
**Ejemplo de workflow:**
```yaml
# .github/workflows/test.yml
name: Test Ansible Playbooks
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install ansible ansible-lint yamllint molecule molecule-docker
- name: Run YAML Lint
run: yamllint .
- name: Run Ansible Lint
run: ansible-lint
- name: Syntax check
run: ansible-playbook deploy.yml --syntax-check
- name: Run Molecule tests
run: |
cd roles/rap
molecule test
```
---
### GitLab CI
**Documentación:**
- GitLab CI para Ansible: https://docs.gitlab.com/ee/ci/examples/ansible/
**Ejemplo de pipeline:**
```yaml
# .gitlab-ci.yml
stages:
- lint
- test
lint:yaml:
stage: lint
image: python:3.9
script:
- pip install yamllint
- yamllint .
lint:ansible:
stage: lint
image: python:3.9
script:
- pip install ansible-lint
- ansible-lint
test:syntax:
stage: test
image: python:3.9
script:
- pip install ansible
- ansible-playbook deploy.yml --syntax-check
test:molecule:
stage: test
image: python:3.9
services:
- docker:dind
script:
- pip install molecule molecule-docker
- cd roles/rap && molecule test
```
---
## Recursos Adicionales
### Guías y Tutoriales
- **Testing Ansible Roles with Molecule**: https://www.digitalocean.com/community/tutorials/how-to-test-ansible-roles-with-molecule-on-ubuntu-20-04
- **Developing and Testing Ansible Roles with Molecule and Podman** (Serie de artículos): https://www.ansible.com/blog/developing-and-testing-ansible-roles-with-molecule-and-podman-part-1
### Best Practices
- **Ansible Best Practices**: https://docs.ansible.com/ansible/latest/tips_tricks/ansible_tips_tricks.html
- **Ansible Style Guide**: https://docs.ansible.com/ansible/latest/dev_guide/style_guide/
### Libros
- **Ansible for DevOps** (Jeff Geerling): https://www.ansiblefordevops.com/
- Capítulos relevantes sobre testing y CI/CD
---
## Estrategia de Testing Recomendada para Abyaya.la
### Fase 1: Inmediato (Quick Wins)
1. **Validación de sintaxis**
```bash
ansible-playbook deploy.yml --syntax-check
```
2. **YAML Lint**
```bash
pip install yamllint
yamllint .
```
3. **Ansible Lint**
```bash
pip install ansible-lint
ansible-lint
```
### Fase 2: Corto Plazo (1-2 semanas)
1. **Molecule para roles críticos**
- Empezar con `roles/rap` (VPN es crítico)
- Continuar con `roles/proxy` (punto de entrada público)
- Agregar `roles/certbot` (seguridad SSL)
2. **Pre-commit hooks**
```bash
pip install pre-commit
# Crear .pre-commit-config.yaml
pre-commit install
```
### Fase 3: Largo Plazo (1-2 meses)
1. **Testinfra para verificación post-despliegue**
- Tests de conectividad VPN
- Tests de disponibilidad de servicios
- Tests de configuración de firewall
- Tests de certificados SSL
2. **CI/CD Pipeline**
- GitHub Actions o GitLab CI
- Tests automáticos en cada push
- Tests de integración en staging
3. **Tests de integración end-to-end**
- Despliegue completo en entorno de prueba
- Verificación de flujo completo: VPN → Proxy → Servicio
---
## Comandos Útiles de Testing
```bash
# Testing rápido de sintaxis
ansible-playbook deploy.yml --syntax-check
# Dry-run (ver qué cambiaría sin aplicar)
ansible-playbook deploy.yml --check --diff -e "host=hetzner alt=abyayala"
# Verificar sintaxis YAML
yamllint *.yml roles/
# Análisis estático con ansible-lint
ansible-lint deploy.yml
# Test de un rol específico con Molecule
cd roles/rap && molecule test
# Verificación post-despliegue con Testinfra
testinfra --hosts=ssh://root@hetzner tests/
# Ver qué tareas se ejecutarían
ansible-playbook deploy.yml --list-tasks -e "host=hetzner alt=abyayala"
# Ejecutar solo con tags específicos en check mode
ansible-playbook deploy.yml --check --tags=vpn -e "host=hetzner alt=abyayala"
```
---
## Contribuir
Al agregar nuevos roles o modificar existentes:
1. Ejecutar ansible-lint antes de commit
2. Verificar sintaxis con --syntax-check
3. Si es un rol crítico, agregar tests de Molecule
4. Documentar casos de prueba en este archivo

View File

@ -14,8 +14,8 @@ matrix:
roles:
- rap
nodos:
- llavero
- marmite
- ka
- nodochasqui
- yanapak
- comun01
@ -57,6 +57,8 @@ matrix:
- kipu
- resistencia
- carabobolibre
- samatuun
- kaasavi
- service_name: respaldos
domains:
@ -70,12 +72,6 @@ matrix:
nodo: marmite.comun
force_https: yes
- service_name: ka
domains:
- 2012k.abyaya.la
nodo: ka.comun
force_https: yes
- service_name: yanapak
domains:
- yanapak.abyaya.la
@ -93,16 +89,8 @@ matrix:
- pilmaiken.abyaya.la
nodo: pilmaiken.comun
force_https: yes
dns_extras:
- 'del pilmaiken mx'
- 'del pilmaiken txt'
- 'del pilmaiken spf'
- 'add pilmaiken mx 10 correspondencia.latina.red.'
- 'add pilmaiken txt "v=spf1 mx a:correspondencia.latina.red -all"'
- 'add pilmaiken spf "v=spf1 mx a:correspondencia.latina.red -all"'
- 'add dkim._domainkey.pilmaiken txt "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQ6JwPaawDzMXuscSgDpvipRFLGXSqgmvvI6jk18lcg0kK2lfxsvXGJ/6U7oCtKa35IEVzdigxD0o7DzklKxAsNIVbcExPJkFWzQuKuP6ATBESo7YUn7Z5qjfxBiNPS0FJp8XpbpUzN+zg/NTgmkggnwwC0tKgcEQ6HnI9AOa1LQIDAQAB"'
- 'add _dmarc.pilmaiken txt "v=DMARC1; p=reject; rua=mailto:postmaster@correspondencia.latina.red; ruf=mailto:postmaster@correspondencia.latina.red; adkim=s; aspf=s"'
ports:
- 222
- service_name: fundeps
domains:
@ -216,9 +204,11 @@ matrix:
- service_name: kipu
domains:
- kipu.abyaya.la
- kipu.latina.red
nodo: kipu.comun
force_https: yes
ssl: no
ports:
- 223
- service_name: carabobolibre
domains:
@ -226,3 +216,14 @@ matrix:
nodo: carabobolibre.comun
force_https: yes
- service_name: samatuun
domains:
- samatuun.abyaya.la
nodo: samatuun.comun
force_https: yes
- service_name: kaasavi
domains:
- kaasavi.abyaya.la
nodo: kaasavi.comun
force_https: yes

View File

@ -3,15 +3,14 @@
---
- hosts: "{{ host }}"
tasks:
- name: "unnattended upgrades"
apt:
name: "unnattended-upgrades"
state: "present"
- name: import matrix
local_action: "include_vars dir=./ files_matching={{ alt }}.yml"
- include_role: name=althost
- include_role: name=firewall
tags: firewall
- include_role: name=proxy
tags: proxy

View File

@ -1,6 +1,6 @@
---
ssh_users:
- name: numerica
- name: berto
comment: "Roberto Soto"
sudo: yes
servers_allow:

View File

@ -1,7 +1,11 @@
# DOCKER CE this is specific for Debian
# https://docs.docker.com/install/linux/docker-ce/debian/
- block:
- name: "unattended upgrades"
apt:
name: "unattended-upgrades"
state: "present"
- name: required packages
apt:
name: ['apt-transport-https', 'ca-certificates', 'curl', 'gnupg2', 'software-properties-common', 'python3-pip']
@ -56,18 +60,6 @@
state: present
break_system_packages: true
# # https://stackoverflow.com/questions/77490435/attributeerror-cython-sources
# - name: fix python package Cython version
# pip:
# name: Cython
# state: present
# version: <3.0.0
# break_system_packages: true
# changed_when: false
# - name: fix python package PyYAML version
# shell: pip install "pyyaml==5.4.1" --no-build-isolation --break-system-packages
- name: ensure python package docker-compose is present
pip:
name: docker-compose
@ -76,7 +68,6 @@
tags: installation
# DOCKER COMPOSITION IN MASTER
- block:
- name: make sure compose path exists

View File

@ -30,14 +30,13 @@
env: yes
value: /bin/bash
# TODO
# - name: automatic letsencrypt certs renewal
# cron:
# name: certificate renewal
# day: 4,18
# hour: 0
# minute: 0
# job: "docker run --rm -v {{ althost }}_certs_data:/etc/letsencrypt -v {{ althost }}_certs_www:/var/www/letsencrypt certbot/certbot renew >> /var/log/renewal.log 2>&1"
- name: automatic letsencrypt certs renewal
cron:
name: certificate renewal
day: 4,18
hour: 0
minute: 0
job: "docker run --rm -v {{ althost }}_certs_data:/etc/letsencrypt --network host {{ CERTBOT_image }} renew --dns-standalone-address={{ host_ip }} --dns-standalone-port=53 >> /var/log/renewal.log 2>&1"
- name: proxy update, after certs renewal
cron:

View File

@ -0,0 +1,14 @@
- name: "Paquetes"
apt:
name:
- "iptables-persistent"
- "ipset-persistent"
state: "present"
- name: "Rules"
with_items:
- "rules.v4"
- "rules.v6"
template:
src: "{{ item }}.j2"
dest: "/etc/iptables/{{ item }}"

View File

@ -0,0 +1,18 @@
*filter
:INPUT DROP [106:5591]
:FORWARD DROP [28:1715]
:OUTPUT ACCEPT [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i comun -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p udp -m udp --dport 655 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 655 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 3000 -j ACCEPT
COMMIT

View File

@ -0,0 +1,5 @@
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT

View File

@ -1,14 +1,19 @@
{% for dns_server in dns_servers %}
server {{ dns_server }}
zone abyaya.la.
origin abyaya.la.
zone {{ zone }}
origin {{ zone }}
ttl 60
del {{ vho }} a
del {{ vho }} ns
add {{ vho }} a {{ host_ip }}
add *.{{ vho }} a {{ host_ip }}
add _acme-challenge.{{ vho }} a {{ host_ip }}
add _acme-challenge.{{ vho }} ns _acme-challenge
del {{ hostname }} a
del {{ hostname }} ns
add {{ hostname }} a {{ host_ip }}
{% if is_abyayala_subdomain %}
add *.{{ hostname }} a {{ host_ip }}
{% else %}
add {{ domain }} a {{ host_ip }}
add *.{{ domain }} a {{ host_ip }}
{% endif %}
add _acme-challenge.{{ hostname }} a {{ host_ip }}
add _acme-challenge.{{ hostname }} ns _acme-challenge
{% if vhost.dns_extras is defined %}
{% for dns_extra in vhost.dns_extras %}
{{ dns_extra }}

View File

@ -1,7 +1,5 @@
- set_fact:
vho: "{{ vhost.domains[0] | regex_replace('([a-z0-9]+)\\.abyaya\\.la', '\\1')}}"
- name: knsupdate
shell: knsupdate
args:
stdin: "{{ lookup('template', 'templates/commands.j2') }}"
- name: process each domain in the list
include_tasks: update_domain.yml
with_items: "{{ vhost.domains }}"
loop_control:
loop_var: domain

View File

@ -0,0 +1,36 @@
- set_fact:
is_abyayala_subdomain: "{{ domain.endswith('.abyaya.la') }}"
- name: extract zone and hostname for abyaya.la subdomains
set_fact:
zone: "abyaya.la."
hostname: "{{ domain | regex_replace('([a-z0-9-]+)\\.abyaya\\.la', '\\1') }}"
when: is_abyayala_subdomain
- name: split domain into parts
set_fact:
domain_parts: "{{ domain.split('.') }}"
when: not is_abyayala_subdomain
- name: detect if domain uses compound TLD
set_fact:
domain_suffix_2: "{{ domain_parts[-2:] | join('.') }}"
uses_compound_tld: "{{ domain_parts[-2:] | join('.') in compound_tlds }}"
when: not is_abyayala_subdomain
- name: extract zone and hostname for FQDN with compound TLD
set_fact:
zone: "{{ domain_parts[-3:] | join('.') }}."
hostname: "{{ domain_parts[:-3] | join('.') if domain_parts | length > 3 else '@' }}"
when: not is_abyayala_subdomain and uses_compound_tld
- name: extract zone and hostname for FQDN with simple TLD
set_fact:
zone: "{{ domain_parts[-2:] | join('.') }}."
hostname: "{{ domain_parts[:-2] | join('.') if domain_parts | length > 2 else '@' }}"
when: not is_abyayala_subdomain and not uses_compound_tld
- name: knsupdate for this domain
shell: knsupdate
args:
stdin: "{{ lookup('template', 'templates/commands.j2') }}"

View File

@ -3,3 +3,18 @@ dns_servers:
- "athshe.sutty.nl"
- "gethen.sutty.nl"
- "ganam.sutty.nl"
compound_tlds:
- com.ar
- com.mx
- co.uk
- com.br
- gov.ar
- org.ar
- gob.ar
- net.ar
- mil.ar
- edu.ar
- co.nz
- net.nz
- org.nz

View File

@ -4,3 +4,8 @@ proxy_redirect off;
proxy_connect_timeout 3m;
proxy_send_timeout 3m;
proxy_read_timeout 3m;
limit_conn connection_limit 50;
limit_req zone=request_limit nodelay burst=20;
add_header Retry-After $retry_after always;

View File

@ -0,0 +1,4 @@
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";

View File

@ -10,17 +10,13 @@
include_role: name=certbot
tags: certbot
- include_tasks: ../../althost/tasks/compose.yml
vars: # forcing since this role is included statically
service_name: proxy
- name: configuration path
file: path={{ conf_path }} state=directory
# TODO leaving unused vhosts bugs proxy
- name: clean vhosts_path
file: path={{ vhosts_path }} state=absent
when: clean_vhosts is defined
- name: configuration paths
file: path={{ comun }} state=directory
with_items:
- "{{ stream_path }}"
- "{{ conf_path }}"
loop_control:
loop_var: comun
- name: virtual hosts path
file: path={{ vhosts_path }} state=directory
@ -35,6 +31,7 @@
with_items:
- common.conf
- common_ssl.conf
- nginx.conf
loop_control:
loop_var: common
@ -48,6 +45,21 @@
loop_control:
loop_var: domino
- name: add default abyaya.la subdomain if not present
set_fact:
matrix_loop_with_defaults: "{{ matrix_loop_with_defaults | default([]) | union([ item_with_default ]) }}"
vars:
has_abyayala_domain: "{{ item.domains | select('match', '.*\\.abyaya\\.la$') | list | length > 0 }}"
default_domain: "{{ item.service_name }}.abyaya.la"
domains_with_default: "{{ item.domains + [default_domain] if not has_abyayala_domain else item.domains }}"
item_with_default: "{{ item | combine({'domains': domains_with_default}) }}"
with_items: "{{ matrix_loop | default([]) }}"
- name: update matrix_loop with defaults
set_fact:
matrix_loop: "{{ matrix_loop_with_defaults }}"
when: matrix_loop_with_defaults is defined
- name: certificates loop
include_tasks: ../../certbot/tasks/certbot.yml
with_items: "{{ matrix_loop | default([]) }}"
@ -62,3 +74,22 @@
loop_control:
loop_var: vhost
when: (service is undefined) or (service is defined and service == vhost.service_name)
- name: streams loop
include_tasks: stream.yml
with_items: "{{ matrix_loop }}"
loop_control:
loop_var: vhost
when: (service is undefined) or (service is defined and service == vhost.service_name)
- name: slice matrix with those having ports defined
set_fact:
matrix_ports: "{{ matrix_ports | default([]) | union(ma.ports) }}"
with_items: "{{ matrix }}"
when: (ma.ports is defined)
loop_control:
loop_var: ma
- include_tasks: ../../althost/tasks/compose.yml
vars: # forcing since this role is included statically
service_name: proxy

View File

@ -0,0 +1,10 @@
- set_fact:
vhost_dest: "{{ stream_path }}/{{ vhost.domains[0] }}.conf"
- name: default stream for ssh
template:
src: "{{ default_stream }}"
dest: "{{ vhost_dest }}"
when: vhost.ports is defined
notify:
- reload proxy

View File

@ -6,6 +6,14 @@
# openssl dhparam -outform pem -out dhparam2048.pem 2048
ssl_dhparam /etc/nginx/conf/dhparam2048.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

View File

@ -15,7 +15,7 @@
client_max_body_size 1G;
proxy_ssl_verify off;
proxy_ssl_server_name on;
proxy_ssl_name $ssl_server_name;
proxy_ssl_name $host;
proxy_pass https://$comun_{{ vhost.nodo | replace(".", "") }};

View File

@ -0,0 +1,56 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server_tokens off;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
# Limitar cada dirección IP a 50 peticiones por segundo por IP y
# servidor.
limit_req_zone $server_name$binary_remote_addr zone=request_limit:10m rate=10r/s;
limit_req_status 429;
# Limita la cantidad de conexiones concurrentes por IP. Según la
# documentación de Nginx, cada request en HTTP/2 se cuenta como una
# conexión separada aunque sean la misma.
limit_conn_zone $binary_remote_addr zone=connection_limit:10m;
limit_conn_status 429;
# Informar a los navegadores que cuando reciban un error de muchas
# conexiones, esperen un segundo antes de reintentar.
map $status $retry_after {
default '';
429 '1';
}
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
stream {
include /etc/nginx/stream.d/*.conf;
}

View File

@ -8,11 +8,14 @@
ports:
- "80:80"
- "443:443"
{% for port in matrix_ports %}
- "{{ port }}:{{ port }}"
{% endfor %}
networks:
- proxy
volumes:
- "{{ vhosts_path }}:/etc/nginx/conf.d/"
- "{{ conf_path }}:/etc/nginx/conf/"
- "certs_data:{{ nginx_certs_path }}:ro"
- "{{ conf_path }}/nginx.conf:/etc/nginx/nginx.conf:ro"
- "{{ stream_path }}:/etc/nginx/stream.d/"

View File

@ -0,0 +1,11 @@
upstream ssh_{{ vhost.nodo | replace(".", "") }} {
server {{ vhost.nodo }}:22;
}
server {
listen {{ vhost.ports[0] }};
server_name {{ vhost.service_name }}.abyaya.la;
proxy_pass ssh_{{ vhost.nodo | replace(".", "") }};
}

View File

@ -3,12 +3,14 @@ domains_default_force_https: no
# nginx
vhosts_path: "{{ compose_path }}/proxy/vhosts"
stream_path: "{{ compose_path }}/proxy/stream"
conf_path: "{{ compose_path }}/proxy/conf"
nginx_certs_path: /etc/nginx/certs
# defaults
needs_vhost: no
default_vhost: roles/proxy/templates/vhost.conf
default_stream: roles/proxy/templates/stream.conf
# certbot
webmaster_email: webmaster@numerica.cl

View File

@ -4,16 +4,20 @@
state: present
tags: installation
# TODO: ERROR! conflicting action statements: synchronize, creates
# - name: copiar el codigo fuente
# synchronize:
# src: ../roles/rap/code/rap/
# dest: "{{ rap_path }}"
# perms: true
# rsync_opts:
# - "--exclude=.git"
# tags: rap
# creates: "{{ rap_path }}"
- name: Verificar si ya existe el codigo fuente
stat:
path: "{{ rap_path }}"
register: rap_status
- name: copiar el codigo fuente, si no existe
synchronize:
src: ../roles/rap/code/rap/
dest: "{{ rap_path }}"
perms: true
rsync_opts:
- "--exclude=.git"
tags: rap
when: not rap_status.stat.exists
- name: agregar nodos a la VPN
shell:

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCrhho0oQcAWX6ndpwxkNykJTohUg6vvEplqBa5pENU1gD61eahP4BU4weGrOWY28fBy3BhXOYFITWOxmdv7V7T9fG8WKXqT8MHkC9zaLzAZJ11tHq8OeDo0QGkBOoetf0dv0e3+FNijA9QKhqiwz+v0t5Ev0bSuSl28zHI3Tr0vF98SZulIR9CxDTTPA+Hel2Tl+LcVDZb80/tG9oAFBctzMK7AWB80X/DgXVfY0qgP97809JPX+Cqo/Q+LK55HxeDnfqDsA3m3KXQPODZm6ATEfmcx3MBYqJ8m0rdMKBwR16xDvAyB/LezHVCgmQvmzTRgzTwwwitOX83F5oIEArTK64s1dm0VLzj+Pw5Tetkde4YPHfajffXUbUxGTgD6M3NCvCLHedDMQDQT3LNOfFJGngGcwWJdStmMCWR6dd81epzPZiWHEZ093UWCPtE8kvrL4fRfIc/qSKWNCIyAoagKw4SwwyOfg0ONBJpjOfI7+vBKJVCZSDZQYyU/+Bo60k= berto@concon