Apuntes de WireGuard

Apuntes sobre WireGuard. Una herramienta para implementar redes VPN

Work in progress
Estos apuntes no están completos, (ni de lejos)

WireGuard

Las implementaciones tradicionales de VPN (Virtual Private Network) más utilizadas en la industria (como IPsec por ejemplo) se implementan dentro del kernel; insertando una capa extra, que se encarga del cifrado y el encapsulado, entre la pila IP y el interfaz de red.

Esto se hace así por que se quiere que sea un proceso tan rápido como sea posible que no penalice la eficiencia de las comunicaciones. A cambio es necesario implementar por separado el plano de tráfico (la capa que hemos mencionado dentro del kernel) y el plano de control que se crea en el espacio de usuario. Esta arquitectura hace que tanto la implementación (el software) como la gestión de estas soluciones sean complejas.

En las implementaciones de VPN en el espacio de usuario, como por ejemplo OpenVPN, todo el proceso de cifrado lo hace un demonio en el espacio de usuario. Con esta arquitectura inevitablemente se penaliza el rendimiento, por qué los paquetes tienen que pasar del espacio kernel al espacio de usuario y viceversa varias veces. Aunque esto no impide que sean soluciones muy populares debido a su facilidad de uso.

WireGuard es una alternativa relativamente reciente a OpenVPN que puede ser muy interesante. Por un lado se implementa como un módulo del Kernel, y por otro mantiene la gestión y configuración a un nivel muy asequible por qué usa ficheros de configuración muy sencillos y se basa en la metáfora de interfaz de red, que se gestiona como cualquier otro interfaz del sistema con las herramientas estándar como ip o ifconfig.

WireGuard cifra y encapsula paquetes IP sobre UDP. Básicamente hay que añadir un interface WireGuard, configurarlo con tu clave privada y la clave pública de los pares (peers) con los que se va a comunicar y ya puedes enviar paquetes a través de ese túnel. En realidad es mucho más parecido a SSH o Mosh que a OpenVPN.

Como hemos dicho, al usar WireGuard se añadirán uno (o varios) interfaces de red de tipo WireGuard (wg0, wg1, ….) a tu máquina. Estos interfaces de red se pueden configurar normalmente con las herramientas del sistema, igual que un eth0 o wlan0. Los aspectos específicos del WireGuard Interface se configuran con la herramienta wg. Los interfaces WireGuard se comportan como un túnel cifrado.

WireGuard asocia las direcciones IP de los túneles con los extremos remotos y sus claves públicas.

Cuando el interfaz envía un paquete a un peer hace lo siguiente:

  1. Veamos, este paquete es para 192.168.30.8. ¿Qué peer es este? Vale, es para el peer pepito. Si no soy capaz de determinar a que peer esta dirigido descartaré el paquete (drop) ¡OJO! WireGuard determina el peer a partir de la IP destino
  2. Cifra el paquete con la clave pública del peer pepito
  3. ¿Qué extremo remoto corresponde a este peer? Vale, es el puerto UDP 53133 en 216.58.211.110
  4. Procedo a enviar los bytes cifrados al extremo remoto via UDP

En el extremo remoto, el peer pepitoque recibe el paquete:

  1. Me ha llegado un paquete del puerto 7361 de 98.139.183.24. Voy a descifrarlo.
  2. Descifro y además compruebo que se garantiza la autenticidad para el peer manolito. Ya que estamos actualizo mi tabla de direcciones: Apunto que el destino más reciente para manolito es el 98.139.183.24:7361/UDP
  3. El paquete descifrado viene de 192.168.43.89 si el peer manolito está autorizado a enviar paquetes con esa IP origen lo aceptamos, de lo contrario lo descartamos (drop)

El concepto fundamental en el que se base WireGuard es el Cryptokey Routing, consiste en almacenar una lista de claves públicas junto con la lista de direcciones IP origen que permitimos, ambas asociadas al peer al que corresponde esa clave.

Así:

  • Cada interfaz de red tiene una clave privada
  • Cada interfaz de red tiene una lista de peers
  • Cada peer tiene una clave pública
  • Para cada peer se especifican las direcciones IP origen autorizadas

Un servidor de WireGuard podría tener una configuración como esta (no está completa es una configuración simplificada):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820

[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24

[Peer]
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16

[Peer]
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
AllowedIPs = 10.10.10.230/32

Un cliente puede tener una configuración más simple (no está completa):

1
2
3
4
5
6
7
8
[Interface]
PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=
ListenPort = 21841

[Peer]
PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=
Endpoint = 192.95.5.69:51820
AllowedIPs = 0.0.0.0/0

Tanto el “servidor” como el “cliente” tienen un interfaz de red (al menos) con su clave privada asociada. Pero no te lies con los términos servidor y cliente, nada impide que configures varios interfaces de tu VPN como servidores.

El servidor tiene también una lista de peer; cada uno con su lista AllowedIPs: la lista de direcciones IP origen que están autorizadas para ese peer

La configuración mínima para un cliente solo necesita tener como peer al servidor. Se almacena una dirección inicial del servidor.

Deliberadamente no se mantienen direcciones estáticas para los peer, los peer se identifican al descifrar un paquete recibido. Y se mantiene una tabla de direcciones más recientes para cada peer que se actualiza con cada mensaje recibido. De esta forma los peer podrían hacer roaming por distintas redes.

Cuando el servidor recibe un paquete comprueba, una vez descifrado, que el peer puede enviar paquetes con ese origen, en caso que no sea así lo descarta.

Cuando el servidor quiere enviar un paquete comprueba las redes asociadas a cada peer para decidir a quien enviarlo, y lo envía a la dirección IP más reciente que tenga registrada para ese peer.

En el cliente (con la configuración de ejemplo) se aceptan paquetes de cualquier origen si provienen del servidor. También se envian todos los paquetes, sea cual sea la dirección de destino hacia el servidor. Como hemos dicho, los paquetes se envían a la dirección registrada más recientemente para el servidor, si el cliente ya ha recibido algún paquete, o a la dirección inicial especificada en la configuración si aún no ha recibido nada desde el servidor.

Instalación de WireGuard

Instalación de WireGuard en Debian Server 10

Añadimos backports a nuestros orígenes de software e instalamos WireGuard.

1
2
3
echo 'deb http://ftp.debian.org/debian buster-backports main' | sudo tee /etc/apt/sources.list.d/buster-backports.list
apt update
apt install wireguard

WireGuard se ejecuta como un módulo del kernel

Genemos las claves en /etc/wireguard

1
2
umask 077
wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

Editamos el fichero sudo nano /etc/wireguard/wg0.conf con el contenido:

1
2
3
4
5
6
7
[Interface]
Address = 10.0.0.1/24
SaveConfig = true
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE

El fichero de configuración no tiene por que ser accesible para nadie (chmod 600 /etc/wireguard/wg0.conf), al fin y al cabo contiene la clave privada.

Este fichero de configuración correspondería a un “servidor” de VPN clásico para dar acceso a internet a los “clientes” de la VPN via NAT.

Los campos del fichero de configuración:

Address

La dirección IP privada (una dirección interna de la VPN) asignada al interfaz. Asignamos IP y máscara en un solo comando.

SaveConfig

Si vale truela configuración del interfaz se salva en el fichero de configuración cada vez que hacemos un shutdown del interfaz

ListenPort

El puerto UDP que escucha nuestro interfaz, los peer (clientes) tienen que dirigirse a ese puerto

PrivateKey

La clave de cifrado para este interfaz

PostUp

Un comando o un script que se tiene que ejecutar antes de levantar el interfaz. En este ejemplo estamos habilitando IP masquerading. Esto permitirá que el tráfico salga del servidor dando a los clientes de la VPN acceso a internet (habría que sustituir ens3 por el verdadero nombre del interfaz público del servidor)

PostDown

Un comando o script que se tiene que ejecutar antes de bajar el interface. En nuestro ejemplo se borran la configuración de IP masquerading de las IPtables. Es imprescindible sustituir ens3 por el puerto real del servidor.

Podemos arrancar ya el interfaz wg0 con el comando wg-quick up wg0 (en mi caso necesité reiniciar el servidor previamente, supongo que para completar la instalación del módulo wireguard en el kernel)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
root@vmi504132:~# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
root@vmi504132:~# wg show wg0
interface: wg0
  public key: rgbUE5KCfrUWT/7Vhh7NTdosCyP9LGx7M5vnJU9EAxw=
  private key: (hidden)
  listening port: 51820
root@vmi504132:~# ip addr show wg0
24: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 10.0.0.1/24 scope global wg0
       valid_lft forever preferred_lft forever

Hasta aquí hemos configurado nuestro nuevo interfaz wg0 pero necesitamos completar un par de cosas más antes de terminar:

Abrir el cortafuegos para WireGuard

Tenemos que permitir tráfico entrante UDP en el puerto 51820, en mi caso estoy usando UFW en el servidor:

1
2
3
4
ufw allow 51820/udp
ufw enable
ufw status numbered
ufw status verbose

Habilitar ip forwarding

Editamos el fichero /etc/sysctl.conf y descomentamos la linea:

net.ipv4.ip_forward=1

Recargamos el fichero ejecutando sysctl -p /etc/sysctl.conf

Evidentemente si queremos usar IPV6 tenemos que descomentar la linea correspondiente.

Configurar el arranque automático del interfaz cuando arranca el servidor

Para dejar configurado el arranque automático del interfaz wg0:

1
2
3
4
5
6
7
8
wg-quick down wg0              # Paramos el interfaz que hemos levantado manualmente

systemctl enable wg-quick@wg0  # Dejamos el servicio habilitado
systemctl status wg-quick@wg0  # Comprobamos el status del servicio


# systemctl start wg-quick@wg0 # Comandos para arrancar o parar el servicio
# systemctl stop wg-quick@wg0  # manualmente

Creación de una máquina virtual en Proxmox con Alpine Linux y WireGuard

Creamos la máquina virtual igual que creamos la de Docker

  1. Creamos la máquina virtual, basada en la ISO de Alpine Linux, asignamos un disco duro de 16Gb

  2. Ejecutamos alpine-setup asignamos IP estática y el tipo de instalación sys

  3. Instalamos qemu-guest-agent y configuramos adecuadamente

  4. Instalamos sudo y habilitamos el grupo wheel (editando /etc/sudoers)

  5. Añadimos usuario wgadmin (ya de paso lo metemos en wheel)

  6. Instalamos UFW

  7. Instalamos el WireGuard

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    apk add sudo
    addgroup wgadmin
    adduser -G wgadmin wgadmin
    adduser wgadmin wheel
    apk add ufw
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw enable
    apk add wireguard-tools
    

Ya tenemos todo listo para proceder con la configuración de WireGuard

1
2
cd /etc/wireguard
umask 077; wg genkey | tee privatekey | wg pubkey > publickey

Creamos el fichero de configuración wg0.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Interface]
Address = 10.0.0.2/24
ListenPort = 51820
PrivateKey = 2IHAoIOz+tWxRZ4oRlI1xHiD5FADVBTKOePnLSZNSkw=


[Peer]
PublicKey = d7TRyBXIX0I3sC/tPOqrIpNAGwEt2bW43QXKK4cEeGw=
AllowedIPs = 0.0.0.0/0
Endpoint = 207.180.207.206:51820
PersistentKeepalive = 20

Habilitamos el IP Forwarding:

Por comando: sysctl net.ipv4.ip_forward=1

O si lo queremos permanente añadimos la linea net.ipv4.ip_forward=1 en el fichero /etc/sysctl.conf

También tenemos que abrir el cortafuegos ufw allow 51820/udp

Ya podemos levantar el interfaz con wg-quick up wg0

Para que la cosa funcione tenemos que habilitar la redirección del puerto 51820/udp en nuestro router de internet para que apunte a la máquina virtual que hemos creado.

Llegamos por ping a la maquina siriowg pero no más allá (faltan rutas de vuelta hacia el servidor)