Actualizado (20/10/2020): La última versión de wg-access-server (0.3.0) cambia algunos aspectos de la configuración. He actualizado el artículo para que funcione con esa versión. Si ya lo tenías instalado y ha dejado de funcionar es posible que sea por haber actualizado la imagen del contenedor. Revisa la configuración según los ejemplos de este post.

WireGuard es un software para montar una VPN de manera muy sencilla. Además da un mayor rendimiento que las soluciones que veníamos utilizando hasta ahora. Por estas dos razones, rendimiento y facilidad de configuración, en poco tiempo ha sustituido a programas como OpenVPN a pesar de ser una solución aún joven.

WireGuard trabaja con parejas de claves pública y privada. Tanto el servidor como los clientes tienen sus propios pares de claves. Y es la gestión de estas claves la complicación más importante de WireGuard.

Recientemente han aparecido algunos gestores que permiten facilitar la gestión de clientes de WireGuard. Uno de ellos es WG Access Server. Su repositorio está en GitHub: https://github.com/place1/wg-access-server/

WG Access Server te permite tener un servidor de VPN WireGuard en menos de 15 minutos y configurar los clientes no podría ser más sencillo.

Vamos a ver cómo instalarlo y configurarlo en un contenedor Docker.

Clonar el repositorio y configurar el dockerfile

El primer paso que tenemos que dar es clonar el repositorio. Para ello, desde la línea de comandos ejecuta

~$ git clone https://github.com/Place1/wg-access-server.git

Si no tienes instalado el comando git tienes que instalarlo antes con el gestor de paquetes de tu distribución. Si usas una derivada de Debian puedes hacerlo con

$ sudo apt install git

Una vez clonado el repositorio lo siguiente que tenemos que hacer es configurar el fichero docker-compose.yml

$ cd wg-access-server
$ nano docker-compose.yml

Tienes que quitar el comentario a la línea - "./config.yaml:/config.yaml" # if you have a custom config file para que puedas personalizar la configuración. Déjalo como aparece a continuación

version: "3.7"
services:
  wg-access-server:
    # to build the docker image from the source
    # build:
    #   dockerfile: Dockerfile
    #   context: .
    image: place1/wg-access-server
    container_name: wg-access-server
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    volumes:
      - "wg-access-server-data:/data"
      - "./config.yaml:/config.yaml" # if you have a custom config file
    ports:
      - "8000:8000/tcp"
      - "51820:51820/udp"
    devices:
      - "/dev/net/tun:/dev/net/tun"
    environment:
      - "WG_ADMIN_USERNAME=admin"
      - "WG_ADMIN_PASSWORD=tu_contraseña_de_administrador"
      - "WG_WIREGUARD_PRIVATE_KEY="
      - "WG_STORAGE=sqlite3://data/db.sqlite3"
      - "WG_EXTERNAL_HOST=tu_dirección_pública o fqdn"
      - "WG_VPN_CIDR=10.1.1.0/24"
      - "WG_VPN_ALLOWED_IPS=0.0.0.0/0"
      - "WG_DNS_UPSTREAM=1.1.1.1"

# shared volumes with the host
volumes:
  wg-access-server-data:
    driver: local

Crear el fichero de configuración

Vamos a crear ahora un fichero para configurar el contenedor según nuestras necesidades. Créalo en el directorio conde has clonado el repositorio.

$ nano config.yaml

La configuración que yo he usado es ésta

# The application's log level.
# Can be debug, info, error
# Optional, defaults to info
loglevel: info
# Disable device metadata storage.
# Device metadata includes the last handshake time,
# total sent/received bytes count, their endpoint IP.
# This metadata is captured from wireguard itself.
# Disabling this flag will not stop wireguard from capturing
# this data.
# See stored data here: https://github.com/Place1/wg-access-server/blob/master/internal/storage/contracts.go#L14
# Optional, defaults to false.
disableMetadata: false
# The port that the web ui server (http) will listen on.
# Optional, defaults to 8000
port: 8000
adminPassword: "tu_contraseña_de_administrador"
# Directory that VPN devices (WireGuard peers)
# What type of storage do you want? inmemory (default), file:///some/directory, or postgresql, mysql, sqlite3
#storage: "memory://"
storage: "sqlite3:///data/db.sqlite3"
wireguard:
  # The network interface name for wireguard
  # Optional, defaults to wg0
  interfaceName: wg0
  # The WireGuard PrivateKey
  # You can generate this value using "$ wg genkey"
  # If this value is empty then the server will use an in-memory
  # generated key
  privateKey: ""
  # ExternalAddress is the address (without port) that clients use to connect to the wireguard interface
  # By default, this will be empty and the web ui
  # will use the current page's origin i.e. window.location.origin
  # Optional
  externalHost: "tu_dirección_pública o fqdn"
  # The WireGuard ListenPort
  # Optional, defaults to 51820
  port: 51820
vpn:
  # CIDR configures a network address space
  # that client (WireGuard peers) will be allocated
  # an IP address from.
  # Optional
  cidr: "10.1.1.0/24"
  # GatewayInterface will be used in iptable forwarding
  # rules that send VPN traffic from clients to this interface
  # Most use-cases will want this interface to have access
  # to the outside internet
  # If not configured then the server will select the default
  # network interface e.g. eth0
  # Optional
  allowedIPs:
    - "0.0.0.0/0"
dns:
  # Enable a DNS proxy for VPN clients.
  # Optional, Defaults to true
  enabled: true
  # upstream DNS servers.
  # that the server-side DNS proxy will forward requests to.
  # By default /etc/resolv.conf will be used to find upstream
  # DNS servers.
  # Optional
  upstream:
    - "1.1.1.1"
# Auth configures optional authentication backends
# to controll access to the web ui.
# Devices will be managed on a per-user basis if any
# auth backends are configured.
# If no authentication backends are configured then
# the server will not require any authentication.
# It's recommended to make use of basic authentication
# or use an upstream HTTP proxy that enforces authentication
# Optional
auth:
  # HTTP Basic Authentication
  basic:
    # Users is a list of htpasswd encoded username:password pairs
    # supports BCrypt, Sha, Ssha, Md5
    # You can create a user using "htpasswd -nB <username>"
    users: []

Fíjate en que aparezca la línea storage: "file:///data" ya que si no la configuración se guarda en memoria y se pierde tras cada reinicio del contenedor.

Puedes personalizar algunas cosas aquí.

  • privateKey (Clave privada del servidor): la rellenaremos después.
  • externalHost (Dirección externa): aquí debes poner tu dirección ip pública o el nombre (FQDN) que apunte a tu servidor desde Internet.
  • cidr (espacio de direcciones del servidor VPN): Puedes elegir cualquier dirección de subred privada como por ejemplo 192.168.100.0/24 o 10.1.1.0/24. IMPORTANTE --> Ten cuidado de que el rango no se repita en tu red local.
  • allowedIPs (direcciones permitidas para los clientes de la VPN): Si has personalizado el campo cidr, ese rango de direcciones debe aparecer también como direcciones permitidas.
  • upstream: es el servidor DNS que va a resolver las peticiones de los clientes de la VPN. Puedes usar cualquier DNS público (8.8.8.8 , 1.1.1.1) o el que utilices en tu red local. Si utilizas PiHole en tu red, pon su dirección aquí para disfrutar del mismo filtrado de publicidad en tu VPN.
  • users: aquí configuramos el usuario y contraseña que dan acceso al menu de administración. Lo veremos en el siguiente punto.

Configurar el usuario administrador

Para configurar un usuario hay que hacerlo añadiéndolo a la linea users: del fichero config.yamlcon el formato [usuario:contraseña]. Aunque la contraseña no se guarda en claro sino como un hash.

Para generar el hash escribe el siguiente comando eligiendo el nombre de usuario y la contraseñal que quieras.

$ htpasswd -nB <usuario>

Por ejemplo:

$ htpasswd -nB administrador
New password:
Re-type new password:
admin:$2y$05$Iv2LXcxmeKQPDQOUyTwbpe67bCfIow1GU5XcrOp3tdPeIXIcF20R6

Ésta última línea es la que tienes que escribir en el fichero config.yaml

    users: [admin:$2y$05$Iv2LXcxmeKQPDQOUyTwbpe67bCfIow1GU5XcrOp3tdPeIXIcF20R6]

Arrancar el contenedor

Ahora vamos a iniciar el contenedor de WG Access Server. Por supuesto, necesitas tener instalado Docker. En este artículo tienes una guía muy buena de cómo hacerlo. https://ugeek.github.io/blog/post/2019-02-24-instalar-servicios-o-aplicaciones-con-docker-para-tu-raspberry-o-cualquier-servidor-gnu-linux.html

También es importante que no tengas ningún interfaz de WireGuard instalado de forma nativa en tu servidor. Puedes comprobarlo con

$ sudo wg show all

Si hay algún interfaz listado tienes que desactivarlo con

$ sudo wg-quick down <nomber-interfaz>
$ sudo wg show all

Ahora no debería aparecer ninguno.

Para iniciar el contenedor simplemente tienes que ejecutar

$ sudo docker-compose up

Si todo va bien, al terminar de ejecutar este comando el contenedor ya estará funcionando.

Puedes comprobarlo abriendo el navegador apuntando a <ip del servidor>:8000

Si aparece lo anterior significa que el contenedor está funcionando.

Cierra docker-composecon CTRL-C e inicia de nuevo el contenedor con

$ sudo docker-compose start

No añadas ningún cliente aún. Primero tenemos que generar una clave privada para el servidor.

Crear la clave privada del servidor

Es necesario añadir una clave privada para el servidor en el fichero config.yaml. Si no lo haces, WG Access Server generará uno por ti, pero se guardará en memoria. Por tanto, si se reinicia el contenedor por cualquier razón esa clave se perderá y se generará una nueva, por lo que los clientes no podrán comunicarse con el servidor.

Para ello podríamos utilizar cualquier instancia de WireGuard, incluso si tienes instalado el paquete en tu distribución. Pero para no depender de ello vamos a utilizar la instancia del contenedor.

Para ello ejecuta

$ sudo docker exec -i -t wg-access-server /usr/bin/wg genkey

Como resultado tendrás algo parecido a esto:

2B3wzgQKr2ndVz2dcRjUJWl95r+RM2JPcGhs1ft4Sk8=

Esa cadena tienes que copiarla en el fichero config.yaml en la línea que comienza con privateKey:. Tiene que quedar así

privateKey: "2B3wzgQKr2ndVz2dcRjUJWl95r+RM2JPcGhs1ft4Sk8="

Ahora simplemente tienes que reiniciar el contenedor con

$ sudo docker-compose stop
$ sudo docker-compose start

Abrir el puerto en el firewall

Si tienes instalado un firewall en tu servidor no olvides añadir una regla para permitir la comunicación en el puerto 51820. Por ejemplo, si usas UFW puedes añadir la regla ejecutando este comando

$ sudo ufw allow 51820/udp comment WireGuard

Puedes comprobar el efecto del comando interrogando el estado de UFW

$ sudo ufw status

Obtendrás algo parecido a esto

Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
51820/udp                  ALLOW       Anywhere                   # Wireguard
22/tcp (v6)                ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)
51820/udp (v6)             ALLOW       Anywhere (v6)              # Wireguard

Abrir en puerto en el router

Adicionalmente, si tienes un router que hace NAT (si estás instalando WireGuard en un servidor en tu casa es seguro al 99.9% que ese es tu caso) tendrás que abrir el puerto 51820 y hacer que apunte a tu servidor.

El método para hacer esto depende del modelo de router que tengas y de tu operador de Internet. Consulta con éste si tienes dudas de cómo conseguirlo.

Si la instalación la estás haciendo en VPS en Internet es probable que éste tenga un dirección IP pública, por lo que no te hará falta este paso. Consulta con tu proveedor de cloud si es necesario.

Añadir clientes al servidor

Después de todo lo anterior queda lo más importante, aunque es lo más sencillo con diferencia. Gracias a WG Access Server es muy fácil crear las configuraciones para los clientes, sea cual sea el sistema operativo en el que trabajen.

Para ello abre el interfaz web del servicio e introduce el usuario y contraseña que configuraste.

Deberías ver algo como esto

De momento no hay ningún cliente configurado. Para añadirlo elige un nombre y pulsa sobre el botón etiquetado como ADD +

Después verás una ventana como ésta

Si tu cliente corre un sistema operativo de escritorio como MacOS, Linux o Windows descarga el fichero de configuración e impórtalo en el cliente de WireGuard.

En cambio, si se trata de un dispositivo móvil tendrás que cambiar a la segunda pestaña para ver el código QR que necesitas para importar el perfil desde la app móvil de WireGuard.

Tras cerrar esta ventana verás el estado del nuevo cliente en el interfaz des servidor. Si tu cliente se conecta correctamente lo verás allí

¿Y qué hago si no funciona?

Si después de todo no consigues comunicar puedes hacer varias cosas para comprobar dónde está el problema.

Lo primero es comprobar que llegan los paquetes desde el cliente al servidor. Para ello puedes intentar capturar los mensajes que llegan al puerto de WireGuard, el 51820.

Comprueba cuál es el nombre del interfaz conectado a Internet. Puedes hacerlo con el comando

$ ifconfig

El nombre será algo parecido a eno0 o eth0 si se trata de una conexión cableada o wlan0 si es una conexión inalámbrica, aunque dependiendo de tu configuración puede tener otro nombre. En mi caso es eno0

Con la ayuda del comando tcpdump captura la actividad del puerto 51820.

$ sudo tcpdump -i eno0 port 51820

Inicia una conexión desde un cliente y observa qué sucede en la línea de comando. Si observas actividad es muy probable que los datos se estén encaminando correctamente y el problema sea otro.

Desde un cliente, prueba a hacer un ping al servidor de VPN. Su dirección será la primera del rango que hayas definido en el fichero config.yaml. Por ejemplo 10.1.1.1.

$ ping 10.1.1.1

Si hay respuesta tu VPN está bien configurada.

El siguiente paso es configurar que desde la VPN llegas a tu red local. Haz un ping al router de tu proveedor de Internet. La dirección podría ser algo parecido a 192.168.1.1

$ ping 192.168.1.1

Si hay respuesta el encaminamiento de paquetes funciona.

Por último prueba a hacer a un ping a una dirección de Internet. Por ejemplo al DNS de google: 8.8.8.8.

$ ping 8.8.8.8

Si la respuesta es correcta toda va bien.

También puedes comprobar que la resolución de nombres está funcionando haciendo un ping a un servidor de Internet. Por ejemplo a ubuntu.com.

$ ping ubuntu.com