Categorías
#!/Console Blog Blog Sin categoría

Montando un servicio DDNS con actualización por HTTP REST en Debian 12 con Bind9

Como siempre, todo surge de una necesidad, o bien por que ningún producto gratuito del mercado la satisface o por que me es más fácil hacer el mío propio que empezar a probar servicios de terceros de alguna lista de Google.

La necesidad es clara, necesito un nombre DNS estático para una IP dinámica y sin pensarlo cree un No-Ip, que es el que siempre había utilizado en mis tiempos mozos y el que OmBliGuoS utilizo durante años para los servidores de CS. La decepción es cuando al cabo del mes me llego un correo para verificar que todavía lo quería mantener durante un mes más… Necesito una alternativa a No-Ip, pues a construir.

Comenzamos por instalar un servidor nuevos dentro de mi ESXi, yo para mi proyecto he elegido un Debian 12.

Una vez instalado y con la configuración básica del sistema vamos a los componentes necesarios, para la gestión DNS utilizaremos BIND 9 (named) y para recibir las peticiones HTTP con un siempre script en PHP bastara, por lo que vamos a necesitar Apache. Para una versión que vayas a llevar fuera de una red local o a producción también necesitaras una base de datos para guardar tokens de seguridad para tus llamas a la API, que voy a obviar por el momento.

apt update
apt install apache2 bind9 php php-pdo php-fpm

Una vez tenemos instalado el software necesario vamos con las configuraciones:

Configurando DNS

Empezamos con el DNS. En el pasado había tenido una cosa similar, en aquella ocasión me pareció buena idea que cada vez que llegaba una petición, mediante un script modificaba la base de datos del DNS modificando en host en la zona y reiniciaba el servidor, podríamos decir que no son las mejores practicas, por lo que esta vez lo vamos a hacer creando una zona que permita las actualizaciones dinámicas y utilizando las herramientas que estan creadas para eso.

Empezamos definiendo el nombre de la zona para este supuesto será «ddns.test.cat» por lo que vamos a crear todo lo necesario.

Creamos una clave HMAC-SHA256 para que sera la que autenticara la firma de actualización

sudo tsig-keygen -a hmac-sha256 ddns.test.cat

## Respuesta similar a esta

key "ddns.test.cat" {
    algorithm hmac-sha256;
    secret "AquiVaLaCave==";
};

Copiamos la respuesta y la vamos a guardar en un archivo llamado update.key a demás esta respuesta la vamos a necesitar para la definción de la zona modificando el archivo /etc/bind/named.conf.local:

zone "ddns.test.cat" IN {
    type master;
    file "/var/lib/bind/ddns.test.cat.db";
    allow-update { key "ddns.test.cat."; };
};

key "ddns.test.cat" {
    algorithm hmac-sha256;
    secret "AquiVaLaCave==";
};

Ahora debemos generar la base de datos de la zona con un contenido básico en el destino elegido, en este caso /var/lib/bind/ddns.test.cat.db:

$TTL 3600
@ IN SOA ns.ddns.test.cat. admin.ddns.controladf.cat. (
            2023010101 ; Serial
            7200       ; Refresh
            1200       ; Retry
            2419200    ; Expire
            60 )       ; Negative Cache TTL
;
@    IN NS  ns.ddns.test.cat.
ns   IN A   192.0.2.1

Configuración completada, vamos a hacer pruebas:

sudo systemctl restart bind9
sudo named-checkzone ddns.test.cat /var/lib/bind/ddns.test.cat.db
## Respuesta Similar a esta

zone ddns.test.cat/IN: loaded serial 2023010102
OK

Si recibimos una respuesta similar a esta donde da el Ok es que todo ha salido bien, vamos ahora a probar una actualización de un nuevo host (test.ddns.test.cat). Para ello creamos un archivo llamado update.txt con el siguiente contenido:

server localhost
zone ddns.test.cat
update delete test.ddns.test.cat A
update add test.ddns.test.cat 3600 A 192.168.133.10
show
send

Y vamos a ejecutar nsupdate con los archivos de clave generado al principio y nuestro txt

nsupdate -k update.key -v update.txt

## Respuesta similar a esta:

Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; ZONE SECTION:
;ddns.test.cat.           IN      SOA

;; UPDATE SECTION:
test.ddns.test.cat. 0     ANY     A
test.ddns.test.cat. 3600  IN      A       192.168.133.10

Si todo ha ido bien no aparecerá ningún error, y con nslookup podremos comprobar que realmente ese nombre tiene resolucion:

alberto@ddns:~$ nslookup
> server localhost
Default server: localhost
Address: ::1#53
Default server: localhost
Address: 127.0.0.1#53
> test.ddns.test.cat
Server:         localhost
Address:        ::1#53

Name:   test.ddns.test.cat
Address: 192.168.133.10
>

Creando nuestro PHP

Vamos a crear nuestro script tipo API Rest que llamaran los host que quieran actualizarse, este script es funciona, pero no refleja las mejores practicas de seguridad, ya que básicamente no utiliza ningun tipo de autenticación.

<?php


$token = $_GET['token']; //para futuras implementaciones
$nombre = $_GET['nombre'];
$ip = $_GET['ip'];

// Vamos a hacer unas expresiones básicas para comprobar que no nos estén pasando parámetros un poco malignos...

if (!preg_match('/^[a-zA-Z0-9_-]+$/', $nombre)) {
    die("Nombre DNS inválido.");
}

if (!filter_var($ip, FILTER_VALIDATE_IP)) {
    die("Dirección IP inválida.");
}

// Asegurate que la clave puede leerla apache y que no se encuentra en ningun directorio que se publique 

$cmd = "nsupdate -k /ruta/a/tu/clave.key << EOF
server localhost
zone ddns.test.cat
update delete $nombre.ddns.test.cat A
update add $nombre.ddns.test.cat 3600 A $ip
send
EOF";

Ahora puedes utilizar CURL o Postman para probar que funcione y ya tendrás una buena base para construir el resto del servicio

alberto@ddns:~$ curl "http://localhost/update.php?nombre=test&ip=192.168.0.1"
Actualización exitosa

Saludos!