HashiCorp Vault

JIT Privileged Access & Dynamic Secrets

Vault Secrets JIT Access Project B
0

What Are We Building?

Understanding Vault and why it matters

🔗 Requires: Project A (Authentik SSO) Completed
🔐
Your Secrets Command Center

Vault manages all your passwords, API keys, and credentials. No more secrets in config files!

How Vault Works

👤
You
🔑
Authentik
Login
🏦
Vault
Secrets
🎫
Credential
Temp
💾
Database
Access
🎯

What You'll Have When Done

  • Centralized secrets management (no more passwords in files!)
  • Dynamic database credentials that auto-expire
  • Just-In-Time (JIT) privileged access
  • SSO login via Authentik (OIDC)
  • Complete audit trail of secret access
  • Automatic secret rotation

⚡ The Problem Vault Solves

Without Vault:

  • Passwords stored in config files, environment variables, or code
  • Same password used everywhere, never rotated
  • No idea who accessed what credentials
  • Leaked credentials = compromised forever

With Vault: Credentials are generated on-demand, auto-expire, and every access is logged!

⏱️

Time Investment

Phase Time Difficulty
Understanding Concepts 20-30 min Reading
Vault Installation 30-45 min Easy
Basic Configuration 30-45 min Easy
Secrets Engines 45-60 min Medium
Authentik Integration 30-45 min Medium
JIT Access Setup 45-60 min Medium

Total: 4-5 hours (can be split across multiple sessions)

1

Core Concepts

Understanding Vault before we build

🧠
Learn These First

Vault has specific terminology. Understanding it makes everything easier!

🔐
Secrets
Any sensitive data: passwords, API keys, certificates, tokens
⚙️
Secrets Engine
Plugin that stores, generates, or encrypts secrets (KV, Database, SSH, etc.)
🔑
Auth Method
How users/apps prove identity (OIDC, Token, AppRole, etc.)
📜
Policy
Rules defining what secrets a user can access
🎫
Token
After login, you get a token to access secrets
Lease
Time limit on secrets. Auto-revoked when expired!
📊

Two Types of Secrets

Static Secrets (KV) Dynamic Secrets
You store the secret in Vault Vault generates the secret on-demand
Example: API keys, certificates Example: Database credentials, SSH keys
Doesn't expire automatically Auto-expires after TTL (time-to-live)
Manual rotation needed New credentials each request

What is Just-In-Time (JIT) Access?

Instead of having permanent access to systems, you request access only when needed:

Traditional

Permanent admin password
Never expires
Shared by team

JIT Access

Request access when needed
Auto-expires in 1 hour
Unique per person

💡
Real World Example

Instead of knowing the database password, you ask Vault: "I need database access for 1 hour." Vault creates a temporary user just for you, and deletes it when time expires!

2

Prerequisites

What you need before starting

Before You Begin

Make sure Project A is complete and working

🔗

Required: Project A Complete

  • Authentik running and accessible at https://authentik.yourdomain.com
  • Traefik reverse proxy working with HTTPS
  • Docker and Docker Compose installed
  • At least one user with MFA enabled
⚠️
Don't Skip Project A!

Vault will authenticate users through Authentik. If Authentik isn't working, Vault SSO won't work either.

🖥️

Additional Server Resources

Resource Additional Required Total with Project A
RAM +1 GB ~9 GB minimum
Storage +5 GB ~105 GB minimum
CPU Same 4 cores minimum
📚

Helpful Knowledge (Optional)

You don't need these to complete the guide, but they help understand what's happening:

  • Basic understanding of how databases work
  • Familiarity with JSON format
  • Understanding of environment variables
3

Install Vault

Adding Vault to your Docker stack

⚠️ Run All Commands on Your SERVER

SSH into your server first, then run these commands in the ~/homelab-iam directory.

1
Create Vault Directory Structure
⏱️ 2 min
📁 Create Directories
# Navigate to project directory
cd ~/homelab-iam

# Create Vault directories
mkdir -p vault/{config,data,logs,policies}

# Set permissions (Vault runs as user 100)
sudo chown -R 100:100 vault/data vault/logs

# Verify structure
ls -la vault/
2
Create Vault Configuration
⏱️ 5 min
⚙️ vault-config.hcl
cat > ~/homelab-iam/vault/config/vault-config.hcl << 'EOF'
# ============================================
# HASHICORP VAULT CONFIGURATION
# ============================================

# UI Settings
ui = true

# Listener (how clients connect)
listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_disable   = true  # TLS handled by Traefik
}

# Storage Backend (where secrets are stored)
storage "file" {
  path = "/vault/data"
}

# API Address (for internal communication)
api_addr = "http://127.0.0.1:8200"

# Cluster Address
cluster_addr = "http://127.0.0.1:8201"

# Disable memory lock (for Docker)
disable_mlock = true

# Logging
log_level = "info"
log_file  = "/vault/logs/vault.log"
EOF
💡
File Storage

We're using file storage for simplicity. For production, you'd use Consul, PostgreSQL, or integrated storage (Raft).

3
Add Vault to Docker Compose
⏱️ 5 min

Add this service to your docker-compose.yml file:

🐳 Vault Service Configuration
# Add this to your docker-compose.yml services section
# Edit with: nano ~/homelab-iam/docker-compose.yml

  # ==========================================
  # HASHICORP VAULT - Secrets Management
  # ==========================================
  vault:
    image: hashicorp/vault:1.15
    container_name: vault
    restart: unless-stopped
    cap_add:
      - IPC_LOCK
    environment:
      VAULT_ADDR: "http://127.0.0.1:8200"
      VAULT_API_ADDR: "http://127.0.0.1:8200"
    volumes:
      - ./vault/config:/vault/config:ro
      - ./vault/data:/vault/data
      - ./vault/logs:/vault/logs
      - ./vault/policies:/vault/policies:ro
    command: server -config=/vault/config/vault-config.hcl
    networks:
      - traefik-public
      - authentik-internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vault.rule=Host(\`vault.${DOMAIN}\`)"
      - "traefik.http.routers.vault.entrypoints=websecure"
      - "traefik.http.routers.vault.tls.certresolver=letsencrypt"
      - "traefik.http.services.vault.loadbalancer.server.port=8200"
✏️
Edit the File Carefully!

Add this in the services: section, after the other services. Make sure indentation matches (2 spaces).

4
Start Vault
⏱️ 3 min
🚀 Start the Service
cd ~/homelab-iam

# Pull the Vault image
docker compose pull vault

# Start Vault
docker compose up -d vault

# Check it's running
docker compose ps vault

# Check logs
docker logs vault
5
Initialize Vault (First Time Only)
⏱️ 5 min

Vault starts "sealed" and needs to be initialized. This generates your master keys.

🔑 Initialize and Unseal
# Initialize Vault (generates keys)
docker exec vault vault operator init -key-shares=1 -key-threshold=1

# ⚠️ SAVE THE OUTPUT! You'll see:
# Unseal Key 1: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Initial Root Token: hvs.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Copy those values, then unseal with:
docker exec vault vault operator unseal YOUR_UNSEAL_KEY_HERE

# Check status (should show "Sealed: false")
docker exec vault vault status
🚨
CRITICAL: Save Your Keys!

Write down both the Unseal Key and Root Token immediately!

If you lose these, you lose access to ALL your secrets forever. Store them securely (password manager, safe, etc.).

6
Access Vault Web UI
⏱️ 2 min
  1. Open your browser to: https://vault.yourdomain.com
  2. Select "Token" as the authentication method
  3. Enter your Root Token from Step 5
  4. Click "Sign In"
Vault is Running!

You should see the Vault dashboard. Next, we'll configure secrets engines and authentication.

4

Configure Vault

Setting up secrets engines and policies

⚙️
Basic Configuration

Enable the secrets engines we'll use

1
Login to Vault CLI
⏱️ 1 min
🔐 Authenticate CLI
# Login with root token
docker exec -it vault vault login

# When prompted, paste your Root Token
# You should see: "Success! You are now authenticated."
2
Enable KV Secrets Engine
⏱️ 3 min

KV (Key-Value) engine stores static secrets like API keys and passwords.

📦 Enable KV Engine
# Enable KV secrets engine version 2
docker exec vault vault secrets enable -path=secret kv-v2

# Verify it's enabled
docker exec vault vault secrets list

# Create a test secret
docker exec vault vault kv put secret/test password="mysecretpassword" username="testuser"

# Read the secret back
docker exec vault vault kv get secret/test
💡
KV Version 2

Version 2 includes versioning (keep history of changes) and soft-delete (recover accidentally deleted secrets).

3
Create Access Policies
⏱️ 5 min

Policies control who can access what secrets. Let's create some basic ones.

📜 Admin Policy
# Create admin policy file
cat > ~/homelab-iam/vault/policies/admin-policy.hcl << 'EOF'
# Admin Policy - Full access to secrets
path "secret/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}

path "sys/*" {
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

path "auth/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}
EOF

# Load the policy into Vault
docker exec vault vault policy write admin /vault/policies/admin-policy.hcl
📜 Developer Policy (Read Only)
# Create developer policy file
cat > ~/homelab-iam/vault/policies/developer-policy.hcl << 'EOF'
# Developer Policy - Read access to app secrets
path "secret/data/apps/*" {
  capabilities = ["read", "list"]
}

path "secret/metadata/apps/*" {
  capabilities = ["list"]
}
EOF

# Load the policy
docker exec vault vault policy write developer /vault/policies/developer-policy.hcl

# List all policies
docker exec vault vault policy list
4
Store Some Real Secrets
⏱️ 5 min

Let's organize secrets by application:

🔐 Store Application Secrets
# Store Grafana OAuth secret (from Project A)
docker exec vault vault kv put secret/apps/grafana \
  oauth_client_id="grafana" \
  oauth_client_secret="YOUR_GRAFANA_SECRET"

# Store a generic API key
docker exec vault vault kv put secret/apps/myapp \
  api_key="sk-xxxxxxxxxxxxxxxxxxxx" \
  api_url="https://api.example.com"

# Store database credentials
docker exec vault vault kv put secret/databases/postgres \
  username="app_user" \
  password="supersecretpassword" \
  host="postgres" \
  port="5432"

# List all secrets
docker exec vault vault kv list secret/apps
Basic Configuration Complete!

Vault is now storing secrets. Next, let's set up dynamic secrets and Authentik integration.

5

Dynamic Secrets

Auto-generated credentials that expire

The Power of Dynamic Secrets

Vault creates unique credentials on-demand that automatically expire!

Dynamic Secrets Flow

👤
App/User
🏦
Request
DB creds
🎫
Generate
user_abc123
💾
Database
Creates user

After TTL expires → Vault automatically deletes the user from the database!

1
Enable Database Secrets Engine
⏱️ 5 min
💾 Enable Database Engine
# Enable the database secrets engine
docker exec vault vault secrets enable database

# Verify it's enabled
docker exec vault vault secrets list
2
Configure PostgreSQL Connection
⏱️ 10 min

Tell Vault how to connect to your PostgreSQL database (from Project A):

🔌 Configure Database Connection
# First, get your PostgreSQL password from .env
grep POSTGRES_PASSWORD ~/homelab-iam/.env

# Configure Vault to connect to PostgreSQL
docker exec vault vault write database/config/authentik-db \
  plugin_name="postgresql-database-plugin" \
  allowed_roles="readonly","readwrite" \
  connection_url="postgresql://{{username}}:{{password}}@postgres:5432/authentik?sslmode=disable" \
  username="authentik" \
  password="YOUR_POSTGRES_PASSWORD"
3
Create Database Roles
⏱️ 10 min

Roles define what permissions the generated credentials have:

👤 Read-Only Role
# Create a read-only database role
docker exec vault vault write database/roles/readonly \
  db_name="authentik-db" \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"
👤 Read-Write Role
# Create a read-write database role
docker exec vault vault write database/roles/readwrite \
  db_name="authentik-db" \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="4h"
4
Test Dynamic Credentials
⏱️ 5 min

Generate temporary database credentials:

🎫 Generate Credentials
# Request read-only credentials
docker exec vault vault read database/creds/readonly

# You'll see output like:
# Key                Value
# ---                -----
# lease_id           database/creds/readonly/xxxxx
# lease_duration     1h
# username           v-root-readonly-xxxxx
# password           xxxxxxxxxxxxxxxx

# Test the credentials work
docker exec postgres psql -U v-root-readonly-xxxxx -d authentik -c "SELECT 1;"
🎉
Dynamic Secrets Working!

Every time you request credentials, you get a NEW unique username/password that auto-expires. No more shared passwords!

6

Just-In-Time Access

Request access only when you need it

Zero Standing Privileges

No permanent admin access. Request it, use it, it expires automatically.

1
Create JIT Access Policy
⏱️ 5 min

Policy that allows requesting database credentials with time limits:

📜 JIT Database Policy
# Create JIT policy
cat > ~/homelab-iam/vault/policies/jit-db-policy.hcl << 'EOF'
# JIT Database Access Policy
# Users can request temporary database credentials

# Allow reading database credentials
path "database/creds/readonly" {
  capabilities = ["read"]
}

path "database/creds/readwrite" {
  capabilities = ["read"]
}

# Allow users to check their own leases
path "sys/leases/lookup" {
  capabilities = ["update"]
}

# Allow users to renew their leases
path "sys/leases/renew" {
  capabilities = ["update"]
}
EOF

# Load the policy
docker exec vault vault policy write jit-db-access /vault/policies/jit-db-policy.hcl
2
Create JIT Access Token
⏱️ 3 min
🎫 Create Limited Token
# Create a token with JIT policy (expires in 8 hours)
docker exec vault vault token create \
  -policy="jit-db-access" \
  -ttl="8h" \
  -display-name="jit-user-token"

# Save the token that's output!
3
Simulate JIT Workflow
⏱️ 5 min

Here's how a user would request JIT database access:

🎬 JIT Access Demo
# Step 1: Login with the limited token
docker exec -it vault vault login YOUR_JIT_TOKEN_HERE

# Step 2: Request database credentials (expires in 1 hour)
docker exec vault vault read database/creds/readonly

# Step 3: Use the credentials for your work...

# Step 4: When done, you can manually revoke
docker exec vault vault lease revoke database/creds/readonly/LEASE_ID

# Or just wait - it expires automatically!
💡
Why This Matters

Traditional: "Here's the database password, don't share it" (everyone shares it)
JIT: "Request access when needed, get unique credentials that expire"

4
Monitor Active Access
⏱️ 3 min
📊 View Active Leases
# Log back in as root first
docker exec -it vault vault login

# List all active database leases
docker exec vault vault list sys/leases/lookup/database/creds/readonly

# Get details on a specific lease
docker exec vault vault lease lookup database/creds/readonly/LEASE_ID

# Revoke all database credentials (emergency)
docker exec vault vault lease revoke -prefix database/creds/
7

Authentik Integration

Login to Vault using your Authentik account

🔗
Single Sign-On for Vault

Use your existing Authentik account to login to Vault!

1
Create OIDC Provider in Authentik
⏱️ 5 min
  1. Open Authentik Admin: https://authentik.yourdomain.com
  2. Go to ApplicationsProvidersCreate
  3. Select: OAuth2/OIDC Provider
  4. Fill in:
    Field Value
    Name Vault OIDC Provider
    Authorization flow default-provider-authorization-implicit-consent
    Client type Confidential
    Client ID vault
    Redirect URIs https://vault.yourdomain.com/ui/vault/auth/oidc/oidc/callback
    https://vault.yourdomain.com/oidc/callback
    Scopes openid, profile, email, groups
  5. Click Create, then copy the Client Secret
2
Create Application in Authentik
⏱️ 3 min
  1. Go to ApplicationsApplicationsCreate
  2. Fill in:
    Field Value
    Name Vault
    Slug vault
    Provider Vault OIDC Provider
    Launch URL https://vault.yourdomain.com
  3. Click Create
3
Configure Vault OIDC Auth
⏱️ 10 min
🔑 Enable OIDC Auth Method
# Login as root first
docker exec -it vault vault login

# Enable OIDC auth method
docker exec vault vault auth enable oidc

# Configure OIDC with Authentik
docker exec vault vault write auth/oidc/config \
  oidc_discovery_url="https://authentik.yourdomain.com/application/o/vault/" \
  oidc_client_id="vault" \
  oidc_client_secret="YOUR_CLIENT_SECRET" \
  default_role="default"
👤 Create OIDC Role
# Create default OIDC role
docker exec vault vault write auth/oidc/role/default \
  bound_audiences="vault" \
  allowed_redirect_uris="https://vault.yourdomain.com/ui/vault/auth/oidc/oidc/callback" \
  allowed_redirect_uris="https://vault.yourdomain.com/oidc/callback" \
  user_claim="preferred_username" \
  groups_claim="groups" \
  policies="default,developer"

# Create admin OIDC role (for admins group)
docker exec vault vault write auth/oidc/role/admin \
  bound_audiences="vault" \
  allowed_redirect_uris="https://vault.yourdomain.com/ui/vault/auth/oidc/oidc/callback" \
  allowed_redirect_uris="https://vault.yourdomain.com/oidc/callback" \
  user_claim="preferred_username" \
  groups_claim="groups" \
  bound_claims='{"groups": "admins"}' \
  policies="default,admin"
📝
Replace Placeholders!

Make sure to replace yourdomain.com and YOUR_CLIENT_SECRET with your actual values.

4
Test SSO Login
⏱️ 3 min
  1. Open https://vault.yourdomain.com
  2. Select "OIDC" as the authentication method
  3. Leave Role blank (uses "default") or enter "admin" if you're in admins group
  4. Click "Sign in with OIDC Provider"
  5. You'll be redirected to Authentik - login with your credentials
  6. After authentication, you'll be redirected back to Vault!
🎉
SSO Integration Complete!

You can now login to Vault using your Authentik credentials. No more separate password!

8

Testing & Verification

Make sure everything works

🧪
Test Checklist

Verify all components are working correctly

✅ Verification Checklist

  • Vault UI: https://vault.yourdomain.com loads
  • Token Login: Can login with root token
  • OIDC Login: Can login via Authentik SSO
  • KV Secrets: Can read/write to secret/test
  • Dynamic Secrets: Can generate database credentials
  • Policies: Limited users can't access admin paths
Quick Health Checks
🔍 Diagnostic Commands
# Check Vault status
docker exec vault vault status

# List enabled secrets engines
docker exec vault vault secrets list

# List enabled auth methods
docker exec vault vault auth list

# List policies
docker exec vault vault policy list

# Check Vault logs
docker logs vault --tail=50
🔧

Common Issues

🔴 Vault is Sealed

Cause: Vault seals itself after restart

Solution: Run the unseal command:
docker exec vault vault operator unseal YOUR_UNSEAL_KEY

🔴 OIDC Login Fails

Cause: Redirect URI mismatch or wrong client secret

Solution:
• Check redirect URIs match exactly in both Authentik and Vault
• Verify client secret is correct
• Check Authentik provider scopes include: openid, profile, email, groups

🔴 Permission Denied

Cause: Policy doesn't allow the action

Solution:
• Check which policies are attached to your token
• Use root token for admin tasks
• Update policy to include needed paths

🔴 Database Credentials Fail

Cause: Database connection issue or wrong credentials

Solution:
• Verify PostgreSQL is running: docker compose ps postgres
• Check connection string has correct password
• Ensure database user has permission to create roles

🎉

Congratulations!

Your Vault setup is complete!

🏆
What You've Accomplished

Enterprise-grade secrets management with JIT access in your home lab!

📊

Skills Demonstrated

  • Secrets management with HashiCorp Vault
  • Dynamic credential generation
  • Just-In-Time privileged access patterns
  • OIDC integration for SSO
  • Policy-based access control
  • Audit logging for compliance
🚀

Next Steps

  • Add more applications' secrets to Vault
  • Configure automatic unseal (auto-unseal)
  • Set up secret rotation policies
  • Add SSH secrets engine for SSH certificate management
  • Configure audit logging to your log aggregator
  • Project C: Teleport for Zero Trust Network Access (ZTNA)