Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring

Escrito por el .
java planeta-codigo programacion
Enlace permanente Comentarios

Spring Cloud Vault facilita la integración con Vault, una de sus usos es utilizarlo para obtener unas credenciales de conexión a la base de datos generadas bajo demanda y con un tiempo de vida limitado en vez de embeberlas en la configuración de la aplicación y con u tiempo de vida indefinido.

Spring

HashiCorp Vault

La herramienta Vault de HashiCorp permite almacenar secretos, otra de sus funcionalidad es ser capaz de generar credenciales de forma dinámica. Habitualmente una aplicación para conectarse a una base de datos incluye en su configuración las credenciales, usuario y contraseña, para autenticarse y crear las conexiones, estas credenciales tiene un tiempo de vida indefinido y comprometidas proporcionan acceso a la base de datos.

Vault permite centralizar la seguridad, otorgar a cada aplicación los permisos para conectarse a una base de datos a través de la obtención de credenciales (usuario y contraseña), auditar su uso y limitar el tiempo de validez de las credenciales a unas horas en vez de forma indefinida.

Spring Cloud Vault permite a una aplicación Spring simplificar la integración con Vault para obtener unas credenciales generadas dinámicamente.

En el archivo de construcción de la aplicación hay que incluir las dependencias de Spring para la integración con Vault.

1
2
3
4
5
...
implementation('org.springframework.cloud:spring-cloud-starter-config', excludeSpringBootStarterLogging)
implementation('org.springframework.cloud:spring-cloud-starter-vault-config', excludeSpringBootStarterLogging)
implementation('org.springframework.cloud:spring-cloud-vault-config-databases', excludeSpringBootStarterLogging)
...
build.gradle

Obtener la credenciales de conexión a la base de datos es transparente para el código de la aplicación, lo único que se necesita es el usuario y contraseña además de la URL de conexión.

 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
29
30
31
32
33
34
35
package io.github.picodotdev.blogbitix.springcloudvault;

...

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Override
    public void run(String... args) {
        System.out.printf("Username: %s%n", username);
        System.out.printf("Password: %s%n", password);

        try (Connection connection = DriverManager.getConnection("jdbc:postgresql://127.0.0.1:5432/app", username, password)) {
            if (connection != null) {
                System.out.println("Connected to the database!");
            } else {
                System.out.println("Failed to make connection!");
            }
        } catch (SQLException e) {
            System.err.format("SQL State: %s\n%s", e.getSQLState(), e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class, args);
    }
}
Main.java
1
2
3
4
5
6
...
10:37:47.013 [main] INFO  io.github.picodotdev.blogbitix.springcloudvault.Main - Started Main in 1.368 seconds (JVM running for 1.784)
Username: v-approle-app-gRlHQlNyPF8qJ3DCUz9U-1566549466
Passowrd: A1a-MPPFfvMD3OQUC00H
Connected to the database!
...
System.out

La parte más relevante está en la configuración necesaria de la aplicación. Hay que añadir como configuración la ubicación del servidor Vault, es necesario configurar un método de autenticación para Vault, para el caso de aplicaciones el recomendado es AppRole. Con AppRole cada aplicación necesita de un role-id y un secret-id que hay que generar previamente. Y el rol del que obtener las credenciales, app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
spring.application.name: app
spring.cloud.vault:
  uri: http://127.0.0.1:8200
  authentication: APPROLE
  app-role:
    role-id: a248529d-882c-ef5f-f7e6-6a9d349afa57
    secret-id: 13b6c224-dc18-0404-7bc1-7c258c4c5a48
  database:
    enabled: true
    role: app
    backend: database
    username-property: spring.datasource.username
    password-property: spring.datasource.password
bootstrap.yml

Para probarlo hay que iniciar en este caso el servidor Consul ya que en el ejemplo se utiliza como el lugar donde se guardan los secretos. También hay que iniciar 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: '2f523068-b196-e3bb-0b09-dc34e20989d2'
         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
consul.sh
 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: jsBAedtA88Lm7uU1pCSYOBK1980Ervf9GSky4usmEdQ=
Root Token: s.8wwm4fRuRRVFm1cbBUMgdNtE

Development mode should NOT be used in production installations!
vault.sh
ui = true

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

La base de datos PostgreSQL se inicia como un contenedor de Docker.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ docker run --rm -it -p "5432:5432" -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:alpine
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8fedf0ef1342        postgres:alpine     "docker-entrypoint.s…"   22 seconds ago      Up 20 seconds       0.0.0.0:5432->5432/tcp   serene_leavitt

$ docker exec -it 8fedf0ef1342 bash
bash-5.0# psql -U postgres
psql (11.4)
Type "help" for help.

postgres=# CREATE DATABASE app;
CREATE DATABASE
postgres=#
postgresql.sh

Para realizar la configuración de Vault primero hay que iniciar sesión, en el modo desarrollo del ejemplo utilizando el token root que es generado y emitido en la salida al iniciarlo.

1
2
$ export VAULT_ADDR='http://127.0.0.1:8200'
$ vault login s.8wwm4fRuRRVFm1cbBUMgdNtE
vault-login.sh

Como en el artículo Generar credenciales de conexión a base de datos bajo demanda con Vault hay que activar el backend de database para generar credenciales de bases de datos, en las que básicamente se especifica la cadena de conexión a la base de datos de PostgreSQL con un usuario y contraseña con permisos suficientes para crear usuarios y la sentencia SQL que los crea. Se habilita y configura el backend database del que obtener las credenciales.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ vault secrets enable database
$ 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"
vault-database.sh

Para que la aplicación de Spring Boot obtenga las credenciales ha de autenticarse en Vault en este caso utilizando el método recomendado para las aplicaciones que es utilizando en mecanismo de autenticación AppRole que básicamente es un usuario y contraseña para las aplicaciones.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ vault auth enable approle
$ vault write auth/approle/role/app \
    secret_id_ttl=10m \
    token_num_uses=10 \
    token_ttl=20m \
    token_max_ttl=30m \
    secret_id_num_uses=40 \
    policies=database-app
$ vault read auth/approle/role/app/role-id
Key        Value
---        -----
role_id    a248529d-882c-ef5f-f7e6-6a9d349afa57
$ vault write -f auth/approle/role/app/secret-id
Key                   Value
---                   -----
secret_id             13b6c224-dc18-0404-7bc1-7c258c4c5a48
secret_id_accessor    fd9a0915-af6e-b0a8-4e6c-dbb6ee587903
vault-approle.sh

En Vault los permisos se otorgan con las policy, los secretos se organiza en una estructura jerárquica de directorios y a cada una de los contextos se le otorga los permisos deseados. Spring obtiene las credenciales para la base de datos del contexto database/creds/app por lo que al rol utilizando para obtener las credenciales hay que asociarle un policy con permisos de lectura para este contexto.

1
2
$ vault policy write database-app database-app.hcl

vault-policy.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
path "secret/application" {
  capabilities = ["read"]
}

path "secret/app" {
  capabilities = ["read"]
}

path "database/creds/app" {
  capabilities = ["read"]
}
database-app.hcl

Obtenido un role-id y un secret-id so observa los policies asociados además de otras propiedades.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vault write auth/approle/login role_id=a248529d-882c-ef5f-f7e6-6a9d349afa57 secret_id=13b6c224-dc18-0404-7bc1-7c258c4c5a48
Key                     Value
---                     -----
token                   s.yAKbv8Qz5tfXpc8n7C8oND01
token_accessor          GPHO70D2NFRHFFdsiNiftmzO
token_duration          20m
token_renewable         true
token_policies          ["database-app" "default"]
identity_policies       []
policies                ["database-app" "default"]
token_meta_role_name    app
vault-role-id.sh

En este caso la aplicación de Spring incluye en su configuración las credenciales del AppRole, también se puede proporcionar como variables de entorno y propiedades del sistema.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.


Este artículo forma parte de la serie hashicorp:

  1. Introducción a Nomad para gestionar aplicaciones y microservicios
  2. Estrategias de despliegue para microservicios con Nomad
  3. Servicios con persistencia en el orquestador de microservicios Nomad
  4. Crear de forma sencilla y rápida máquinas virtuales de VirtualBox con Vagrant
  5. Registro y descubrimiento de servicios en contenedores de Docker con Consul y Registrator
  6. Administrar secretos y proteger datos sensibles con Vault
  7. Generar credenciales de conexión a base de datos bajo demanda con Vault
  8. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  9. Microservicios con Spring Cloud, Consul, Nomad y Traefik
  10. Comunicaciones seguras, autenticación mutua y autorizaciones con intenciones entre servicios usando Consul Connect y Nomad
  11. Revertir un servicio a una versión anterior con Nomad
  12. Arquitectura de referencia de Consul, Vault y Nomad para un centro de datos
  13. Cifrado y descifrado como servicio con Vault
  14. Acceso simple y seguro a sistemas remotos con Boundary
  15. Planificar procesos periódicos y scripts con Nomad
  16. Las funcionalidades de un service mesh en una arquitectura de microservicios
  17. Construir, desplegar y observar aplicaciones de forma uniforme con Waypoint
  18. Configurar GNU/Linux para usar forward DNS y el servidor DNS de Consul

Este artículo forma parte de la serie spring-cloud:

  1. Datos de sesión externalizados con Spring Session
  2. Aplicación Java autocontenida con Spring Boot
  3. Configuración de una aplicación en diferentes entornos con Spring Cloud Config
  4. Información y métricas de la aplicación con Spring Boot Actuator
  5. Registro y descubrimiento de servicios con Spring Cloud y Consul
  6. Aplicaciones basadas en microservicios
  7. Registro y descubrimiento de servicios con Spring Cloud Netflix
  8. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  9. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  10. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  11. Balanceo de carga y resiliencia en un microservicio con Spring Cloud Netflix y Ribbon
  12. Proxy para microservicios con Spring Cloud Netflix y Zuul
  13. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  14. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  15. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  16. Trazabilidad en microservicios con Spring Cloud Sleuth
  17. Implementar tolerancia a fallos con Resilience4j
  18. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  19. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  20. Microservicios con Spring Cloud, Consul, Nomad y Traefik
  21. Trazabilidad en servicios distribuidos con Sleuth y Zipkin
  22. Configuración de una aplicación con Spring Boot y configuración centralizada con Spring Cloud Config
Comparte el artículo: