Skip to main content

Command Palette

Search for a command to run...

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

Como gestionar accesos a base de datos, de una manera eficaz.

Updated
8 min read
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.

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.

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

Hashicorp Vault

Part 1 of 1