Bitwarden: Tu gestor de contraseñas

El proyecto que se menciona en este artículo ha pasado a denominarse Vaultwarden. Las instrucciones siguen siendo las mismas, sólo se trata de un rebranding.

Como muchos de vosotros, tras años usando Internet, he terminado acumulando incontables cuentas en innumerables servicios y delegando en el navegador, en muchas ocasiones, el recordar las contraseñas de cada cuenta y, además, reutilizar muchas de ellas. Como sabéis, eso es un grave error. Llevaba tiempo detrás de un gestor de contraseñas que fuera de código abierto, que pudiera gestionar en mi propio servidor y que fuera compatible con el mayor número de dispositivos.

Finalmente encontré lo que buscaba. Y se llama Bitwarden. Es un gestor de contraseñas de código abierto que reúne todo lo que buscaba y que, además, para cuentas personales, es gratuito (con opciones premium pero bastante económicas).

Tras investigarlo un poco, descubrí que, además, puede ser instalado mediante Docker y de manera bastante sencilla. Pero me encontré con un problema... ¡Desplegar la infraestructura que requiere y todos sus módulos freía mi pequeño y modesto VPS! Casi 2GB de memoria que requería y un espacio en disco un poco desmesurado, causado principalmente porque se sostiene bajo una base de datos de Microsoft (MS-SQL). Lo cual, para mi uso personal, era como matar moscas a cañonazos.

Desilusionado, me puse a buscar y di con un fork no oficial de Bitwarden (recordemos que es de código abierto), denominado ~~Bitwarden RS~~ Vaultwarden. Se trata de un proyecto iniciado por el español Daniel García y cuya premisa es un Bitwarden más ligero y menos exigente con los recursos, y de paso con alguna mejora premium que en este caso es completamente gratuita (como las organizaciones). Este proyecto se sustenta, por defecto, sobre una base de datos SQLite, lo cual hace que todo el proyecto, una vez desplegado, apenas consuma recursos (¡ideal para mi pequeño monstruo!).

Así pues, una vez vuelta la ilusión al proyecto, me decidí a montar todo para, finalmente, desligarme de los gestores de contraseñas de los navegadores y poseer, finalmente, el mío propio. Y cómo no, os lo voy a contar paso a paso.

Docker-compose al rescate

La forma de empezar, cómo no, será definiendo un fichero docker-compose.yml que contenga el servicio que queremos desplegar y la configuración necesaria para ello. Como siempre, os dejo aquí el fichero y después lo voy desgranando poco a poco:

version: '3.2'

services:
  vaultwarden:
    image: vaultwarden/server
    container_name: "vaultwarden"
    restart: always
    volumes:
      - ./bwdata:/data
    networks:
      - traefik
    environment:
      LOG_FILE: '/data/vaultwarden.log'
      DOMAIN: 'https://vaultwarden.midomin.io'
      SMTP_HOST: 'smtp.sendgrid.net'
      SMTP_FROM: 'noreply@midomin.io'
      SMTP_PORT: 587
      SMTP_SSL: 'false'
      SMTP_USERNAME: 'apikey'
      SMTP_PASSWORD: '<SEND_GRID_API_KEY>'
      SIGNUPS_ALLOWED: 'false'
      INVITATIONS_ALLOWED: 'true'
      LOG_LEVEL: 'warn'
      EXTENDED_LOGGING: 'true'
      ADMIN_TOKEN: 'passwordUltraSecreta'
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik
      - traefik.http.routers.bitwarden-ui-https.rule=Host(`vaultwarden.midomin.io`)
      - traefik.http.routers.bitwarden-ui-https.entrypoints=websecure
      - traefik.http.routers.bitwarden-ui-https.tls=true
      - traefik.http.routers.bitwarden-ui-https.service=bitwarden-ui
      - traefik.http.routers.bitwarden-ui-http.rule=Host(`vaultwarden.midomin.io`)
      - traefik.http.routers.bitwarden-ui-http.entrypoints=web
      - traefik.http.routers.bitwarden-ui-http.middlewares=redirect-to-https
      - traefik.http.routers.bitwarden-ui-http.service=bitwarden-ui
      - traefik.http.services.bitwarden-ui.loadbalancer.server.port=80
      - traefik.http.routers.bitwarden-websocket-https.rule=Host(`vaultwarden.midomin.io`) && Path(`/notifications/hub`)
      - traefik.http.routers.bitwarden-websocket-https.entrypoints=websecure
      - traefik.http.routers.bitwarden-websocket-https.tls=true
      - traefik.http.routers.bitwarden-websocket-https.service=bitwarden-websocket
      - traefik.http.routers.bitwarden-websocket-http.rule=Host(`vaultwarden.midomin.io`) && Path(`/notifications/hub`)
      - traefik.http.routers.bitwarden-websocket-http.entrypoints=web
      - traefik.http.routers.bitwarden-websocket-http.middlewares=redirect-to-https
      - traefik.http.routers.bitwarden-websocket-http.service=bitwarden-websocket
      - traefik.http.services.bitwarden-websocket.loadbalancer.server.port=3012

networks:
  traefik:
    external: true
FIchero docker-compose.yml para desplegar Vaultwarden

Como podéis ver, las primeras líneas nos definen la imagen a utilizar, el nombre que le daremos, si debe reiniciarse, el volumen que montaremos y la red de Traefik a la que estará conectado, para poder acceder a él desde nuestro servidor.

Posteriormente, entramos en la configuración de las variables de entorno que definirán el comportamiento de nuestro contenedor, así como el servidor de correo que usaremos para enviar los correos y el acceso al panel de administración.

Después, las etiquetas para Traefik que definirán los endpoint, tanto HTTP como HTTPS, que deberemos redireccionar para mantener la sincronización y el acceso al panel web (por si no podemos acceder a nuestro gestor desde ninguna aplicación o estamos en un dispositivo que no es nuestro).

Finalmente, definimos las redes que vamos a usar, en nuestro caso la de Traefik únicamente.

Configuración de Vaultwarden

Vaultwarden tiene 2 maneras de configurarse: Mediante las variables de entorno o mediante el fichero config.json. Es recomendable no mezclar ambas opciones, ya que la segunda sobreescribirá a la primera. Dicho fichero de configuración se genera la primera vez que arrancamos el contenedor a partir de la configuración de las variables de entorno, por lo que recomiendo definirla ahí y, una vez arrancado, eliminar del docker-compose.yml aquella configuración que no necesitemos ya o que sea más sensible (como los token). Para una configuración básica pero completa, he utilizado la siguiente (puedes consultar todas las posibles configuraciones en la wiki del proyecto).

  • LOG_FILE: Define el nombre del fichero de log que usará el servicio. La ruta hace referencia dentro del contenedor.
  • LOG_LEVEL: Definimos el nivel de log, desde trace a error, pudiendo deshabilitar el log poniendo el nivel a off.
  • EXTENDED_LOGGING: Combinado con el anterior, nos permite definir el nivel de log.
  • DOMAIN: Lo usaremos para poner el dominio de nuestro servicio, y será necesario tanto para el envío de correos como para el acceso web. En el ejemplo, exponemos el servicio en https://bitwarden.midomin.io
  • SIGNUPS_ALLOWED: Con esta variable, permitimos o denegamos que cualquiera se pueda dar de alta en nuestro servicio. Deberá estar a true la primera vez, para que, por lo menos, podamos crear nuestra cuenta, y después lo cambiaremos a false ¡o cualquiera podría registrarse en nuestro servicio y utilizarlo!
  • INVITATIONS_ALLOWED: Si esta variable está a true, permitiremos que, aquellas cuentas que hayan recibido un enlace de invitación de nuestro servicio, se puedan registrar aunque tengamos prohibido el registro con la variable SIGNUPS_ALLOWED=false.
  • ADMIN_TOKEN: Si esta variable está definida, se nos habilitará el endpoint /admin, el cual nos permitirá gestionar nuestro servicio, usuarios, organizaciones, etc. El valor que pongamos será el que nos pedirá para acceder a dicha administración, por lo que, si deseamos tener habilitado el acceso, recomiendo poner un valor generado complicado de conocer, haciendo uso, por ejemplo, del comando openssl rand -base64 48 en cualquier terminal.
PRECAUCIÓN: Mantén el token especificado en ADMIN_TOKEN a salvo y a buen recaudo, es ni más ni menos que la contraseña de acceso a la zona de administración de tu servidor de contraseñas.

Dicha configuración, tras el arranque inicial del contenedor, generará dentro del volumen un fichero denominado config.json que, en cada reinicio, sobreescribirá cualquier valor especificado en las variables de entorno del docker-compose.yml. Con lo que, por ejemplo, el ADMIN_TOKEN una vez iniciado el contendor, podremos borrar de nuestro YAML y cambiar los permisos del recién creado config.json a 600.

Configuración del servidor de correo

Con nuestro gestor de contraseñas ya creado, podremos dar de alta un usuario con un correo electrónico que requerirá que se confirme para poder acceder a todas las características (habilitar 2FA, añadir a organizaciones, etc). Además, si queremos enviar invitaciones a nuestro gestor de manera individual, sólo se podrán hacer por correo electrónico, por lo que es imprescindible disponer de uno. Para ello, necesitamos hacer uso de un servidor de correo que, o bien gestionemos nosotros, o bien deleguemos en un servicio externo. Debido a las campañas agresivas de SPAM y demás, muchas veces la primera opción puede ser un dolor de cabeza para mantener una buena reputación de nuestro servidor y que no se nos identifique como SPAM, por lo que recomiendo lo segundo.

Hay multitud de opciones que nos permiten un uso básico y gratuito para lo que lo vamos a necesitar. Mailjet, SendGrid, una cuenta dedicada para esto en Gmail... En mi caso, he optado por SendGrid, ya que nos permite enviar un máximo de 100 correos al día (más que de sobra para nuestro objetivo) de manera completamente gratuita y, además, nos permite personalizar bastante bien los permisos del token que usaremos.

Para ello, nos crearemos una cuenta en SendGrid. Accedemos a sendgrid.com y nos registramos. Una vez dentro, deberemos acceder, en el menú de Settings, en API Keys y pulsar sobre Create API Key para crear un token nuevo. Os aconsejo que lo seleccionéis como Restricted Access y le concedáis sólo el permiso de Mail Send, pues es para lo único que lo vamos a necesitar, y de esa manera evitamos exponer nuestra cuenta de manera innecesaria. Al pulsar sobre Create and View veremos que se nos ha creado el nuevo token y que nos lo muestra con el aviso de que no lo volverá a hacer más, por lo que lo copiamos (al pulsar sobre él se copiará en el portapapeles) y lo apuntamos de manera temporal en un editor de textos o donde prefiramos.

API Key creada por el servicio de SendGrid

Volviendo a nuestro fichero docker-compose.yml podemos ya rellenar los campos referentes al servidor de correo, de la manera siguiente:

  • SMTP_HOST: Pondremos el host que nos especifique el servidor de correo. Para el caso de SendGrid, éste será smtp.sendgrid.net.
  • SMTP_FROM: Aquí especificaremos la dirección que figurará como remitente en los correos que se nos envíen. Algo como no-reply@midomin.io, por ejemplo. Es importante que sea relativa a vuestro dominio donde vayáis a tener funcionando Bitwarden, no os la inventéis.
  • SMTP_PORT: El puerto que diga el servidor de correo. De nuevo, tomando como ejemplo SendGrid, se tratará del 587. Suele ser ése o el 25 y, si vamos a usar SSL o TLS para los correos, el 465.
  • SMTP_SSL: Indicamos si vamos a usar SSL para enviar los correos. Como de los certificados en nuestro caso se va a ocupar Traefik, lo pondremos a false.
  • SMTP_USERNAME: El usuario del servidor de correo. Para SendGrid es único, y su valor deberá ser apikey.
  • SMTP_PASSWORD: La contraseña del servidor de correo. En el caso de SendGrid, será el token que se nos generó y que tenemos apuntado por algún lado del paso anterior, cuando creamos dicho token (unos párrafos más arriba). Lo pondremos aquí y, una vez que arranquemos el servicio, ya estará en el fichero config.json y podremos editarlo desde el path /admin, por lo que lo podremos borrar tras la primera ejecución del contenedor.

Respecto a las etiquetas relacionadas con Traefik, todas ellas ya las he comentado en anteriores artículos y, por no hacer más extensa esta entrada, podéis revisar dichos artículos, en especial en ésta y en ésta otra. Pero sí comentaré los siguientes puntos:

  • Se han conectado los endpoints HTTP y HTTPS, y se han añadido los correspondientes redireccionamientos del primero al segundo.
  • Se han creado 2 enrutadores: uno para la UI web (que el contenedor expone en el puerto 80) y otro para el websocket de actualización, el cual está en el puerto 3012.
  • El websocket se usará para mantener sincronizados los clientes y la web con el servicio en sí, por lo que se añade el endpoint /notifications/hub y se enlaza con dicho websocket.

¡Y listo! Guardamos, reiniciamos nuestro contenedor... Y ya tendremos nuestro propio gestor de contraseñas funcionando. Si accedemos a vaultwarden.midomin.io, veremos la pantalla de login y podremos crear nuestra cuenta o entrar directamente. Recordad que, aunque tengamos deshabilitado el registro de nuevos usuarios, el botón de Crear cuenta, por ahora al menos, seguirá estando ahí y será sólo cuando intentemos crear una, que veamos un mensaje de error informando de que esa opción está deshabilitada.

Pantalla de login de nuestro servicio funcionando

En posteriores entradas puede que haga un tutorial más a nivel de usuario de cómo utilizarlo y de la sección de administración, pero para lo básico es bastante sencillo e intuitivo 😊.

¡Y ya estaría! Como siempre, si tenéis alguna duda, sugerencia o cualquier comentario constructivo, será bien recibido en la sección de comentarios.

¡Hasta la próxima entrada!