Authelia: Tu propio proveedor OAuth2

Seguro que, en algún momento, todos hemos hecho uso de este tipo de servicios OAuth2, como por ejemplo a la hora de registrarnos en alguna página a través de algún botón tipo "Regístrate con tu cuenta de Google" o "Inicia sesión con tu cuenta de Facebook". Incluso mediante aplicaciones como Gitea, como comentaba en una entrada anterior.

Hoy vengo a hablaros de Authelia. Como dice en su página web, se puede describir como: Un servidor y portal de autenticación y autorización de código abierto que cumple la función de gestión de identidad y acceso (IAM) de la seguridad de la información, al proporcionar autenticación multifactor e inicio de sesión único (SSO) para sus aplicaciones a través de un portal web.

Este servicio me está permitiendo enlazar todas las cuentas que tengo en mi ya extensa nube personal, facilitando el inicio de sesión en ellas y gestionando de manera más efectiva el acceso. Actualmente, lo tengo implementado en algunos de mis servicios y poco a poco lo voy haciendo extensible a todos aquellos que lo soportan de alguna u otra forma.

He comenzado por implementarlo en aquellos servicios que están expuestos al exterior y hago uso de los túneles de Cloudflare para tener una capa extra de seguridad, pues tan sólo se puede acceder a mi instancia de SSO bajo determinadas circunstancias.

Instalación

Comenzaremos viendo, como ya es habitual, el fichero de Docker Compose que usaremos para levantar nuestra instancia. Éste tendrá una forma similar a la siguiente:

---
version: '3.3'

services:
  authelia:
    image: authelia/authelia
    container_name: authelia
    user: 1001:1001 # Si usamos bind mounts, no olvidar poner el UID:GID que corresponda a nuestro usuario
    volumes:
      - ./config:/config
    networks:
      - public
      - data
      - redis
    expose:
      - 9091
    restart: always
    env_file:
      - .env
    environment:
      - TZ=Europe/Madrid
      - AUTHELIA_NOTIFIER_SMTP_PASSWORD=${AUTH_SMTP_PASSWORD}
      - AUTHELIA_STORAGE_POSTGRES_PASSWORD=${DB_PASSWORD}
      - AUTHELIA_JWT_SECRET=${JWT_SECRET}
      - AUTHELIA_SESSION_SECRET=${SESSION_SECRET}
      - AUTHELIA_STORAGE_ENCRYPTION_KEY=${STORAGE_ENCRYPTION_KEY}
      - AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET=${OIDC_HMAC_SECRET}
      - AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE=/config/keys/private.pem
    labels:
      - traefik.enable=true
      - "traefik.http.routers.authelia.entrypoints=websecure"
      - "traefik.http.routers.authelia.rule=Host(`sso.example.org`)"
      - "traefik.http.routers.authelia.tls=true"
      - "traefik.http.routers.authelia.tls.certresolver=certlocal"

networks:
  public:
    external: true
  data:
    external: true
  redis:
    external: true
...
Fichero docker-compose.yml de Authelia

Como se puede ver, es bastante sencillo. En este caso lo he puesto con las etiquetas para Traefik, ya que internamente hago uso de dicho servicio para acceder en mi intranet. Además, se conecta a la red que comparte con un servicio de Redis, el cual permite que, cuando reiniciemos Authelia, se mantengan las sesiones abiertas (de lo contrario, con cada reinicio cerraríamos las sesiones y tendríamos que volver a iniciar sesión en Authelia para mantener nuestros servicios con las sesiones activas).

Si no tenemos o no queremos usar Redis, se pueden omitir todas las configuraciones y variables de entorno del resto del tutorial que hacen referencia a este servicio.

En cuanto al fichero .env, lo he usado para definir algunas variables de entorno que serán pasadas al campo environment y tiene esta forma:

AUTH_SMTP_PASSWORD=
DB_PASSWORD=
JWT_SECRET=
SESSION_SECRET=
STORAGE_ENCRYPTION_KEY=
OIDC_HMAC_SECRET=
Fichero .env con los secretos que usaremos

Lo he hecho así porque, de esta forma, me es más sencillo hacer una copia en Pass y poder desplegarlo en otro servidor rápidamente, de ser necesario (recordad mantener ese fichero con permisos 0600 para mayor seguridad).

Veamos esos secretos detenidamente:

  • AUTH_SMTP_PASSWORD: Este secreto contiene el apikey de mi servidor de correo SMTP, el cual uso para enviar correos electrónicos desde distintos servicios (en este caso, para validar los token de seguridad que uso en Authelia).
  • DB_PASSWORD: Contiene la contraseña de la base de datos donde Authelia persiste la información.
  • JWT_SECRET: Define el secreto utilizado para crear tokens JWT aprovechados por el proceso de verificación de identidad.
  • SESSION_SECRET: La clave secreta utilizada para cifrar los datos de la sesión en Redis.
  • STORAGE_ENCRYPTION_KEY: Se trata de la clave de encriptación para la información a persistir en base de datos.
  • OIDC_HMAC_SECRET: Se usa para firmar los token JWT.

Tanto JWT_SECRET, como SESSION_SECRET, STORAGE_ENCRYPTION_KEY o OIDC_HMAC_SECRET son cadenas de caracteres que deben tener una longitud recomendada mínima de 64 caracteres. Para obtener algo así, basta con ejecutar el comando openssl rand -hex 64, cuya salida nos dará algo parecido a:

$ openssl rand -hex 64
d597b8a30f2193b5c8f02ac07ceaaf191f0824c58d65b49da48afcaa52494e62bff11f26fb71efc51bc7048fee5510d9fd3cbf1876a976f8666c83d7961a3231
Ejemplo de generación de una clave de 64 caracteres con openssl

Por último, crearemos la carpeta config y, dentro de ella, las subcarpetas assets, keys y los ficheros users.yml y configuration.yml, cuyo contenido veremos en el apartado siguiente. Por ahora, nos quedaremos con que, dentro de assets, podemos meter nuestro logo (para que en la web aparezca) si añadimos los ficheros favicon.ico y logo.png con el icono y logo que queramos usar.

Configuración

Una vez generado todo lo que necesitamos para la instalación, pasaremos a la configuración del servicio de Authelia propiamente dicho, el cual se basa en ficheros YML (no dispone de interfaz gráfica).

Usuarios

Para configurar los usuarios, editaremos el fichero users.yml que creamos previamente en la carpeta config. La función de dicho fichero será definir los usuarios con los cuales podremos iniciar sesión en Authelia y, por lo tanto, en las aplicaciones que la usen como SSO. Si bien se puede enlazar con un LDAP, para este tutorial lo haremos por fichero, pues es más sencillo, y dejaremos ese caso para otra entrada. Su contenido será algo similar a esto:

---
###############################################################
#                         Users Database                      #
###############################################################

# This file can be used if you do not have an LDAP set up.

# List of users
users:
  pepito:
    disabled: false
    displayname: "Pepito"
    password: "$pbkdf2-sha512$310000$CHs986Ff1aRTG6I0ewbgCA$OdYY12jXm2RHX4tjTdPilcf1O6QFxobY0ACsoHrwru1L9Bopvag0jE1eeEDbq6GXl9Ja3unvsu/G03Wg5brKZg"
    email: pepito@example.org
    groups:
      - admins
      - dev
...
Fichero users.yml

El fichero se explica por sí solo: Anidado en users tendremos el nombre de usuario que queremos dar de alta (podemos agregar tantos como queramos, respetando el formato YML) y, dentro de éste, podremos habilitarlo o deshabilitarlo, especificar el nombre que se mostrará en la web, su contraseña, correo electrónico (para recibir los correos de Authelia que nos permiten validar token) y los grupos a los que pertenece el usuario, que en este caso serán admins y dev. Estos grupos deberán estar asociados al usuario administrador de la instancia.

Respecto a la contraseña, lo ideal es poner un hash de la misma, en lugar de en texto plano directamente. Authelia viene con un generador de encriptación, que podemos usar de la siguiente forma:

$ docker run authelia/authelia authelia crypto hash generate argon2 --password MiSuperPassword
Digest: $argon2id$v=19$m=65536,t=3,p=4$xPR/labRLu9Q+zQb5IiR9A$v0+aIht30m0asOe8ECcGqBtkSeCKG+ghg46xU9zioQQ
Hash de la contraseña en formato Argon2

Mediante dicho comando, correremos la imagen de Authelia para que nos genere un hash de la contraseña MiSuperPassword, el cual será el que pegaremos en el campo password de nuestro usuario pepito.

Configuración de las claves

Estas claves son necesarias para que Authelia opere como proveedor OpenID Connect. En este caso, serán 2 claves RSA que estarán bajo el nombre de public.pem y private.pem, haciendo referencia a las claves pública y privada, respectivamente.

Configuración de Authelia

Una vez tenemos el o los usuarios "registrados", pasamos a editar el contenido dle fichero configuration.yml, el cual pasará a tener el siguiente formato:

---
###############################################################
#                   Authelia configuration                    #
###############################################################

default_redirection_url: https://auth.example.org
default_2fa_method: webauthn

theme: grey

server:
  host: 0.0.0.0
  port: 9091
  asset_path: /config/assets

log:
  level: warn

totp:
  issuer: example.org

authentication_backend:
  file:
    path: /config/users.yml

access_control:
  default_policy: deny
  rules:
    - domain: "*.example.org"
      policy: bypass
      networks:
        - '10.0.0.0/8'
    - domain: "*.example.org"
      policy: two_factor

session:
  name: authelia_session
  domain: example.org
  expiration: 3600  # 1 hour
  inactivity: 300  # 5 minutes
  redis:
    host: redis
    port: 6379

regulation:
  max_retries: 2
  find_time: 120
  ban_time: 300

storage:
  postgres:
    host: postgres
    port: 5432
    database: authelia_db
    schema: public
    username: authelia_user

notifier:
  smtp:
    host: smtp.gmail.com
    port: 587
    timeout: 10s
    username: mi_smtp_username
    sender: "Authelia <no-reply@example.org>"
    identifier: example.org
    subject: "[Authelia] {title}"
    startup_check_address: pepito@example.org
    disable_require_tls: false
    disable_starttls: false
    disable_html_emails: false

identity_providers:
  oidc:
    clients:
      - id: portainer
        description: Portainer
        public: false
        secret: ''
        authorization_policy: two_factor
        redirect_uris: https://portainer.example.org
        scopes:
          - openid
          - profile
          - groups
          - email
        consent_mode: implicit
        pre_configured_consent_duration: 1M

...
Fichero de configuración de Authelia

Es un poco largo, así que lo analizaremos por secciones:

La variable default_redirection_url contendrá la URL donde queremos exponer nuestro servicio de Authelia, mientras que default_2fa_method le indica cuál será el sistema de autenticación por defecto (webauthn para usar token de seguridad como los de Yubikey, o totp para que, por defecto, se nos pida un código generado por aplicaciones como Aegis o Google Authenticator).

Mediante la variable theme, podemos definir el tema de la web app, ésta puede ser light, dark o grey, que, para mi gusto, es un tema oscuro más bonito que el dark.

Bajo server, definimos los host desde los que se puede acceder a Authelia. Al estar en un contenedor Docker, lo suyo es especificar que cualquiera puede alcanzarlo, mediante la IP 0.0.0.0. El puerto expuesto será el 9091 y la ruta para los assets (el logo y el favicon) se encuentra en este caso bajo /config/assets.

Podemos definir el nivel de log a través de log.level.

Bajo totp.issuer especificamos el texto que queremos que aparezca cuando damos de alta el servicio en una aplicación 2FA, yo aquí prefiero poner el host y así veo, de un plumazo, qué servidor estoy usando.

Con authentication_backend especificamos dónde se encuentran los usuarios autorizados. Como comentaba anteriormente, se puede conectar Authelia a un servidor LDAP, pero para este tutorial los hemos registrado mediante el fichero users.yml, lo cual indicamos mediante el identificador file.path y la ruta al mismo. En caso de usar este sistema, los usuarios no podrán resetear sus contraseñas, con lo que seremos los encargados de mantenerlas.

Mediante access_control podemos definir reglas que nos permiten simplificar el inicio de sesión bajo determinadas condiciones. En este caso, hemos definido la política por defecto default_policy a deny, de manera que, de base, denegamos el inicio de sesión, y sólo lo autorizamos para los casos en los que los servicios vengan de cualquier subdominio de example.org. Además, si accedemos desde una IP concreta o una subred (en este caso una VPN), no solicitaremos ningún tipo de autenticación, mientras que si no es así, pediremos tanto el usuario y contraseña, como un 2FA. Esto es útil para casos como el que tengo en casa, donde en mi LAN no necesito andar registrándome pero si me conecto por una VPN sí quiero forzar que eso ocurra (ya que no tengo expuesto el acceso desde el exterior, con lo que en mi caso particular las reglas son un poco distintas).

En session definimos datos de la sesión de Authelia, como el tiempo máximo de vida antes de que caduque o el tiempo de inactivación máxima que tendrá. El name nos permite darle un nombre a esa cookie y domain nos permite separar dicha sesión por dominios. En caso de usar un Redis, especificaremos el host y el puerto en el que se encuentra.

El campo regulation actúa como un Fail2Ban, de forma que nos permite un máximo número de intentos a la hora de iniciar sesión, el tiempo máximo que nos dará para cada uno, y el tiempo de baneo del origen en caso de superar dichos intentos. Útil para evitar ataques de fuerza bruta.

Con storage definimos dónde almacenará Authelia la información. Aquí podemos hacer uso de una base de datos PostgreSQL, un MariaDB... o directamente un SQLite. En este último caso, será tan sencillo como indicar el fichero .db que la contendrá: storage.local.path:/config/db.sqlite3.

Mediante notifier podremos indicar la configuración SMTP para que Authelia nos envíe los correos necesarios para dar de alta dispositivos Web-Authn, resetear contraseñas (en los casos que se permita), etc. No voy a entrar en detalle en los campos, ya que son bastante descriptivos, pero sí quiero comentar una cosa: Si no queremos configurar esta parte (bien porque estamos probando, bien porque no lo vamos a necesitar mucho), Authelia nos provee de un sistema de notificación por fichero. Si ponemos la siguiente configuración:

notifier:
  disable_startup_check: false
  filesystem:
    filename: /config/notification.txt
Configuración para usar el notifier por fichero de texto

Authelia escribirá en un fichero notification.txt lo mismo que nos mandaría al correo, con lo que bastaría con acceder a dicho fichero y mostrar su contenido. Un ejemplo de ello sería:

Date: 2023-07-01 19:05:00.248652808 +0200 CEST m=+41.721271860
Recipient: { pepito@example.org}
Subject: Register your key
Body: This email has been sent to you in order to validate your identity.

If you did not initiate the process your credentials might have been compromised and you should reset your password and contact an administrator.

To setup your 2FA please visit the following URL: https://auth.example.org/webauthn/register?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

This email was generated by a user with the IP 10.4.0.2.

Please contact an administrator if you did not initiate this process.
Ejemplo de fichero notification.txt

A través de identity_providers.oidc definiremos los clientes que podrán usar el servicio. Estos clientes los podemos ver listados en https://www.authelia.com/integration/openid-connect/, junto con la configuración que cada uno requiere. Esta lista es extensa, y poco a poco va creciendo, pero cualquier servicio que soporte OAuth2, debería ser posible configurarlo, tomando cualquiera de los ejemplos de esa lista como plantilla. Para este tutorial, podemos borrar por completo esta sección, ya que nos centraremos sólo en el servicio de Authelia en sí.

Accediendo al servicio

Una vez tenemos configurado todo, ya podemos levantar nuestro servicio de Authelia mediante el ya confiable docker compose up -d y acceder a la URL donde lo hayamos expuesto (según el ejemplo: https://auth.example.org). Esto nos mostrará una ventana similar a:

Pantalla de inicio de sesión de Authelia

En ella, sólo tendremos que iniciar sesión con nuestro usuario y contraseña, definidos en el fichero users.yml y nos encontraremos con lo siguiente:

Pantalla de registro de un 2FA

Como podéis ver, nuestro usuario Pepito necesita definir un dispositivo 2FA la primera vez que inicia sesión (y más concretamente, una Security Key, ya que indicamos que éste sería el sistema por defecto en la configuración), con lo que procedemos a registrarlo mediante el enlace Registrar Dispositivo. En cuanto hacemos clic, se nos enviará un correo con un enlace al que debemos acceder y que nos permitirá registrar nuestro dispositivo (recordad que, si habéis optado por la opción de file en lugar de un proveedor de correo, deberéis fijaros en el contenido del fichero config/notification.txt o el que hayáis puesto en la configuración):

Pantalla de espera mientras validamos nuestra llave de seguridad

Una vez lo hagamos, veremos que hemos logrado iniciar sesión gracias al check verde:

Pantalla de confirmación de sesión iniciada

Con esto, ya tendremos nuestra sesión de Authelia iniciada. Cualquier aplicación que conectemos con ella, si está en este estado, automáticamente nos redirigirá de nuevo a la aplicación, pero ya con nuestro inicio de sesión correcto.

Al final ha quedado un poco extenso el tutorial, pero nos vale para tener nuestro propio proveedor de OAuth2 listo para usar en nuestras aplicaciones. En futuras entradas veremos cómo configurarlo con un servidor LDAP para gestionar de manera más sencilla los usuarios y grupos y algún ejemplo de integración.

Si te ha gustado la entrada, o te ha sido útil y quieres ayudarme a pagar los gastos que conlleva el servidor y mantener así el blog libre completamente de anuncios, puedes hacer una donación en Bitcoin en la siguiente dirección: