1. Terraform Vault Provider
Terraform Vault Provider cho phép quản lý cấu hình Vault as Code — secrets engines, auth methods, policies, entities, groups, tất cả đều có thể version-controlled trong Git.
Cấu hình Provider
# providers.tf
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 4.0"
}
}
}
provider "vault" {
address = "https://vault.company.com:8200"
# Auth qua VAULT_TOKEN env var hoặc:
# auth_login {
# path = "auth/approle/login"
# parameters = {
# role_id = var.vault_role_id
# secret_id = var.vault_secret_id
# }
# }
}
Quản lý Secrets Engines
# secrets-engines.tf
# KV v2
resource "vault_mount" "kv" {
path = "secret"
type = "kv-v2"
description = "KV v2 secrets engine"
options = {
version = "2"
max_versions = 10
}
}
# Database secrets engine
resource "vault_mount" "database" {
path = "database"
type = "database"
}
resource "vault_database_secret_backend_connection" "postgres" {
backend = vault_mount.database.path
name = "production-postgres"
allowed_roles = ["app-role", "readonly-role"]
postgresql {
connection_url = "postgresql://{{username}}:{{password}}@db.company.com:5432/production"
username = var.db_admin_user
password = var.db_admin_password
}
}
resource "vault_database_secret_backend_role" "app_role" {
backend = vault_mount.database.path
name = "app-role"
db_name = vault_database_secret_backend_connection.postgres.name
default_ttl = 3600
max_ttl = 14400
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 = [
"REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\";",
"DROP ROLE IF EXISTS \"{{name}}\";",
]
}
# PKI
resource "vault_mount" "pki" {
path = "pki"
type = "pki"
max_lease_ttl_seconds = 315360000 # 10 years
}
resource "vault_pki_secret_backend_root_cert" "root" {
backend = vault_mount.pki.path
type = "internal"
common_name = "Company Root CA"
ttl = "315360000"
}
Quản lý Auth Methods
# auth.tf
# Kubernetes auth
resource "vault_auth_backend" "kubernetes" {
type = "kubernetes"
path = "kubernetes"
}
resource "vault_kubernetes_auth_backend_config" "config" {
backend = vault_auth_backend.kubernetes.path
kubernetes_host = "https://kubernetes.default.svc:443"
}
resource "vault_kubernetes_auth_backend_role" "webapp" {
backend = vault_auth_backend.kubernetes.path
role_name = "webapp"
bound_service_account_names = ["webapp-sa"]
bound_service_account_namespaces = ["production"]
token_policies = ["webapp-policy"]
token_ttl = 3600
}
# AppRole
resource "vault_auth_backend" "approle" {
type = "approle"
}
resource "vault_approle_auth_backend_role" "cicd" {
backend = vault_auth_backend.approle.path
role_name = "cicd-pipeline"
token_policies = ["cicd-deploy"]
token_ttl = 1800
secret_id_ttl = 600
secret_id_num_uses = 1
}
Quản lý Policies
# policies.tf
resource "vault_policy" "webapp" {
name = "webapp-policy"
policy = file("policies/webapp.hcl")
}
resource "vault_policy" "cicd" {
name = "cicd-deploy"
policy = <<EOT
path "secret/data/production/*" {
capabilities = ["read"]
}
path "database/creds/app-role" {
capabilities = ["read"]
}
EOT
}
Đọc secrets từ Vault trong Terraform
# Đọc secret để dùng trong Terraform resources
data "vault_kv_secret_v2" "db_creds" {
mount = "secret"
name = "production/db"
}
resource "aws_db_instance" "production" {
engine = "postgres"
username = data.vault_kv_secret_v2.db_creds.data["username"]
password = data.vault_kv_secret_v2.db_creds.data["password"]
# ... other config
}
2. Ansible với Vault
Lookup Plugin
# playbook.yml
- name: Deploy application
hosts: webservers
vars:
db_password: "{{ lookup('hashi_vault', 'secret/data/production/db:password') }}"
api_key: "{{ lookup('hashi_vault', 'secret/data/production/api:key') }}"
tasks:
- name: Configure application
template:
src: app.conf.j2
dest: /etc/app/app.conf
mode: '0640'
vars:
db_host: "{{ lookup('hashi_vault', 'secret/data/production/db:host') }}"
- name: Get dynamic database credentials
set_fact:
db_creds: "{{ lookup('hashi_vault', 'database/creds/deploy-role') }}"
- name: Run database migration
command: ./migrate.sh
environment:
DB_USER: "{{ db_creds.username }}"
DB_PASS: "{{ db_creds.password }}"
Ansible Collection
# Install collection
ansible-galaxy collection install community.hashi_vault
- name: Read KV secret
community.hashi_vault.vault_kv2_get:
url: https://vault.company.com:8200
engine_mount_point: secret
path: production/db
auth_method: approle
role_id: "{{ vault_role_id }}"
secret_id: "{{ vault_secret_id }}"
register: db_secret
- name: Write KV secret
community.hashi_vault.vault_kv2_write:
url: https://vault.company.com:8200
engine_mount_point: secret
path: production/app/deploy-info
data:
version: "{{ app_version }}"
deployed_by: "{{ ansible_user_id }}"
deployed_at: "{{ ansible_date_time.iso8601 }}"
3. CI/CD Integration
GitHub Actions (OIDC — Recommended)
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get secrets from Vault
uses: hashicorp/vault-action@v3
with:
url: https://vault.company.com:8200
method: jwt
path: github-actions
role: my-app
jwtGithubAudience: https://github.com/my-org
exportEnv: true
secrets: |
secret/data/production/db username | DB_USER ;
secret/data/production/db password | DB_PASS ;
secret/data/production/registry token | REGISTRY_TOKEN
- name: Build and push
run: |
echo "$REGISTRY_TOKEN" | docker login -u _token --password-stdin registry.company.com
docker build -t registry.company.com/myapp:${{ github.sha }} .
docker push registry.company.com/myapp:${{ github.sha }}
ArgoCD Vault Plugin
# ArgoCD Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp
spec:
source:
repoURL: https://github.com/company/webapp
path: k8s
plugin:
name: argocd-vault-plugin
env:
- name: AVP_TYPE
value: vault
- name: AVP_AUTH_TYPE
value: k8s
- name: VAULT_ADDR
value: https://vault.company.com:8200
- name: AVP_K8S_ROLE
value: argocd
# k8s/secret.yaml (template)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
annotations:
avp.kubernetes.io/path: "secret/data/production/webapp"
type: Opaque
stringData:
DB_PASSWORD: <password>
API_KEY: <api_key>
External Secrets Operator
# ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-store
spec:
provider:
vault:
server: "https://vault.company.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: external-secrets-sa
---
# ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-db-secret
namespace: production
spec:
refreshInterval: 1m
secretStoreRef:
kind: ClusterSecretStore
name: vault-store
target:
name: app-db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: production/db
property: username
- secretKey: password
remoteRef:
key: production/db
property: password
4. Vault trong GitOps Workflow
┌─────────────┐ Push config ┌──────────────┐
│ Developer │ ───────────────▶ │ Git │
│ │ │ (manifests │
└─────────────┘ │ + secret │
│ placeholders│
└──────┬───────┘
│
ArgoCD / Flux
│
▼
┌──────────────┐
│ AVP / ESO │
│ (resolve │
│ secrets) │
└──────┬───────┘
│
Fetch secrets │
▼
┌──────────────┐
│ Vault │
│ │
└──────────────┘
5. Tổng kết
Terraform Vault Provider — Vault configuration as code, quản lý toàn bộ Vault setup
Ansible — lookup plugin và collection cho operational workflows
GitHub Actions — OIDC-based (no static secrets), vault-action
ArgoCD Vault Plugin — resolve secret placeholders trong GitOps
External Secrets Operator — Kubernetes-native sync từ Vault
Phần cuối cùng sẽ đi vào Production & Enterprise — HA, Integrated Storage, Replication, Namespaces, Monitoring và Troubleshooting.