HashiCorp Vault with Spring Cloud Config Server

TL;DR: Setting up Vault for secrets management with Spring Cloud Config Server in Docker environments

Overview

This guide covers deploying HashiCorp Vault alongside Spring Cloud Config Server for centralized secrets management in microservices architectures.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Config Infrastructure                    │
│                                                             │
│  ┌───────────────┐         ┌────────────────────┐          │
│  │    Vault      │◀───────▶│  Config Server     │          │
│  │  (Secrets)    │         │  (Spring Cloud)    │          │
│  └───────────────┘         └────────────────────┘          │
│         │                           │                       │
│         │                           ▼                       │
│         │                  ┌────────────────────┐          │
│         │                  │   Git Repository   │          │
│         │                  │  (Config Files)    │          │
│         │                  └────────────────────┘          │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────────────┐       │
│  │              Microservices                      │       │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐        │       │
│  │  │Service A│  │Service B│  │Service C│        │       │
│  │  └─────────┘  └─────────┘  └─────────┘        │       │
│  └─────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────┘

Environment Setup

ServiceHostPortRegion
Vault172.xx.xx.xx8090me-central-1
Config Server172.xx.xx.xx8888me-central-1

Vault Setup

Docker Compose Configuration

# docker-compose.yml
version: '3.6'

services:
  vault:
    image: vault:1.13.3
    container_name: vault-server
    restart: always
    ports:
      - 8090:8200
    volumes:
      - ./vault/config:/vault/config
      - ./vault/policies:/vault/policies
      - ./vault/data:/vault/data
      - ./vault/logs:/vault/logs
    environment:
      - VAULT_ADDR=http://127.0.0.1:8200
    cap_add:
      - IPC_LOCK
    entrypoint: vault server -config=/vault/config/vault-config.json

Vault Configuration

// vault/config/vault-config.json
{
  "backend": {
    "file": {
      "path": "vault/data"
    }
  },
  "listener": {
    "tcp": {
      "address": "0.0.0.0:8200",
      "tls_disable": 1
    }
  },
  "ui": true
}

Initialization Process

  1. Access Vault UI at http://host:8090/ui/vault/auth?with=token
  2. Configure unseal parameters:
    • Key shares: 5 (number of unseal keys generated)
    • Key threshold: 3 (minimum keys needed to unseal)
  3. Store root token and unseal keys securely

Unsealing Vault

# Login to vault container
docker exec -it vault-server sh

# Unseal with minimum threshold keys
vault operator unseal <key-1>
vault operator unseal <key-2>
vault operator unseal <key-3>

# Login with root token
vault login <root-token>

Spring Cloud Config Server

Application Configuration

# src/main/resources/application.yml
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: git@bitbucket.org:org/config-repo.git
          default-label: master
          search-paths: '{application}'
        vault:
          host: 172.xx.xx.xx
          port: 8090
          scheme: http
          authentication: TOKEN
          token: ${VAULT_TOKEN}
          kv-version: 2
          backend: secret

server:
  port: 8888

Docker Deployment

# docker-compose.yml for config server
version: '3.6'

services:
  config-server:
    image: config-server:latest
    restart: unless-stopped
    container_name: config-server
    ports:
      - 8888:8888
    environment:
      - VAULT_TOKEN=${VAULT_TOKEN}

Building the Image

# Build JAR
mvn clean install

# Build Docker image
docker build -f docker/Dockerfile \
  --no-cache \
  --build-arg env=local \
  -t config-server:latest .

Dockerfile

FROM openjdk:17-slim

ARG env=local
ENV SPRING_PROFILES_ACTIVE=${env}

WORKDIR /app

COPY target/config-server.jar app.jar
COPY docker/known_hosts /root/.ssh/known_hosts

EXPOSE 8888

ENTRYPOINT ["java", "-jar", "app.jar"]

Vault Token Management

Create Application Token

# Login with root token
vault login token=<root-token>

# Create periodic token for applications
vault token create -period=30d

# Output:
# Key                  Value
# ---                  -----
# token                hvs.EXAMPLE_TOKEN
# token_accessor       ACCESSOR_ID
# token_duration       720h
# token_renewable      true
# token_policies       ["root"]

Store Service Secrets

# Store database credentials
vault kv put secret/service-api \
  spring.datasource.password=<db-password> \
  spring.datasource.username=app_user

# Store multiple secrets
vault kv put secret/service-callback \
  spring.datasource.password=<password> \
  api.key=<api-key>

Client Service Configuration

# bootstrap.yml in client service
spring:
  application:
    name: service-api
  cloud:
    config:
      uri: http://config-server:8888
      token: ${CONFIG_TOKEN}
      fail-fast: true
      retry:
        max-attempts: 5
        max-interval: 3000

Testing Configuration

# Verify config server is responding
curl --location --request GET \
  'http://172.xx.xx.xx:8888/service-api/production/master' \
  --header 'X-Config-Token: <token>'

Troubleshooting

Config Server Issues

  1. SSH Key Problems

    • Update known_hosts file for bitbucket/github
    • Regenerate deploy keys if needed
  2. Vault Connection Failures

    • Verify Vault is unsealed: vault status
    • Check token validity: vault token lookup
    • Verify network connectivity

Common Fixes

# Check container logs
docker logs config-server

# Verify Vault seal status
docker exec vault-server vault status

# Test Vault connectivity from config server
docker exec config-server curl http://vault:8200/v1/sys/health

Production Considerations

High Availability

# Production Vault docker-compose
version: '3.6'
services:
  vault:
    image: registry/vault:latest
    container_name: vault-server
    ports:
      - 8090:8200
    volumes:
      - ./vault/config:/vault/config
      - ./vault/policies:/vault/policies
      - ./vault/data:/vault/data
      - ./vault/logs:/vault/logs
    environment:
      - VAULT_ADDR=http://127.0.0.1:8200
    command: server -config=/vault/config/vault-config.json
    cap_add:
      - IPC_LOCK

Supervisor Configuration

For non-containerized deployments:

# /etc/supervisor/conf.d/vault.conf
[program:vault]
command=/usr/local/bin/vault server -config=/etc/vault/config.hcl
autostart=true
autorestart=true
stderr_logfile=/var/log/vault.err.log
stdout_logfile=/var/log/vault.out.log

Best Practices

  1. Never commit tokens to version control
  2. Use periodic tokens that auto-renew
  3. Implement policies for least-privilege access
  4. Monitor Vault audit logs for security events
  5. Automate unseal with cloud KMS in production