# Optimiza la seguridad: Gestión de accesos temporales con n8n y Vault

Hice algo que compartí con la comunidad y me pidieron "plasmarlo en papel". Lo que quiero crear es un proceso de acceso a la base de datos de manera que tanto el usuario como el administrador se sientan satisfechos y libres de procesos complicados. Voy a hacer una breve explicación del procedimiento. Cuando un usuario inicia sesión en **Authentik**, que actúa como portal y está conectado a **OpenLDAP**, puede solicitar acceso temporal a una base de datos.  
Esa solicitud se envía automáticamente a los miembros del grupo **OU=SecOps** para su aprobación.  
Una vez aprobada, **n8n** recibe el evento y solicita a **HashiCorp Vault** la generación de **credenciales dinámicas** con un **tiempo de vida limitado (TTL)**.  
Vault crea de forma segura el usuario en la base de datos y devuelve las credenciales a **n8n**, que las entrega al solicitante.  
Finalmente, todo el proceso queda registrado en un sistema de **auditoría**, asegurando la **trazabilidad completa** y la **revocación automática** de los accesos una vez expirado el TTL.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761681827025/28b05ae1-39f4-4d2d-ab10-a692efc01de3.png align="center")

### LDAP

A continuación, echemos un vistazo a la estructura LDAP que queremos construir. El árbol muestra la estructura jerárquica del directorio, comenzando por el DN base dc=ironbox,dc=local. Debajo de esta base hay tres departamentos (ou=IT, ou=DevOps y ou=SecOps) y varios usuarios. Esto se definirá en el siguiente paso utilizando un archivo LDIF.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761328597172/02ba476b-b03b-4774-904e-51872c534b37.png align="center")

Aca el `.ldif`

```xml
# admin user
dn: cn=admin,dc=ironbox,dc=local
changetype: add
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
userPassword: adminpassword
description: LDAP Administrator

# organisational unit for IT department
dn: ou=IT,dc=ironbox,dc=local
changetype: add
objectClass: organizationalUnit
ou: IT

# user: Juan Perez for unit IT department
dn: uid=juanperez,ou=IT,dc=ironbox,dc=local
changetype: add
objectClass: inetOrgPerson
cn: Juan Perez
sn: Perez
uid: juanperez
mail: juanperez@ironbox.local
userPassword: password123

# user: Ana Rey for unit IT department
dn: uid=anna.meier,ou=IT,dc=ironbox,dc=local
changetype: add
objectClass: inetOrgPerson
cn: Ana Rey
sn: Rey
uid: ana.rey
mail: ana.rey@ironbox.local
userPassword: password456

# organisational unit for DevOps department
dn: ou=DevOps,dc=ironbox,dc=local
changetype: add
objectClass: organizationalUnit
ou: DevOps

# user: Pedro Sanchez for the  der marketing department
dn: uid=pedro.sanchez,ou=DevOps,dc=ironbox,dc=local
changetype: add
objectClass: inetOrgPerson
cn: Pedro Sanchez
sn: Sanchez
uid: pedro.sanchez
mail: pedro.sanchez@ironbox.local
userPassword: password789

# organisational unit for Information Security department
dn: ou=SecOps,dc=ironbox,dc=local
changetype: add
objectClass: organizationalUnit
ou: DevOps

# user: Santiago Fernandez for the  der marketing department
dn: uid=santiago.fernandez,ou=SecOps,dc=ironbox,dc=local
changetype: add
objectClass: inetOrgPerson
cn: Santiago Fernandez
sn: Ssantiago
uid: santiago.fernandez
mail: santiago.fernandez@ironbox.local
userPassword: Marte2000
```

### Authentik

**Authentik** es una herramienta **open source** que sirve para **gestionar usuarios, accesos y autenticaciones** dentro de una organización. Podés pensarla como el “centro de control” donde definís **quién puede entrar**, **a qué sistemas**, y **por cuánto tiempo**.

Permite unificar el acceso a distintas aplicaciones (internas o externas) usando **SSO (inicio de sesión único)**, autenticación multifactor (MFA) y reglas de seguridad personalizables.

A diferencia de otros sistemas cerrados como Okta o Azure AD, Authentik se puede **instalar en tus propios servidores**, lo que da más control y privacidad sobre los datos de tus usuarios.

Una vez que levantamos el compose vamos a obtener credeciales. Para luego ingresar a http://localhost:9000

```xml
docker exec -it authentik-server ak changepassword akadmin
```

Vamos a ingresar y crear la conexión con LDAP.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761327732718/19046184-8a0d-4506-be91-74dba6b4dcac.png align="center")

Los datos importantes son:

* URI del servidor: ldap://openldap:389
    
* CN de enlace: cn=admin,dc=ironbox,dc=local
    
* Contraseña de enlace: admin
    
* DN base: dc=ironbox,dc=local
    
* Habilitar StartTLS: NO
    
* Usar URI del servidor para verificación SNI: NO
    
* Certificado de verificación TLS: (vacío)
    
* Certificado de autenticación del cliente TLS: (vacío)
    
* DN de usuario adicional: (VACÍO - muy importante)
    
* DN de grupo de adición: (VACÍO - muy importante)
    
* Filtro de objetos de usuario: (objectClass=inetOrgPerson)
    
* Filtro de objetos de grupo: (objectClass=groupOfNames)
    
* Campo pertenencia a grupos: member
    
* Campo de unicidad de objetos: Uusar: entryUUID
    
* Agregar estos Mappings:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761328215681/028dd3fb-57d7-4ede-a43f-ad2e87c4cdd3.png align="center")

Dejamos correr la sincronización.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761328382077/5abae118-7b7b-469d-a0fe-6eda43388ac8.png align="center")

¡Voilà! Ya vemos los usuarios de nuestro iDP LDAP.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761328664640/aa430708-feca-4027-8596-6cdf5d21bac7.png align="center")

### Aplicación

Generación de Proxy en Proveedores.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680608795/99682c43-e1be-4786-b0c0-3c56d70b17b4.png align="center")

Vamos a crear la aplicación para que ejecute el el Workflow de Credenciales.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680492934/12f06c8d-b96c-46a2-a988-d78d7b57ad91.png align="center")

Transporte de Notificacion

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680560042/f381ca7c-d473-45f7-b1b1-233ba2ca8750.png align="center")

### Vault

Vamos agregar los roles, usando la Base de Datos que ya tenemos. Podríamos agregar las que desearemos.

Primero habilitamos los secretos.

```xml
docker exec -e VAULT_TOKEN='root' vault vault secrets enable database
```

Luego configuramos la conexión con MySQL.

```xml
# Configurar la conexión
docker exec -e VAULT_TOKEN='root' vault vault write database/config/mysql-test \
    plugin_name=mysql-database-plugin \
    allowed_roles="mysql-readonly,mysql-readwrite,mysql-admin" \
    connection_url="{{username}}:{{password}}@tcp(mysql-test:3306)/testdb" \
    username="root" \
    password="rootpass123"
```

Ahora los roles que necesitamos, para esta Prueba.

```xml
# Crear roles
docker exec -e VAULT_TOKEN='root' vault vault write database/roles/mysql-readonly \
    db_name=mysql-test \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON testdb.* TO '{{name}}'@'%';" \
    default_ttl="1h" \
    max_ttl="24h"

docker exec -e VAULT_TOKEN='root' vault vault write database/roles/mysql-readwrite \
    db_name=mysql-test \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON testdb.* TO '{{name}}'@'%';" \
    default_ttl="1h" \
    max_ttl="2h"
```

Agregamos la politica para N8N

```xml
vault policy write n8n-policy - <<EOF
path "database/creds/*" {
  capabilities = ["read"]
}
path "sys/leases/revoke" {
  capabilities = ["update"]
}
EOF
```

Creamos el Token, para que N8N pueda dialogar con Vault.

```xml
vault token create -policy=n8n-policy -ttl=8760h
```

### Auditoria

Vamos a generar un base de datos, donde guardaremos todos los pedidos.

```xml
docker exec -it mysql-test mysql -u root -prootpass123
```

Ejecutamos

```xml
-- Usar la base de datos (o crear una nueva)
USE testdb;

-- O crear una base de datos específica para esto:
-- CREATE DATABASE db_access_manager;
-- USE db_access_manager;

-- Crear tabla de solicitudes
CREATE TABLE IF NOT EXISTS access_requests (
  id INT AUTO_INCREMENT PRIMARY KEY,
  request_id VARCHAR(100) UNIQUE NOT NULL,
  user_email VARCHAR(255) NOT NULL,
  user_username VARCHAR(255) NOT NULL,
  user_fullname VARCHAR(255) NOT NULL,
  database_name VARCHAR(100) NOT NULL,
  database_label VARCHAR(255) NOT NULL,
  access_type VARCHAR(50) NOT NULL,
  duration VARCHAR(10) NOT NULL,
  reason TEXT NOT NULL,
  status VARCHAR(20) DEFAULT 'pending',
  requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  approved_by VARCHAR(255) NULL,
  approved_at TIMESTAMP NULL,
  rejection_reason TEXT NULL,
  vault_role VARCHAR(100) NOT NULL,
  db_host VARCHAR(255) NOT NULL,
  db_port INT NOT NULL,
  db_name VARCHAR(255) NOT NULL,
  db_type VARCHAR(50) NOT NULL,
  credentials_username VARCHAR(255) NULL,
  credentials_sent BOOLEAN DEFAULT FALSE,
  INDEX idx_request_id (request_id),
  INDEX idx_status (status),
  INDEX idx_user_email (user_email),
  INDEX idx_requested_at (requested_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Verificar que se creó correctamente
DESCRIBE access_requests;

-- Ver las tablas
SHOW TABLES;
```

Agregamos las credenciales de MySQL a N8N

### Workflows

Aquí están los 4 que usaremos. Ahora los revisaremos uno por uno.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761679498159/c25448dc-7abe-44d8-b5b1-76144824d716.png align="center")

El primero que se dispara es el `Init DB Request Form` que es llamado por Authentik. En este caso el usuario solicita el acceso, pero antes de solicitarselo a **Vault** es necesario que los integrantes de **SecOps** aprueben esta solicitud. El formulario ejecuta `Get Available Databases`, para saber cuales tenemos cargadas en **Vault** para administrar.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761679648308/35ceaedb-d594-4c7b-b6dc-653c69f58fc0.png align="center")

Al solicitar el acceso, se ejecuta Database Access Processor para el envío de correo, de modo que le llegue al equipo de SecOps.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761679751158/5eae15ea-df7b-497d-8fb8-61958e8706f3.png align="center")

Mientras tanto, en la Base de Datos de Auditoría, ya tenemos el registro del pedido.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761679805729/fa159b34-22b6-4aeb-8634-7198c6cd35f2.png align="center")

Una vez que se apruebe o rechace se ejecuta Process Email Approval. Que tiene estos pasos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680072249/27df647d-f81b-4cee-a9c8-8ebab0290d39.png align="center")

Donde se verifica si el token es válido, si existe una solicitud y, según la aprobación o no por parte de SecOps, se ejecuta la aplicación de permisos. En este caso de prueba, vamos a seleccionar *aprobar*.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680193463/efc542d3-0c2a-45c3-9ac7-8b03c3163a2f.png align="center")

Mientras que el usuario le llega este correo, con todos los datos necesarios.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680218273/2bb35457-dd4b-49c5-a94e-798ba78d756d.png align="center")

Aquí les dejo un resumen de los tiempos de ejecución.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761680293719/e01849d2-1e64-4d3a-9c96-dffa91c5f658.png align="center")

Lógicamente, el registro de auditoría cambió de estado, incluyendo todos los datos de aprobación.

Aca el Docker Compose.

```yaml
version: '3'

networks:
  ldap_network:
    driver: bridge

services:
  # PostgreSQL para Authentik
  postgresql:
    image: docker.io/library/postgres:16-alpine
    container_name: authentik-db
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS:-admin123}
      POSTGRES_USER: ${PG_USER:-authentik}
      POSTGRES_DB: ${PG_DB:-authentik}
    networks:
      - ldap_network

  # Redis para Authentik
  redis:
    image: docker.io/library/redis:alpine
    container_name: authentik-redis
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redis-data:/data
    networks:
      - ldap_network

  # Authentik Server
  server:
    image: ghcr.io/goauthentik/server:2024.8.3
    container_name: authentik-server
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:-admin123}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-changeme-please-generate-a-secret-key}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
      # Para integración con LDAP
      AUTHENTIK_LDAP__HOST: openldap
      AUTHENTIK_LDAP__PORT: 389
    volumes:
      - ./authentik/media:/media
      - ./authentik/custom-templates:/templates
    ports:
      - "9000:9000"
      - "9443:9443"
    depends_on:
      - postgresql
      - redis
      - openldap
    networks:
      - ldap_network

  # Authentik Worker
  worker:
    image: ghcr.io/goauthentik/server:2024.8.3
    container_name: authentik-worker
    restart: unless-stopped
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:-admin123}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-changeme-please-generate-a-secret-key}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./authentik/media:/media
      - ./authentik/certs:/certs
      - ./authentik/custom-templates:/templates
    depends_on:
      - postgresql
      - redis
      - openldap
    networks:
      - ldap_network

  # OpenLDAP
  openldap:
    image: osixia/openldap:latest
    container_name: openldap
    environment:
      LDAP_BASE_DN: "dc=ironbox,dc=local"
      LDAP_ORGANISATION: "Ironbox"
      LDAP_DOMAIN: "ironbox.local"
      LDAP_ADMIN_PASSWORD: "admin"
      LDAP_TLS: "false"
    volumes:
      - ./ldap/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-bootstrap.ldif
      - ldap-data:/var/lib/ldap
      - ldap-config:/etc/ldap/slapd.d
    networks:
      - ldap_network
    ports:
      - "389:389"
    command: --copy-service
    restart: unless-stopped

# phpLDAPadmin
  phpldapadmin:
    image: osixia/phpldapadmin:latest
    container_name: phpldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: openldap
      PHPLDAPADMIN_HTTPS: "false"
    networks:
      - ldap_network
    ports:
      - "8081:80"
    restart: unless-stopped
    depends_on:
      - openldap

  # N8N - Workflow Automation
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=admin123
      - N8N_HOST=localhost
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - WEBHOOK_URL=http://localhost:5678/
      - GENERIC_TIMEZONE=America/Argentina/Buenos_Aires
    volumes:
      - n8n-data:/home/node/.n8n
    networks:
      - ldap_network

  # HashiCorp Vault
  vault:
    image: hashicorp/vault:latest
    container_name: vault
    restart: unless-stopped
    ports:
      - "8200:8200"
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: "root"
      VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
      VAULT_ADDR: "http://0.0.0.0:8200"
    cap_add:
      - IPC_LOCK
    networks:
      - ldap_network
    command: server -dev
  # MySQL de prueba
  mysql-test:
    image: mysql:8.0
    container_name: mysql-test
    environment:
      MYSQL_ROOT_PASSWORD: rootpass123
      MYSQL_DATABASE: testdb
      MYSQL_USER: testuser
      MYSQL_PASSWORD: testpass123
    ports:
      - "3306:3306"
    networks:
      - ldap_network
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local
  ldap-data:
    driver: local
  ldap-config:
    driver: local
  n8n-data:
    driver: local
  mysql_data:
    driver: local
```

¡Espero que les sirva! Les dejo el [repositorio](https://github.com/safernandez666/AuthentikWithVault) y un pequeño video para que vean lo que hemos creado.

Saludos.

%[https://www.youtube.com/watch?v=lGBp7TG7FVk] 

### Referencias

[https://helgeklein-com.translate.goog/blog/authentik-authentication-sso-user-management-password-reset-for-home-networks/?\_x\_tr\_sl=en&\_x\_tr\_tl=es&\_x\_tr\_hl=es&\_x\_tr\_pto=tc](https://helgeklein-com.translate.goog/blog/authentik-authentication-sso-user-management-password-reset-for-home-networks/?_x_tr_sl=en&_x_tr_tl=es&_x_tr_hl=es&_x_tr_pto=tc)
