Generar credenciales de conexión a base de datos bajo demanda con Vault

Escrito por el .
programacion planeta-codigo software
Comentarios

Una de las funcionalidades proporcionada por Vault es generar credenciales dinámicas para la conexión a bases de datos. Generar las credenciales de forma dinámica proporciona varios beneficios: no hay unas credenciales que usen las aplicaciones que tengan un tiempo de vida indefinido, las aplicaciones no necesitan guardar en su configuración las credenciales, las credenciales se rotan de forma automática y los permisos para obtenerlas se pueden revocar de forma centralizada con Vault para cuales quiera bases de datos que se utilicen. Soporta las bases de datos más populares entre ellas postgres.

Vault
PostgreSQL

Las base de datos para proteger los datos realizan autenticación del usuario que se conecta. Normalmente utilizando un usuario y contraseña, una vez autenticado el usuario mediante permisos se realiza la autorización de las operaciones que puede realizar, a que bases de datos se puede conectar, que tablas puede acceder y que operaciones puede realizar.

Este modelo de autenticación tiene algunos inconvenientes. Uno de los inconvenientes es que los usuarios y contraseñas para mayor seguridad han de cambiarse con cierta frecuencia para evitar que si las credenciales quedan comprometidas no puedan utilizarse indefinidamente. Otro problema es que cada aplicación ha de conocer las credenciales de la base de datos, esto hace que haya múltiples posibilidades de que las credenciales queden comprometidas. Por otro lado el uso de las credenciales no queda registrado de forma centralizada que es necesario para saber ante una brecha de seguridad qué datos han sido accedidos y por quien.

La herramienta Vault de HashiCorp da solución a estos problemas generando credenciales de acceso a las bases de datos de forma dinámica, bajo demanda y con un tiempo de concesión limitado. Las credenciales tiene un tiempo de vida limitado si no se renueva su concesión y la generación de las credenciales queda registrado. La forma que tiene Vault de generar credenciales de forma dinámica en una base de datos relacional como postgres es conectarse a la base de datos con un usuario con permisos para generarlas y ejecutar una sentencia SQL que crea las credenciales.

Para permitir a Vault generar credenciales de conexión hay que activar el backend de bases de datos, configurarlo con la sentencia SQL que se utilizará para generar las credenciales y crear el rol de Vault que genera las credenciales cuando se le solicite. En este ejemplo se muestra para la base de datos postgres pero Vault también soporta otras bases de datos como MySQL. En el ejemplo la base de datos postgres se ejecuta en un contenedor de Docker en el que se asignan el usuario y contraseña del usuario root que utiliza Vault para generar las credenciales de forma dinámica.

Con las siguientes comandos se inicia Consul y Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ consul agent -dev
==> Starting Consul agent...
==> Consul agent running!
           Version: 'v1.5.0'
           Node ID: '38d4f541-0958-6d7d-d49e-a31a15987286'
         Node name: 'archlinux'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
      Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ vault server -dev -config vault.hcl
==> Vault server configuration:

             Api Address: http://127.0.0.1:8200
                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: info
                   Mlock: supported: true, enabled: false
                 Storage: consul (HA available)
                 Version: Vault v1.1.1
             Version Sha: a3dcd63451cf6da1d04928b601bbe9748d53842e

WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variable:

    $ export VAULT_ADDR='http://127.0.0.1:8200'

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: jeW10eak6TxJzH2qFnwk7bWk7HcpDXd3KQOobi1rZTQ=
Root Token: s.0YRcRzojVcPG8LbbzyUd1MEA

Development mode should NOT be used in production installations!
1
2
3
4
5
6
ui = true

storage "consul" {
  address = "127.0.0.1:8500"
  path  = "vault"
}

La base de datos se inicia en un contenedor de Docker, se crea una base de datos y una tabla.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ docker run -it -p "5432:5432" -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:alpine
$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2792b13c36c1        postgres:alpine     "docker-entrypoint.s…"   3 minutes ago       Up 3 minutes        0.0.0.0:5432->5432/tcp   distracted_keldysh
$ docker exec -it 2792b13c36c1 bash
bash-5.0# psql -U postgres
psql (11.4)
Type "help" for help.

postgres=# CREATE DATABASE app;
postgres=# \connect app
You are now connected to database "app" as user "postgres".
app=# CREATE TABLE product (
    id          bigint PRIMARY KEY,
    name       varchar(256) NOT NULL
);
app=# \dt
          List of relations
 Schema |  Name   | Type  |  Owner   
--------+---------+-------+----------
 public | product | table | postgres
(1 row)
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 2792b13c36c1
172.17.0.2

En Vault hay que crear la configuración para conectarse a la base de datos y un rol que contiene la configuración para generar las credenciales y permitir obtenerlas, básicamente es un sentencia SQL con el nombre del usuario y contraseña, los permisos que se le asignan y el tiempo de concesión.

1
2
3
$ export VAULT_ADDR=http://127.0.0.1:8200
$ vault login s.0YRcRzojVcPG8LbbzyUd1MEA
$ vault secrets enable database
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vault write database/config/app \
   plugin_name=postgresql-database-plugin \
   allowed_roles="app" \
   connection_url="postgresql://{{username}}:{{password}}@localhost:5432/?sslmode=disable" \
   username="postgres" \
   password="postgres"
$ vault write database/roles/app \
    db_name=app \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

Las credenciales se generan en el momento de leer una propiedad de Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ vault read database/roles/app
Key                      Value
---                      -----
creation_statements      [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";]
db_name                  app
default_ttl              1h
max_ttl                  24h
renew_statements         []
revocation_statements    []
rollback_statements      []
$ vault read database/creds/app
Key                Value
---                -----
lease_id           database/creds/app/rFFlNmpNoxezccTVh3WufZOT
lease_duration     1h
lease_renewable    true
password           A1a-6hRTGNaShFIEGLvp
username           v-root-app-ydbbHqVq1gYQqsUxMuIc-1565857370

En postgres la conexión desde la máquina local se permiten sin necesidad de credenciales, para simular realizar la conexión desde otra máquina hay que iniciar otro contenedor. En la conexión se utilizan las credenciales que ha proporcionado Vault. Dado que se realiza una operación de red hay que desactivar el firewall del sistema o permitir la conexión al puerto de la base de datos que en postgres es el 5432 si fuera necesario. Listando los usuarios de la base de datos con el comando \du se muestra el creado por Vault y si tiempo de validez.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ docker run -it postgres:alpine bash
bash-5.0# PGPASSWORD=A1a-6hRTGNaShFIEGLvp psql -U v-root-app-ydbbHqVq1gYQqsUxMuIc-1565857370 -h 172.17.0.2 -d app
psql (11.4)
Type "help" for help.

app=> \dt
          List of relations
 Schema |  Name   | Type  |  Owner   
--------+---------+-------+----------
 public | product | table | postgres
(1 row)
app=# \du
                                                    List of roles
                 Role name                  |                         Attributes                         | Member of 
--------------------------------------------+------------------------------------------------------------+-----------
 postgres                                   | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 v-root-app-ydbbHqVq1gYQqsUxMuIc-1565857370 | Password valid until 2019-08-15 09:22:55+00                | {}
app=> quit

Si el usuario y contraseña no es correcto no se permite la conexión a la base de datos.

1
2
bash-5.0# PGPASSWORD=tampered psql -U v-root-app-ydbbHqVq1gYQqsUxMuIc-1565857370 -h 172.17.0.2 -d app
psql: FATAL:  password authentication failed for user "v-root-app-ydbbHqVq1gYQqsUxMuIc-1565857370"

En las aplicaciones Java que utilizan Spring el proyecto Spring Cloud Vault proporciona las utilidades para simplificar en gran medida la obtención de las credenciales a la base de datos utilizando Vault.

Esto permite que únicamente Vault conozca la cuenta root de la base de datos o una con suficientes permisos para crear credenciales, las aplicaciones no almacenan en su configuración las credenciales para conectarse la base de datos solo las de Vault que le permiten obtener unas credenciales para la base de datos con un tiempo de vida limitado y que pueden revocarse desde Vault en caso de un problema de seguridad para cuales quiera bases de datos que se utilicen.