Soporte para FQDN #76

Merged
Numerica merged 26 commits from issue42 into master 2025-12-16 15:00:07 +00:00
9 changed files with 246 additions and 32 deletions

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
fauno marked this conversation as resolved
Review

creo que es confuso en la documentación decir que el dominio HUERTA.abyaya.la es automático y luego usar de ejemplo un subdominio de abyaya.la específico. creo que tendríamos que dejar los subdominios de abyaya.la reservados solo para huertas

creo que es confuso en la documentación decir que el dominio HUERTA.abyaya.la es automático y luego usar de ejemplo un subdominio de abyaya.la específico. creo que tendríamos que dejar los subdominios de abyaya.la reservados solo para huertas
Review

Ah ok, aunque esa documentación es interna para nos, la eliminaremos en el merge

Los dominios* son para huertas, de hecho son el default, es opcional mencionarlos para retrocompatibilidad. en realidad ahora domains: servirá más para agregar dominios fqdn

Ah ok, aunque esa documentación es interna para nos, la eliminaremos en el merge Los dominios* son para huertas, de hecho son el default, es opcional mencionarlos para retrocompatibilidad. en realidad ahora domains: servirá más para agregar dominios fqdn
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
Numerica marked this conversation as resolved
Review

no veo dónde sucede esto, ya lo hacía certbot? me preocupa en términos de privacidad hacer público que dos o más dominios están relacionados con la misma huerta, y que certbot haga cualquiera cuando agreguemos un dominio más. haría que certbot genere un certificado para cada dominio. si eso es muy complicado, usaría la flag --expand para aclarar que queres que se agreguen dominios a un certificado que ya existía. sino creo que hace la gilada esa de poner -0001 al final del archivo

no veo dónde sucede esto, ya lo hacía certbot? me preocupa en términos de privacidad hacer público que dos o más dominios están relacionados con la misma huerta, y que certbot haga cualquiera cuando agreguemos un dominio más. haría que certbot genere un certificado para cada dominio. si eso es muy complicado, usaría la flag `--expand` para aclarar que queres que se agreguen dominios a un certificado que ya existía. sino creo que hace la gilada esa de poner -0001 al final del archivo
Review

en el primer caso, hay que generar un archivo de configuración de nginx por cada dominio o hacer que el certificado se cargue desde la variable $ssl_server_name

en el primer caso, hay que generar un archivo de configuración de nginx por cada dominio o hacer que el certificado se cargue desde la variable `$ssl_server_name`
Review

interesante..

  • si, ya lo hacía certbot, estaba comentando desde que cambiamos a standalone. lo deja en un cron
  • si, toma para todos los dominios. efectivamente es un problema cuando agregamos posteriormente dominios (en mi experiencia en Numérica). si lo podemos resolver con --expand creo que sería fácil y limpio, es lo que estamos haciendo.
  • dominios independiente resolverían el tema que planteas de privacidad, lo evaluaremos
interesante.. - si, ya lo hacía certbot, estaba comentando desde que cambiamos a standalone. lo deja en un cron - si, toma para todos los dominios. efectivamente es un problema cuando agregamos posteriormente dominios (en mi experiencia en Numérica). si lo podemos resolver con --expand creo que sería fácil y limpio, es lo que estamos haciendo. - dominios independiente resolverían el tema que planteas de privacidad, lo evaluaremos
Review

ok le agregás el --expand por ahora?

ok le agregás el --expand por ahora?
```
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.
Numerica marked this conversation as resolved
Review

guarda que esto implica que haya un subdominio _acme-challenge para todos los dominios solicitados

guarda que esto implica que haya un subdominio `_acme-challenge` para todos los dominios solicitados
Review

si, se estarían agregando más abajo en knsupdate

si, se estarían agregando más abajo en knsupdate
## 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

View File

@ -214,14 +214,14 @@ matrix:
- service_name: kipu
domains:
- abyaya.la
- www.abyaya.la
# - kipu.abyaya.la
# - abyaya.la
# - www.abyaya.la
- kipu.latina.red
nodo: kipu.comun
ports:
- 223
force_https: yes
root: yes
ssl: yes
# root: yes
- service_name: carabobolibre
domains:

View File

@ -31,7 +31,7 @@
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:
@ -50,7 +50,7 @@
volumes:
- "{{ althost }}_certs_data:/etc/letsencrypt"
- "{{ althost }}_certbot_webroot:{{ certbot_webroot }}"
command: "--non-interactive --agree-tos --email {{ webmaster_email }} certonly --webroot --webroot-path {{ certbot_webroot }} {% for domain in loop.domains %} -d {{ domain }} {% endfor %}"
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:

View File

@ -1,25 +1,27 @@
{% for dns_server in dns_servers %}
server {{ dns_server }}
zone abyaya.la.
origin abyaya.la.
zone {{ zone }}
Numerica marked this conversation as resolved
Review

no habria que agregar el punto al final?

no habria que agregar el punto al final?
Review

ah, el punto se lo agrega la extracción de la zona

ah, el punto se lo agrega la extracción de la zona
origin {{ zone }}
ttl 60
del {{ vho }} a
del {{ vho }} ns
del {{ hostname }} a
del {{ hostname }} ns
add {{ hostname }} a {{ host_ip }}
add {{ vho }} a {{ host_ip }}
{% if vho != '@' and vho != 'www' %}
add *.{{ vho }} a {{ host_ip }}
{% if hostname != '@' and hostname != 'www' %}
add *.{{ hostname }} a {{ host_ip }}
{% else %}
add {{ domain }} a {{ host_ip }}
add *.{{ domain }} a {{ host_ip }}
Numerica marked this conversation as resolved Outdated
Outdated
Review

ok, todo esto asume que todos los dominios estan gestionados por el mismo dns, cierto?

ok, todo esto asume que todos los dominios estan gestionados por el mismo dns, cierto?

si, por ej el dominio kipu.latina.red lo delegué, puede hacerle

dig kipu.latina.red NS
dig kipu.latina.red CNAME

mas, relacionado a esto quizas falte arreglar algo, que crea estar publicando estos registros en knsupdate tambien? en realidad debe escupirlos como una instruccion: "Copia esto en tu servidor DNS"

si, por ej el dominio kipu.latina.red lo delegué, puede hacerle ``` dig kipu.latina.red NS dig kipu.latina.red CNAME ``` mas, relacionado a esto quizas falte arreglar algo, que crea estar publicando estos registros en knsupdate tambien? en realidad debe escupirlos como una instruccion: "Copia esto en tu servidor DNS"
Outdated
Review

a donde lo delegaste? lo que digo es que la delegación y el knsupdate a los knot de sutty solo es necesaria para HUERTA.abyaya.la, pero no para otros dominios

a donde lo delegaste? lo que digo es que la delegación y el knsupdate a los knot de sutty solo es necesaria para HUERTA.abyaya.la, pero no para otros dominios
{% endif %}
{% if vho == '@' %}
{% if hostname == '@' %}
add _acme-challenge a {{ host_ip }}
add _acme-challenge ns _acme-challenge
{% else %}
add _acme-challenge.{{ vho }} a {{ host_ip }}
add _acme-challenge.{{ vho }} ns _acme-challenge
add _acme-challenge.{{ hostname }} a {{ host_ip }}
add _acme-challenge.{{ hostname }} ns _acme-challenge
{% endif %}
{% include "files/dns_extras/" ~ vhost.domains[0] ignore missing %}

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,14 +1,5 @@
- set_fact:
vho: >-
{%- if vhost.domains[0] == 'abyaya.la' -%}
@
{%- elif vhost.domains[0].endswith('.abyaya.la') -%}
{{ vhost.domains[0] | regex_replace('([a-z0-9]+)\\.abyaya\\.la', '\\1') }}
{%- else -%}
{{ vhost.domains[0] | regex_replace('\\.abyaya\\.la$', '') }}
{%- endif -%}
- 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
Review

no entiendo para qué hacer todo esto, si le paso un dominio sutty.coop.ar o sutty.nl va a pensar que la zona es .coop.ar. y .nl.?

no entiendo para qué hacer todo esto, si le paso un dominio `sutty.coop.ar` o `sutty.nl` va a pensar que la zona es `.coop.ar.` y `.nl.`?
Review

o entiendo que pasandolo por la lista compound_tlds obtiene que la zona es sutty.coop.ar siempre y cuando .coop.ar esté incluido en compound_tlds?

o entiendo que pasandolo por la lista `compound_tlds` obtiene que la zona es `sutty.coop.ar` siempre y cuando `.coop.ar` esté incluido en `compound_tlds`?
Review

en cualquier caso me parece raro tener que hacer todo esto, porque...

  • no podemos asumir que el dominio es la zona? entonces el fqdn sutty.coop.ar es una zona en knot que ya acepta actualizaciones desde el proxy.
  • en el caso de huerta.abyaya.la, ya sabemos que la zona es abyaya.la.
  • con eso, no haría falta tener una lista de tlds admitidos (tarea extra, confusión frente a dominios nuevos, configuraciones potencialmente rotas)
  • de hecho, los dominios externos no necesitan knot para nada, porque esa configuración se puede hacer estáticamente. usamos knot para dar de alta huertas automáticamente, pero un dominio nuevo se configura una sola vez y ya.
en cualquier caso me parece raro tener que hacer todo esto, porque... * no podemos asumir que el dominio es la zona? entonces el fqdn `sutty.coop.ar` es una zona en knot que ya acepta actualizaciones desde el proxy. * en el caso de `huerta.abyaya.la`, ya sabemos que la zona es `abyaya.la`. * con eso, no haría falta tener una lista de tlds admitidos (tarea extra, confusión frente a dominios nuevos, configuraciones potencialmente rotas) * de hecho, los dominios externos no necesitan knot para nada, porque esa configuración se puede hacer estáticamente. usamos knot para dar de alta huertas automáticamente, pero un dominio nuevo se configura una sola vez y ya.
Review

la idea aqui era soportar fqdn que puedan ser compuestos, previendo que pasará con nuevaradio.org.ar y así

está bien que esto no pase por knsupdate, pero es soporte para estos dominios a lo largo del código (las regex cachan si es fqdn o compuesto y ajustan las variables acorde)

la idea aqui era soportar fqdn que puedan ser compuestos, previendo que pasará con __nuevaradio.org.ar__ y así está bien que esto no pase por knsupdate, pero es soporte para estos dominios a lo largo del código (las regex cachan si es fqdn o compuesto y ajustan las variables acorde)
Review

entonces no entiendo la diferenciación que hacés entre fqdn y compuesto. para mi fqdn es cualquier dominio completo incluyendo (g)tld. sutty.comun es un fqdn, porque es un hostname dentro de un dominio, aunque su resolución no sea por dns publico.

la pregunta es por qué es necesario hacer todo esto si no es necesario hacer knsupdate para estos dominios

entonces no entiendo la diferenciación que hacés entre fqdn y compuesto. para mi fqdn es cualquier dominio completo incluyendo (g)tld. `sutty.comun` es un fqdn, porque es un hostname dentro de un dominio, aunque su resolución no sea por dns publico. la pregunta es por qué es necesario hacer todo esto si no es necesario hacer knsupdate para estos dominios
Review

si. yo estoy mal con la nomenlclatura. me rediero a dominios externos (.org .com ...)

si. yo estoy mal con la nomenlclatura. me rediero a dominios externos (.org .com ...)
- 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

@ -47,6 +47,24 @@
loop_control:
loop_var: domino
fauno marked this conversation as resolved Outdated
Outdated
Review

me pregunto si es necesario agregar todo esto en vez de documentar y hacer explicito que hay que listar todos los dominios de una huerta

me pregunto si es necesario agregar todo esto en vez de documentar y hacer explicito que hay que listar todos los dominios de una huerta

bueno digamos que ya está hecho y simplifica. tambien es retro-compatible entonces podes dejarlos si querés

de hecho hice otra rama para unificar la definicio de nodo (que ahora siempre repetimos 3 veces llamandole igual 99.9% de las veces NODO.comun NODO.abayaya.la service_name: nodo, se reducen a una sola variable nodo #80

bueno digamos que ya está hecho y simplifica. tambien es retro-compatible entonces podes dejarlos si querés de hecho hice otra rama para unificar la definicio de nodo (que ahora siempre repetimos 3 veces llamandole igual 99.9% de las veces NODO.comun NODO.abayaya.la service_name: nodo, se reducen a una sola variable nodo #80
- 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([]) }}"