53 Commits

Author SHA1 Message Date
ea1e264fd4 Merge pull request 'Certbot standalone auto-renewal' (#77) from feat-cert-auto-renewal into issue42
Reviewed-on: #77
2025-12-16 20:48:59 +00:00
a6c1bcaeff Merge branch 'master' into feat-cert-auto-renewal 2025-12-16 12:02:49 -03:00
090c397e24 Merge pull request 'Soporte para FQDN' (#76) from issue42 into master
Reviewed-on: #76
2025-12-16 15:00:07 +00:00
89866df0bf Soporte FQDN
closes #76
2025-12-16 00:45:50 -03:00
216c81df39 feat: use knsupdate only for abyayala subdomains, display DNS config for external domains
- knsupdate now only executes when is_abyayala_subdomain is true
- For external domains, display DNS configuration instructions in console
- Created dns_info.j2 template to show required DNS records for manual configuration
- External domains now show: A records, wildcard A records, and ACME challenge NS delegation

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 00:31:26 -03:00
a8b3a63807 se agrega expand en caso de certbot y se arregla condicion de Knsupdate (que sera solo para abyayala_subdomains).
en la amtriz testeando en prod
2025-12-16 00:30:09 -03:00
02b135f486 Merge branch 'master' into issue42 2025-12-15 19:55:15 -03:00
7e12b7b20a poner archivos de webroot 2025-12-15 19:49:25 -03:00
0a99c7f2d1 feat: implement webroot method for root domain SSL certificates
Replace dns-standalone with HTTP-01 webroot validation for root domains
(when root: yes flag is set). This change improves reliability by avoiding
DNS conflicts and simplifies the certificate obtention process.

Changes:
- Add acme_challenge.conf to serve .well-known/acme-challenge directory
- Update certbot.yml to use --webroot for root domains instead of dns-standalone
- Use official certbot/certbot:latest image for webroot (lighter, no DNS needed)
- Add certbot_webroot volume shared between nginx and certbot containers
- Configure vhost.conf to include ACME challenge location for root domains
- Add certbot_webroot variable (/var/www/certbot) to proxy vars

Benefits for root domains:
- No port 53 conflicts with Knot DNS
- Faster validation (HTTP vs DNS propagation)
- More reliable and simpler error handling
- Works with nginx already running on port 80

Wildcard domains continue using DNS-01 challenge as HTTP-01 does not
support wildcard certificates.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 19:48:44 -03:00
fc76a7fb91 refactor: rename no_wildcard to root and fix nginx vhost generation
Rename the flag from `no_wildcard` to `root` throughout the codebase
for better semantics, and fix nginx configuration generation for root
domains.

Changes:
1. Renamed `no_wildcard` → `root` flag in:
   - abyayala.yml (abyaya_root service)
   - roles/certbot/tasks/certbot.yml (uses is_root_domain internally)
   - README-root-domain.md (documentation)

2. Fixed nginx vhost generation in roles/proxy/templates/:
   - vhost.conf: Handle root domains without leading dot in server_name
     * root: yes → `server_name abyaya.la www.abyaya.la;` (exact match)
     * root: no  → `server_name .comun.abyaya.la;` (wildcard match)
   - stream.conf: Same logic for SSH proxy streams

Problem fixed:
- Previous: `.abyaya.la` matched all subdomains, conflicting with
  other vhosts (comun.abyaya.la, sutty.abyaya.la, etc.)
- Now: `abyaya.la www.abyaya.la` matches only root domain exactly

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 19:45:54 -03:00
7d5044f1f1 feat: add support for root domain www.abyaya.la
Add capability to route root domain (abyaya.la and www.abyaya.la)
to sutty.comun while maintaining all existing subdomain routes.

Changes:
- Add no_wildcard flag support in certbot certificate generation
- Split certificate obtention into two conditional paths:
  * Standard mode (with wildcard) for subdomains
  * No-wildcard mode for root domains
- Add abyaya_root service in matrix routing to sutty.comun
- Include implementation plan as README-root-domain.md

Technical details:
- Certificates for root domain will only include abyaya.la and
  www.abyaya.la (no *.abyaya.la wildcard)
- Prevents certificate confusion between root and subdomains
- Maintains clean separation of responsibilities
- All existing subdomain certificates remain unchanged

Generated with Claude Code
2025-12-15 19:45:20 -03:00
f1d37f21f7 tageo como instalacion esto 2025-12-15 19:45:20 -03:00
66e3c6fcaf fix: separar set_fact para compatibilidad con Ansible 2.15+
En Ansible 2.15+ las variables en el mismo set_fact se evalúan
simultáneamente, no secuencialmente. Separar needs_cert en su propio
set_fact antes de usarlo en needs_vhost y obtain_cert.
2025-12-15 19:45:20 -03:00
1fc58432dd fix: aplicar | bool en when clause de obtain_cert 2025-12-15 19:45:20 -03:00
d683f8bcd2 fix: envolver expresiones booleanas en templates Jinja2
Aplicar | bool al resultado final de cada expresión y envolver
en sintaxis {{ }} para forzar evaluación correcta como booleanos.
2025-12-15 19:45:20 -03:00
5c19e789c4 fix: forzar conversión a bool en condicionales de certbot
Ansible requiere que las condicionales resulten en booleanos.
Agregado filtro | bool para convertir explícitamente strings a booleanos
en las evaluaciones de needs_cert, needs_vhost y obtain_cert.
2025-12-15 19:45:20 -03:00
838cc5833c fix: mover limpieza de repos Docker fuera del bloque installation
La limpieza de repositorios Docker antiguos debe ejecutarse SIEMPRE,
incluso cuando se usa --skip-tags=installation, para evitar conflictos
APT antes de que knsupdate u otros roles intenten usar apt.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 19:45:20 -03:00
135edfe527 fix: limpiar repositorios Docker antiguos antes de configurar deb822
Soluciona conflicto APT causado por configuraciones de repositorio Docker
duplicadas con valores Signed-By contradictorios. Ahora se eliminan los
archivos de repositorio antiguos antes de agregar la configuración deb822.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 19:45:20 -03:00
f72983049d deabajo nodo 2025-12-15 19:45:20 -03:00
be5f8fc5e7 dns_extras en archivos con nombres de dominio principal, en
roles/knsupdate/files/dns_extras
2025-12-15 19:44:01 -03:00
acdf35e5a7 poner archivos de webroot 2025-12-15 17:06:28 -03:00
ee5513587e Merge branch 'webroot-certbot' 2025-12-15 14:26:29 -03:00
4ad59e5068 feat: implement webroot method for root domain SSL certificates
Replace dns-standalone with HTTP-01 webroot validation for root domains
(when root: yes flag is set). This change improves reliability by avoiding
DNS conflicts and simplifies the certificate obtention process.

Changes:
- Add acme_challenge.conf to serve .well-known/acme-challenge directory
- Update certbot.yml to use --webroot for root domains instead of dns-standalone
- Use official certbot/certbot:latest image for webroot (lighter, no DNS needed)
- Add certbot_webroot volume shared between nginx and certbot containers
- Configure vhost.conf to include ACME challenge location for root domains
- Add certbot_webroot variable (/var/www/certbot) to proxy vars

Benefits for root domains:
- No port 53 conflicts with Knot DNS
- Faster validation (HTTP vs DNS propagation)
- More reliable and simpler error handling
- Works with nginx already running on port 80

Wildcard domains continue using DNS-01 challenge as HTTP-01 does not
support wildcard certificates.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 14:03:22 -03:00
e17f872aba fix: properly handle DNS delegation for root domain and www subdomain
Updates knsupdate logic to correctly generate DNS records for:
- abyaya.la (zone apex @)
- www.abyaya.la (www subdomain)
- Existing subdomains (sutty, marmite, etc.)

This fixes the Let's Encrypt DNS-01 challenge failure for www.abyaya.la
by ensuring _acme-challenge records are created correctly.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Merge
2025-12-15 13:47:50 -03:00
e5a756525b fix: properly handle DNS delegation for root domain and www subdomain
Updates knsupdate logic to correctly generate DNS records for:
- abyaya.la (zone apex @)
- www.abyaya.la (www subdomain)
- Existing subdomains (sutty, marmite, etc.)

This fixes the Let's Encrypt DNS-01 challenge failure for www.abyaya.la
by ensuring _acme-challenge records are created correctly.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 13:34:17 -03:00
c085c99382 Merge branch 'root-domain' 2025-12-15 13:17:39 -03:00
890c496b4e refactor: rename no_wildcard to root and fix nginx vhost generation
Rename the flag from `no_wildcard` to `root` throughout the codebase
for better semantics, and fix nginx configuration generation for root
domains.

Changes:
1. Renamed `no_wildcard` → `root` flag in:
   - abyayala.yml (abyaya_root service)
   - roles/certbot/tasks/certbot.yml (uses is_root_domain internally)
   - README-root-domain.md (documentation)

2. Fixed nginx vhost generation in roles/proxy/templates/:
   - vhost.conf: Handle root domains without leading dot in server_name
     * root: yes → `server_name abyaya.la www.abyaya.la;` (exact match)
     * root: no  → `server_name .comun.abyaya.la;` (wildcard match)
   - stream.conf: Same logic for SSH proxy streams

Problem fixed:
- Previous: `.abyaya.la` matched all subdomains, conflicting with
  other vhosts (comun.abyaya.la, sutty.abyaya.la, etc.)
- Now: `abyaya.la www.abyaya.la` matches only root domain exactly

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 12:15:19 -03:00
c86059e319 Merge pull request 'Custom DNS' (#78) from customdnsfiles into master
Reviewed-on: #78
2025-12-12 18:50:15 +00:00
07dfd834ca feat: add support for root domain www.abyaya.la
Add capability to route root domain (abyaya.la and www.abyaya.la)
to sutty.comun while maintaining all existing subdomain routes.

Changes:
- Add no_wildcard flag support in certbot certificate generation
- Split certificate obtention into two conditional paths:
  * Standard mode (with wildcard) for subdomains
  * No-wildcard mode for root domains
- Add abyaya_root service in matrix routing to sutty.comun
- Include implementation plan as README-root-domain.md

Technical details:
- Certificates for root domain will only include abyaya.la and
  www.abyaya.la (no *.abyaya.la wildcard)
- Prevents certificate confusion between root and subdomains
- Maintains clean separation of responsibilities
- All existing subdomain certificates remain unchanged

Generated with Claude Code
2025-12-12 15:15:22 -03:00
554c51e94c Merge branch 'master' into customdnsfiles 2025-12-12 14:26:22 -03:00
7673ccd160 tageo como instalacion esto 2025-12-12 14:25:03 -03:00
ad118402f7 fix: separar set_fact para compatibilidad con Ansible 2.15+
En Ansible 2.15+ las variables en el mismo set_fact se evalúan
simultáneamente, no secuencialmente. Separar needs_cert en su propio
set_fact antes de usarlo en needs_vhost y obtain_cert.
2025-12-12 13:42:05 -03:00
54b24af0b5 fix: aplicar | bool en when clause de obtain_cert 2025-12-12 13:34:49 -03:00
08a3e563d4 fix: envolver expresiones booleanas en templates Jinja2
Aplicar | bool al resultado final de cada expresión y envolver
en sintaxis {{ }} para forzar evaluación correcta como booleanos.
2025-12-12 12:42:50 -03:00
b31a9abcad fix: forzar conversión a bool en condicionales de certbot
Ansible requiere que las condicionales resulten en booleanos.
Agregado filtro | bool para convertir explícitamente strings a booleanos
en las evaluaciones de needs_cert, needs_vhost y obtain_cert.
2025-12-12 11:35:23 -03:00
11ec613ae9 fix: mover limpieza de repos Docker fuera del bloque installation
La limpieza de repositorios Docker antiguos debe ejecutarse SIEMPRE,
incluso cuando se usa --skip-tags=installation, para evitar conflictos
APT antes de que knsupdate u otros roles intenten usar apt.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 11:09:48 -03:00
3fb144d02f fix: limpiar repositorios Docker antiguos antes de configurar deb822
Soluciona conflicto APT causado por configuraciones de repositorio Docker
duplicadas con valores Signed-By contradictorios. Ahora se eliminan los
archivos de repositorio antiguos antes de agregar la configuración deb822.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 10:58:59 -03:00
e5dff263d1 deabajo nodo 2025-12-11 18:40:38 -06:00
f
06654b9e41 Merge branch 'master' into issue42 2025-12-06 12:09:17 -03:00
d94cfd89d0 fix: asegurar que dominio .abyaya.la esté siempre en domains[0]
Modifica la lógica para garantizar que el dominio .abyaya.la siempre
sea el primero en la lista de dominios, independientemente del orden
definido en abyayala.yml. Esto es crítico para certificados SSL y
configuraciones vhost que dependen de domains[0].
2025-12-02 19:07:38 -03:00
1a00b8a60c expand en certbot para multiples dominios variables 2025-12-02 17:44:30 -03:00
127504cb62 fix logs del certbot 2025-12-02 17:37:18 -03:00
1b69f0d13f simplifico sintaxis con include 2025-12-01 15:08:07 -03:00
5e849ecee2 Merge branch 'master' into customdnsfiles 2025-12-01 14:44:51 -03:00
f180972d15 fix: revertir proxy_ssl_name a $ssl_server_name
El uso de $host en lugar de $ssl_server_name no es correcto ya que:
- proxy_ssl_verify está deshabilitado, por lo que el SNI no importa
- $ssl_server_name es el valor correcto para SNI en proxies SSL
- $host causaba confusión innecesaria

Revierte a la configuración estándar y correcta.
2025-11-27 16:28:55 -03:00
dfbd04480c Merge branch 'master' into cutomdnsfile 2025-11-26 14:50:46 -03:00
180a7f2ab6 recorto algunos tld innecesarios
y le saco el no a ssl para otra cosa
2025-11-26 13:11: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
f106838dc1 dns_extras en archivos con nombres de dominio principal, en
roles/knsupdate/files/dns_extras
2025-11-25 14:04:05 -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
21 changed files with 637 additions and 39 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

99
README-root-domain.md Normal file
View File

@ -0,0 +1,99 @@
# Plan: Añadir Soporte para Dominio Principal www.abyaya.la
## Objetivo
Habilitar que el dominio raíz `abyaya.la` y `www.abyaya.la` apunten al servidor `sutty.comun`, mientras se mantienen funcionando todos los subdominios existentes (ej: `sutty.abyaya.la`, `marmite.abyaya.la`).
## Estrategia de Desarrollo
### Rama Tópica
Los cambios se implementarán en esta rama tópica:
- **Nombre de rama**: `root-domain`
- **Bifurcada desde**: `master`
## Contexto Técnico
### Arquitectura Actual
- **Certificados**: Cada servicio en `abyayala.yml` con `domains` + `nodo` genera un certificado con certbot: `-d DOMAIN -d *.DOMAIN`
- **Patron actual**: Todos los dominios siguen el patrón `subdomain.abyaya.la` (ej: `sutty.abyaya.la`)
### Decisión de Diseño
Crear un **servicio separado** para el dominio raíz con un nuevo flag `root: yes` que indica a certbot que NO solicite el certificado wildcard. Esto mantiene limpia la separación de responsabilidades:
- Servicio `sutty`: maneja `sutty.abyaya.la` y `*.sutty.abyaya.la`
- Servicio `abyaya_root`: maneja únicamente `abyaya.la` y `www.abyaya.la` (sin wildcard)
## Cambios Implementados
### 1. `roles/certbot/tasks/certbot.yml`
- Lee el flag `root` del servicio y lo mapea a variable `is_root_domain`
- Dividido el bloque de certificados en dos tareas condicionales:
- Modo estándar (con wildcard): para subdominios (`when: not is_root_domain`)
- Modo sin wildcard: para dominios raíz (`when: is_root_domain`)
### 2. `roles/proxy/templates/vhost.conf`
- Agregado condicional para manejar flag `root`
- Cuando `root: yes`: genera `server_name` sin punto prefijo (exacto)
- Cuando `root: no` (default): genera `server_name .domain` (con wildcard)
### 3. `abyayala.yml`
- Agregado servicio `abyaya_root` con:
- Dominios: `abyaya.la` y `www.abyaya.la`
- Enrutamiento a: `sutty.comun`
- Flag: `root: yes`
- HTTPS forzado y compresión habilitada
## Comandos de Despliegue
### Despliegue completo
```bash
ansible-playbook deploy.yml -e "alt=abyayala host=hetzner"
```
### Despliegue solo del servicio nuevo (para testing)
```bash
ansible-playbook deploy.yml -e "alt=abyayala host=hetzner service=abyaya_root"
```
## Verificación Post-Despliegue
### 1. Verificar certificado
```bash
ssh root@hetzner
ls -la /var/lib/docker/volumes/abyayala_certs_data/_data/live/abyaya.la/
docker run --rm -v abyayala_certs_data:/etc/letsencrypt certbot/certbot certificates | grep abyaya.la
```
Debe contener: `abyaya.la`, `www.abyaya.la` (SIN `*.abyaya.la`)
### 2. Verificar nginx
```bash
cat /opt/abyayala/proxy/vhosts/abyaya.la.conf
docker exec abyayala_proxy nginx -t
```
### 3. Verificar enrutamiento
```bash
curl -I http://abyaya.la # Debe redirigir a HTTPS
curl -I https://abyaya.la # Debe devolver 200 OK
curl -I https://www.abyaya.la # Debe devolver 200 OK
```
### 4. Verificar subdominios siguen funcionando
```bash
curl -I https://sutty.abyaya.la
curl -I https://marmite.abyaya.la
```
## Requisitos DNS
Antes del despliegue, configurar:
```
abyaya.la A <IP-del-proxy>
www.abyaya.la A <IP-del-proxy>
```
## Extensibilidad Futura
Este patrón `root: yes` puede reutilizarse para otros dominios raíz:
1. Agregar entrada en `abyayala.yml` con `root: yes`
2. Desplegar con ansible-playbook
3. Configurar DNS

View File

@ -59,6 +59,7 @@ matrix:
- samatuun
- kaasavi
- llavero
- deabajo
- service_name: respaldos
domains:
@ -165,6 +166,15 @@ matrix:
force_https: yes
enable_compression: yes
- service_name: abyaya_root
domains:
- abyaya.la
- www.abyaya.la
nodo: sutty.comun
force_https: yes
enable_compression: yes
root: yes
- service_name: mexe
domains:
- mexe.abyaya.la
@ -204,11 +214,14 @@ matrix:
- service_name: kipu
domains:
- kipu.abyaya.la
# - abyaya.la
# - www.abyaya.la
- kipu.latina.red
nodo: kipu.comun
ports:
- 223
force_https: yes
ssl: yes
# root: yes
- service_name: carabobolibre
domains:
@ -232,4 +245,10 @@ matrix:
domains:
- llavero.abyaya.la
nodo: llavero.comun
force_https: yes
force_https: yes
- service_name: deabajo
domains:
- deabajo.abyaya.la
nodo: deabajo.comun
force_https: yes

View File

@ -1,6 +1,16 @@
# DOCKER CE this is specific for Debian
# https://docs.docker.com/install/linux/docker-ce/debian/
# Soporta Debian 12 (bookworm) y Debian 13 (trixie)
# Clean up conflicting Docker repositories first (always runs, even with --skip-tags=installation)
- name: remove old docker repository files to avoid APT conflicts
file:
path: "{{ item }}"
state: absent
loop:
- /etc/apt/sources.list.d/docker.list
- /etc/apt/sources.list.d/download_docker_com_linux_debian.list
- block:
- name: "unattended upgrades"
apt:

View File

@ -10,33 +10,55 @@
register: vhost_stat
- set_fact:
needs_cert: (loop.ssl | default(domains_default_ssl) ) or (loop.force_https | default(domains_default_force_https))
needs_vhost: needs_cert and not vhost_stat.stat.exists
obtain_cert: needs_cert and not ssl_cert.stat.exists
needs_cert: "{{ ((loop.ssl | default(domains_default_ssl) | bool) or (loop.force_https | default(domains_default_force_https) | bool)) | bool }}"
- set_fact:
needs_vhost: "{{ (needs_cert | bool and not vhost_stat.stat.exists) | bool }}"
obtain_cert: "{{ (needs_cert | bool and not ssl_cert.stat.exists) | bool }}"
- name: certificate obtention
block:
- set_fact:
vhost: "{{ loop }}"
- name: fetch certificate with certbot container
- set_fact:
is_root_domain: "{{ loop.root | default(false) | bool }}"
- name: fetch certificate with certbot container (with wildcard)
docker_container:
name: chencriptemos
image: "{{ CERTBOT_image }}"
state: started
volumes:
- "{{ althost }}_certs_data:/etc/letsencrypt"
command: "--non-interactive --agree-tos --email {{ webmaster_email }} certonly --preferred-challenges dns --authenticator dns-standalone --dns-standalone-address={{ host_ip }} --dns-standalone-port=53 --dns-standalone-propagation-seconds=10 {% for domain in loop.domains %} -d {{ domain }} -d *.{{ domain }} {% endfor %}"
command: "--non-interactive --agree-tos --expand --email {{ webmaster_email }} certonly --preferred-challenges dns --authenticator dns-standalone --dns-standalone-address={{ host_ip }} --dns-standalone-port=53 --dns-standalone-propagation-seconds=10 {% for domain in loop.domains %} -d {{ domain }} -d *.{{ domain }} {% endfor %}"
detach: no
cleanup: yes
ports:
ports:
- "{{ host_ip }}:53:53/tcp"
- "{{ host_ip }}:53:53/udp"
notify:
- reload proxy
register: cert_result
when: not is_root_domain | bool
when: obtain_cert
- name: fetch certificate with certbot container (without wildcard, root domain - using webroot)
docker_container:
name: chencriptemos
image: certbot/certbot:latest
state: started
volumes:
- "{{ althost }}_certs_data:/etc/letsencrypt"
- "{{ althost }}_certbot_webroot:{{ certbot_webroot }}"
command: "--non-interactive --agree-tos --expand --email {{ webmaster_email }} certonly --webroot --webroot-path {{ certbot_webroot }} {% for domain in loop.domains %} -d {{ domain }} {% endfor %}"
detach: no
cleanup: yes
notify:
- reload proxy
register: cert_result
when: is_root_domain | bool
when: obtain_cert | bool
# RESET
- set_fact:

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 --log-driver=journald"
- name: proxy update, after certs renewal
cron:

View File

@ -0,0 +1,8 @@
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"

View File

@ -2,5 +2,6 @@
apt:
name: "knot-dnsutils"
state: "present"
tags: installation
- include_tasks: loop.yml

View File

@ -1,19 +1,31 @@
{% 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
{% if vhost.dns_extras is defined %}
{% for dns_extra in vhost.dns_extras %}
{{ dns_extra }}
{% endfor %}
del {{ hostname }} a
del {{ hostname }} ns
add {{ hostname }} a {{ host_ip }}
{% if hostname != '@' and hostname != 'www' %}
add *.{{ hostname }} a {{ host_ip }}
{% else %}
add {{ domain }} a {{ host_ip }}
add *.{{ domain }} a {{ host_ip }}
{% endif %}
{% if hostname == '@' %}
add _acme-challenge a {{ host_ip }}
add _acme-challenge ns _acme-challenge
{% else %}
add _acme-challenge.{{ hostname }} a {{ host_ip }}
add _acme-challenge.{{ hostname }} ns _acme-challenge
{% endif %}
{% include "files/dns_extras/" ~ vhost.domains[0] ignore missing %}
send
{% endfor %}
quit

View File

@ -0,0 +1,21 @@
========================================
Configuracion de DNS requerida: {{ domain }}
========================================
Por favor configue los siguiente registros DNS en su proveedor:
{% if hostname == '@' %}
{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
*.{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
_acme-challenge.{{ zone | regex_replace('\\.$', '') }} IN NS ns-acme.{{ zone | regex_replace('\\.$', '') }}.
ns-acme.{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
{% else %}
{{ hostname }}.{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
*.{{ hostname }}.{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
_acme-challenge.{{ hostname }}.{{ zone | regex_replace('\\.$', '') }} IN NS ns-acme.{{ hostname }}.{{ zone | regex_replace('\\.$', '') }}.
ns-acme.{{ hostname }}.{{ zone | regex_replace('\\.$', '') }} IN A {{ host_ip }}
{% endif %}
========================================

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,42 @@
- 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') }}"
when: is_abyayala_subdomain
- name: display DNS configuration instructions for external domains
debug:
msg: "{{ lookup('template', 'templates/dns_info.j2') }}"
when: not is_abyayala_subdomain

View File

@ -3,3 +3,10 @@ dns_servers:
- "athshe.sutty.nl"
- "gethen.sutty.nl"
- "ganam.sutty.nl"
compound_tlds:
- com.ar
- com.mx
- com.br
- org.ar
- edu.ar

View File

@ -15,6 +15,7 @@
with_items:
- "{{ stream_path }}"
- "{{ conf_path }}"
- "{{ certbot_webroot }}"
loop_control:
loop_var: comun
@ -32,6 +33,7 @@
- common.conf
- common_ssl.conf
- nginx.conf
- acme_challenge.conf
loop_control:
loop_var: common
@ -45,6 +47,24 @@
loop_control:
loop_var: domino
- name: ensure abyaya.la subdomain is always first in domains list
set_fact:
matrix_loop_with_defaults: "{{ matrix_loop_with_defaults | default([]) | union([ item_with_default ]) }}"
vars:
existing_abyayala_domains: "{{ item.domains | select('match', '.*\\.abyaya\\.la$') | list }}"
has_abyayala_domain: "{{ existing_abyayala_domains | length > 0 }}"
default_domain: "{{ item.service_name }}.abyaya.la"
other_domains: "{{ item.domains | reject('match', '.*\\.abyaya\\.la$') | list }}"
abyayala_domain_to_use: "{{ existing_abyayala_domains[0] if has_abyayala_domain else default_domain }}"
domains_with_default: "{{ [abyayala_domain_to_use] + other_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([]) }}"

View File

@ -0,0 +1,25 @@
# Let's Encrypt ACME challenge configuration
# This configuration serves the .well-known/acme-challenge directory
# for HTTP-01 validation when using webroot method
location ^~ /.well-known/acme-challenge/ {
# Serve files from the certbot webroot
root {{ certbot_webroot }};
# Allow access to challenge files
allow all;
# Ensure plain text content type
default_type "text/plain";
# Disable any authentication
auth_basic off;
# Serve the challenge file directly
try_files $uri =404;
}
# Deny access to other .well-known paths for security
location ~ /\.well-known/ {
deny all;
}

View File

@ -19,3 +19,4 @@
- "certs_data:{{ nginx_certs_path }}:ro"
- "{{ conf_path }}/nginx.conf:/etc/nginx/nginx.conf:ro"
- "{{ stream_path }}:/etc/nginx/stream.d/"
- "certbot_webroot:{{ certbot_webroot }}"

View File

@ -5,7 +5,11 @@ upstream ssh_{{ vhost.nodo | replace(".", "") }} {
server {
listen {{ vhost.ports[0] }};
server_name .{{ vhost.domains | join(' .') }};
{% if vhost.root | default(false) %}
server_name {{ vhost.domains | join(' ') }};
{% else %}
server_name .{{ vhost.domains | join(' .') }};
{% endif %}
proxy_pass ssh_{{ vhost.nodo | replace(".", "") }};
}

View File

@ -1,18 +1,31 @@
map $http_host $comun_{{ vhost.nodo | replace(".", "") }} {
hostnames;
{% for domain in vhost.domains %}
{% if vhost.root | default(false) %}
{{ domain }} {{ vhost.nodo }};
{% else %}
.{{ domain }} {{ vhost.nodo }};
{% endif %}
{% endfor %}
}
server {
{% if vhost.root | default(false) %}
server_name {{ vhost.domains | join(' ') }};
{% else %}
server_name .{{ vhost.domains | join(' .') }};
{% endif %}
listen 80;
resolver 10.13.12.1 valid=300s;
resolver_timeout 5s;
{% if vhost.root | default(false) %}
# ACME challenge for HTTP-01 validation (webroot method for root domains)
include conf/acme_challenge.conf;
{% endif %}
{% if not needs_vhost and ((vhost.ssl | default(domains_default_ssl) ) or (vhost.force_https | default(domains_default_force_https))) %}
listen 443 ssl;

View File

@ -1 +1,2 @@
certs_data:
certbot_webroot:

View File

@ -15,3 +15,4 @@ default_stream: roles/proxy/templates/stream.conf
# certbot
webmaster_email: webmaster@numerica.cl
CERTBOT_image: numericalatina/certbot-wildcard
certbot_webroot: /var/www/certbot