Gestión de secretos con Vault

Avanzado
SpringBoot
SpringBoot
Actualizado: 08/10/2025

HashiCorp Vault

HashiCorp Vault es una solución de gestión de secretos diseñada para centralizar, proteger y controlar el acceso a información sensible como credenciales de bases de datos, claves API, certificados y tokens. A diferencia del Config Server que vimos en lecciones anteriores, Vault se especializa específicamente en gestionar secretos con cifrado automático, auditoría completa y controles de acceso granulares.

En entornos de microservicios, donde múltiples servicios necesitan acceder a secretos de manera segura, Vault proporciona una capa de abstracción que elimina la necesidad de hardcodear credenciales en código o archivos de configuración.

Arquitectura KV y conceptos básicos

Vault organiza los secretos mediante motores de secretos (secrets engines). El motor KV (Key-Value) es el más utilizado para almacenar secretos estáticos como credenciales y configuraciones sensibles.

El motor KV tiene dos versiones:

  • KV v1: Almacenamiento simple sin versionado
  • KV v2: Incluye versionado, metadatos y capacidades de eliminación suave

Para nuestro entorno de desarrollo utilizaremos KV v2, que viene habilitado por defecto en modo desarrollo. Los secretos se almacenan en rutas jerárquicas como secret/aplicacion/entorno/credencial.

Configuración de Vault con Docker

Para configurar un entorno de desarrollo, utilizaremos Vault en modo dev con Docker. Este modo simplifica la configuración inicial pero no debe usarse en producción.

Creamos un archivo docker-compose.yml para levantar Vault:

version: '3.8'
services:
  vault:
    image: hashicorp/vault:latest
    container_name: dev-vault
    restart: unless-stopped
    ports:
      - "8200:8200"
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: 'dev-root-token'
      VAULT_DEV_LISTEN_ADDRESS: '0.0.0.0:8200'
    cap_add:
      - IPC_LOCK
    command: 'vault server -dev -dev-root-token-id=dev-root-token'

Iniciamos el contenedor:

docker-compose up -d

El token raíz dev-root-token nos permite acceso completo durante el desarrollo. En el log del contenedor veremos la confirmación de que Vault está funcionando:

docker logs dev-vault

Almacenamiento de secretos con KV v2

Una vez que Vault está ejecutándose, podemos almacenar secretos utilizando la CLI. Primero, configuramos las variables de entorno:

export VAULT_ADDR='http://localhost:8200'
export VAULT_TOKEN='dev-root-token'

Almacenamos un secreto de base de datos en la ruta secret/microservicio/database:

vault kv put secret/microservicio/database \
  username=app_user \
  password=secure_password_123 \
  url=jdbc:postgresql://localhost:5432/appdb

Podemos verificar que el secreto se almacenó correctamente:

vault kv get secret/microservicio/database

La salida mostrará tanto los metadatos (versión, fecha de creación) como los datos del secreto.

Método de autenticación AppRole

En aplicaciones reales no utilizamos el token raíz. AppRole es un método de autenticación diseñado para aplicaciones y servicios automatizados. Funciona con dos componentes:

  • Role ID: Identificador público del rol (similar a un username)
  • Secret ID: Credencial secreta (similar a un password)

Habilitamos AppRole:

vault auth enable approle

Creamos una política que define qué puede hacer nuestra aplicación. Guardamos esto en app-policy.hcl:

path "secret/data/microservicio/*" {
  capabilities = ["read"]
}

Aplicamos la política:

vault policy write app-policy app-policy.hcl

Creamos un rol AppRole llamado microservicio-app:

vault write auth/approle/role/microservicio-app \
  secret_id_ttl=24h \
  token_ttl=1h \
  token_max_ttl=2h \
  secret_id_num_uses=0 \
  policies="app-policy"

Los parámetros configuran:

  • secret_id_ttl: Tiempo de vida del Secret ID (24 horas)
  • token_ttl: Duración inicial del token (1 hora)
  • token_max_ttl: Duración máxima del token (2 horas)
  • secret_id_num_uses: Límite de usos del Secret ID (0 = ilimitado)

Obtención de credenciales AppRole

Recuperamos el Role ID:

vault read auth/approle/role/microservicio-app/role-id

Generamos un Secret ID:

vault write -f auth/approle/role/microservicio-app/secret-id

El output nos proporcionará tanto el Role ID como el Secret ID que necesitaremos para configurar nuestra aplicación Spring Boot.

Autenticación con AppRole

Para probar la autenticación, utilizamos ambas credenciales para obtener un token:

vault write auth/approle/login \
  role_id="<role-id-obtenido>" \
  secret_id="<secret-id-obtenido>"

Este comando devuelve un token cliente con permisos limitados según la política app-policy. Con este token, podemos acceder únicamente a los secretos bajo la ruta secret/microservicio/.

Verificación del acceso a secretos

Utilizamos el token cliente para verificar el acceso:

export VAULT_TOKEN="<client-token-obtenido>"
vault kv get secret/microservicio/database

Si intentamos acceder a rutas no autorizadas, Vault denegará el acceso:

vault kv get secret/otro-microservicio/config
# Error: permission denied

Esta configuración proporciona la base de seguridad necesaria para que nuestros microservicios accedan a secretos de forma controlada. En la siguiente sección veremos cómo integrar esta configuración con Spring Cloud Vault para automatizar la recuperación de secretos durante el arranque de la aplicación.

Spring Cloud Vault: integración básica

Spring Cloud Vault proporciona integración automática entre aplicaciones Spring Boot y HashiCorp Vault, eliminando la necesidad de gestionar manualmente la autenticación y recuperación de secretos. Esta integración utiliza el ConfigData API de Spring Boot 3 para cargar secretos como propiedades de configuración durante el arranque de la aplicación.

A diferencia de Spring Cloud Config que vimos anteriormente, Spring Cloud Vault se especializa en gestión segura de secretos con capacidades adicionales como rotación automática de credenciales y múltiples métodos de autenticación.

Configuración de dependencias

Para integrar Spring Cloud Vault en nuestro proyecto haremos lo siguiente.

Como ya venimos de crear una aplicación Spring Boot con Spring Cloud Config Server ya tenemos un servidor de configuración, lo que haremos es agregar la siguiente dependencia a esa aplicación config server para que pueda comunicarse con el Vault dockerizado.

Por lo que añadimos la dependencia correspondiente al pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>

Esta dependencia incluye automáticamente el cliente Vault y las funcionalidades de auto-configuración necesarias para Spring Boot.

Ejemplo completo de cómo quedaría el pom de la aplicación config server con vault:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-vault-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

ConfigData API vs Bootstrap Context

Spring Cloud Vault 3.0+ utiliza el ConfigData API de Spring Boot, reemplazando el contexto bootstrap tradicional. Esto significa que la configuración se realiza principalmente en application.yml en lugar de bootstrap.yml.

Para habilitar la integración con Vault, configuramos la propiedad spring.config.import en nuestro application.yml:

spring:
  application:
    name: microservicio-demo
  config:
    import: vault://
  cloud:
    vault:
      uri: http://localhost:8200
      authentication: APPROLE
      app-role:
        role-id: ${VAULT_ROLE_ID}
        secret-id: ${VAULT_SECRET_ID}

La propiedad spring.config.import: vault:// instruye a Spring Boot para importar configuración desde Vault durante la fase de inicialización.

Configuración con autenticación AppRole

Configuramos la autenticación AppRole utilizando las credenciales que obtuvimos en la sección anterior. Creamos variables de entorno para evitar hardcodear los valores sensibles:

export VAULT_ROLE_ID="tu-role-id-aqui"
export VAULT_SECRET_ID="tu-secret-id-aqui"

El archivo application.yml completo sería:

spring:
  application:
    name: microservicio-demo
  config:
    import: vault://
  cloud:
    vault:
      uri: http://localhost:8200
      authentication: APPROLE
      app-role:
        role-id: ${VAULT_ROLE_ID}
        secret-id: ${VAULT_SECRET_ID}
      kv:
        enabled: true
        backend: secret
        default-context: microservicio

Configuración de propiedades específicas

Spring Cloud Vault permite configurar múltiples aspectos del comportamiento de integración:

  • backend: Define el motor de secretos a utilizar (por defecto secret)
  • default-context: Establece el contexto base para buscar secretos (por defecto el nombre de la aplicación)
  • enabled: Habilita o deshabilita la integración KV (por defecto true)

Inyección automática de propiedades

Una vez configurado, los secretos almacenados en Vault se cargan automáticamente como propiedades de Spring. Si tenemos el secreto almacenado en secret/microservicio/database, podemos acceder a él mediante:

@Component
public class DatabaseConfig {
    
    @Value("${username}")
    private String dbUsername;
    
    @Value("${password}")
    private String dbPassword;
    
    @Value("${url}")
    private String dbUrl;
    
    // Getters y lógica de configuración
    public void printConfig() {
        System.out.println("Database URL: " + dbUrl);
        System.out.println("Username: " + dbUsername);
        // No imprimas la password en logs reales
    }
}

Configuration Properties con Vault

Para una aproximación más estructurada, utilizamos @ConfigurationProperties:

@ConfigurationProperties(prefix = "database")
@Component
public class DatabaseProperties {
    
    private String username;
    private String password;
    private String url;
    
    // Constructores, getters y setters
    public DatabaseProperties() {}
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    public String getUrl() {
        return url;
    }
    
    public void setUrl(String url) {
        this.url = url;
    }
}

Esta clase se vincula automáticamente con los secretos almacenados bajo la ruta secret/microservicio/database donde cada clave (username, password, url) se mapea a las propiedades correspondientes.

Verificación de la integración

Para verificar que la integración funciona correctamente, creamos un endpoint de prueba:

@RestController
public class ConfigTestController {
    
    private final DatabaseProperties databaseProperties;
    
    public ConfigTestController(DatabaseProperties databaseProperties) {
        this.databaseProperties = databaseProperties;
    }
    
    @GetMapping("/config/test")
    public Map<String, String> testConfig() {
        Map<String, String> config = new HashMap<>();
        config.put("username", databaseProperties.getUsername());
        config.put("url", databaseProperties.getUrl());
        // Nunca expongas passwords en endpoints reales
        config.put("password", "***HIDDEN***");
        return config;
    }
}

Configuración de múltiples rutas

Spring Cloud Vault permite configurar múltiples rutas de secretos para una misma aplicación:

spring:
  cloud:
    vault:
      generic:
        enabled: true
        backend: secret
        default-context: microservicio
        contexts:
          - database
          - external-apis
          - certificates

Con esta configuración, Vault buscará secretos en:

  • secret/microservicio/database
  • secret/microservicio/external-apis
  • secret/microservicio/certificates

Alternativa con token directo

Para entornos de desarrollo o testing, podemos utilizar autenticación por token:

spring:
  cloud:
    vault:
      uri: http://localhost:8200
      authentication: TOKEN
      token: ${VAULT_TOKEN:dev-root-token}

Esta configuración es útil para pruebas rápidas pero no se recomienda para entornos productivos debido a las implicaciones de seguridad del token raíz.

Gestión de errores y fallback

Spring Cloud Vault proporciona mecanismos de fallback cuando Vault no está disponible:

spring:
  cloud:
    vault:
      fail-fast: false
      config:
        lifecycle:
          enabled: true
  • fail-fast: false: Permite que la aplicación arranque aunque Vault no esté disponible
  • lifecycle.enabled: true: Habilita la gestión del ciclo de vida para rotación de tokens

Con esta configuración básica, nuestra aplicación Spring Boot puede recuperar automáticamente secretos de Vault durante el arranque, utilizando la autenticación AppRole que configuramos anteriormente. Los secretos se integran transparentemente en el sistema de propiedades de Spring, permitiendo su uso a través de @Value o @ConfigurationProperties.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en SpringBoot

Documentación oficial de SpringBoot
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SpringBoot es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de SpringBoot

Explora más contenido relacionado con SpringBoot y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la arquitectura y funcionamiento básico de HashiCorp Vault, especialmente el motor KV v2.
  • Configurar un entorno de desarrollo Vault con Docker y almacenar secretos de forma segura.
  • Implementar el método de autenticación AppRole para aplicaciones automatizadas.
  • Integrar Spring Cloud Vault en aplicaciones Spring Boot para la recuperación automática de secretos.
  • Configurar y utilizar propiedades inyectadas desde Vault en aplicaciones Spring mediante @Value y @ConfigurationProperties.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje