Optimiza la seguridad: Gestión de accesos temporales con n8n y Vault
Como gestionar accesos a base de datos, de una manera eficaz.

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.

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.

Aca el .ldif
# 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
docker exec -it authentik-server ak changepassword akadmin
Vamos a ingresar y crear la conexión con LDAP.

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:

Dejamos correr la sincronización.

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

Aplicación
Generación de Proxy en Proveedores.

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

Transporte de Notificacion

Vault
Vamos agregar los roles, usando la Base de Datos que ya tenemos. Podríamos agregar las que desearemos.
Primero habilitamos los secretos.
docker exec -e VAULT_TOKEN='root' vault vault secrets enable database
Luego configuramos la conexión con MySQL.
# 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.
# 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
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.
vault token create -policy=n8n-policy -ttl=8760h
Auditoria
Vamos a generar un base de datos, donde guardaremos todos los pedidos.
docker exec -it mysql-test mysql -u root -prootpass123
Ejecutamos
-- 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.

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.

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

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

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

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.

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

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

Lógicamente, el registro de auditoría cambió de estado, incluyendo todos los datos de aprobación.
Aca el Docker Compose.
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 y un pequeño video para que vean lo que hemos creado.
Saludos.



