Self-host Vault HashiCorp trên VPS: secrets management cho startup

Chia sẻ bài viết

Mục lục
TL;DR
  • HashiCorp Vault là secret manager open source: API key, password, certificate.
  • Self-host trên VPS 2GB Ubuntu, dùng KV v2 cho secret tĩnh, transit cho encryption.
  • Tokens với policy, TTL, renew - không hard-code password vào .env nữa.
  • Audit log mọi secret access, GDPR compliance.
  • SDK Node/Python/Go - app gọi vault.read() lấy secret runtime, không leak khi git clone.

Startup dev 3 người, mỗi người có .env riêng chứa STRIPE_KEY, DATABASE_URL, RESEND_API_KEY. Share qua Slack? Commit accidentally vào git? Dev cũ rời thì revoke key cách nào? Đây là lý do cần secret manager pro. HashiCorp Vault là gold standard, self-host được trên VPS với 199k/tháng.

Vault không chỉ lưu secret. Nó cấp token có TTL (1 giờ tự expire), audit log mọi access, hỗ trợ dynamic secret (mỗi connection tới Postgres có credential riêng do Vault tạo). Đủ cho startup tới enterprise.

Bài này hướng dẫn cài Vault trên Cloud VPS TND 2GB Ubuntu 24.04, setup KV v2, policy, tích hợp app Node.js/Python. Đủ cho team 3-10 dev.

1. Cài Vault trên VPS

# Add HashiCorp repo
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update
sudo apt install -y vault

vault --version

2. Config Vault server mode

# /etc/vault.d/vault.hcl
ui = true
disable_mlock = true

storage "file" {
  path = "/opt/vault/data"
}

listener "tcp" {
  address = "127.0.0.1:8200"
  tls_disable = 1   # disable vì sẽ có Caddy reverse proxy SSL
}

api_addr = "https://vault.yourdomain.com"
cluster_addr = "https://vault.yourdomain.com:8201"

Lưu ý: dùng storage "file" cho single-node startup. Production critical nên dùng Raft (cluster) hoặc Consul.

sudo mkdir -p /opt/vault/data
sudo chown vault:vault /opt/vault/data

sudo systemctl enable --now vault
sudo systemctl status vault

3. Caddy reverse proxy SSL

# /etc/caddy/Caddyfile
vault.yourdomain.com {
    reverse_proxy 127.0.0.1:8200

    @blocked {
        not remote_ip 1.2.3.4 5.6.7.8   # IP văn phòng
    }
    respond @blocked "Forbidden" 403
}

sudo systemctl reload caddy

4. Initialize và unseal Vault

export VAULT_ADDR=https://vault.yourdomain.com

vault operator init
# Output:
# Unseal Key 1: xxxxx
# Unseal Key 2: xxxxx
# Unseal Key 3: xxxxx
# Unseal Key 4: xxxxx
# Unseal Key 5: xxxxx
# Initial Root Token: hvs.xxxxx

CỰC KỲ QUAN TRỌNG: lưu 5 unseal key + root token ở 5 nơi khác nhau (1Password, Bitwarden, USB key, giấy in). Mất là không recover được data Vault.

# Unseal cần 3/5 key
vault operator unseal 
vault operator unseal 
vault operator unseal 

vault status
# Sealed: false ✓

# Login với root token
vault login hvs.xxxxx

5. Enable KV v2 secret engine

vault secrets enable -path=secret kv-v2

# Lưu secret đầu tiên
vault kv put secret/app/prod 
  stripe_key=sk_live_xxx 
  resend_key=re_xxx 
  database_url=postgres://...

# Đọc lại
vault kv get secret/app/prod
vault kv get -field=stripe_key secret/app/prod

KV v2 có versioning - mỗi update tạo version mới, có thể rollback. Ngon cho audit "secret này thay đổi khi nào, ai sửa".

6. Policy - giới hạn quyền

# app-prod-policy.hcl
path "secret/data/app/prod" {
  capabilities = ["read"]
}

path "secret/data/app/dev" {
  capabilities = ["read", "create", "update"]
}

vault policy write app-prod app-prod-policy.hcl

# Tạo token cho app prod với policy này
vault token create -policy=app-prod -ttl=24h -renewable=true
# Output token: hvs.YYY

App prod dùng token này chỉ đọc được secret/app/prod, không sửa được. Dev dùng token khác có quyền sửa secret/app/dev.

7. Tích hợp app Node.js

npm install node-vault

// vault-client.js
import Vault from 'node-vault';

const vault = Vault({
  endpoint: process.env.VAULT_ADDR,
  token: process.env.VAULT_TOKEN,
});

// Đọc secret runtime
const secrets = await vault.read('secret/data/app/prod');
const stripeKey = secrets.data.data.stripe_key;
const dbUrl = secrets.data.data.database_url;

// Init Stripe
import Stripe from 'stripe';
const stripe = new Stripe(stripeKey);

App chỉ cần VAULT_TOKEN trong .env. Secret thật sự lưu trong Vault. Token rotation 24h tự động qua cron.

8. Tích hợp app Python

pip install hvac

# vault_client.py
import hvac
import os

client = hvac.Client(
    url=os.getenv('VAULT_ADDR'),
    token=os.getenv('VAULT_TOKEN'),
)

# Read
secrets = client.secrets.kv.v2.read_secret_version(
    path='app/prod',
    mount_point='secret',
)
stripe_key = secrets['data']['data']['stripe_key']

# Cache trong memory, refresh mỗi giờ

9. Dynamic secret cho Postgres (advanced)

Vault có thể tạo Postgres user on-the-fly với TTL 1 giờ. Mỗi connection có user/password riêng, audit dễ:

# Enable Postgres secret engine
vault secrets enable database

# Config connection
vault write database/config/myapp-db 
  plugin_name=postgresql-database-plugin 
  allowed_roles="readonly,readwrite" 
  connection_url="postgresql://{{username}}:{{password}}@127.0.0.1:5432/myapp?sslmode=disable" 
  username="vault-admin" 
  password="vault-admin-pwd"

# Define role readonly
vault write database/roles/readonly 
  db_name=myapp-db 
  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"

# Request dynamic credential
vault read database/creds/readonly
# Output: username=v-token-xxx, password=yyy, lease_id=...
# Postgres tự tạo user, expire sau 1h

10. Audit log

# Enable audit log file
vault audit enable file file_path=/var/log/vault-audit.log

# Mọi secret access log vào đây
tail -f /var/log/vault-audit.log | jq .

# Output ví dụ:
# {
#   "type": "request",
#   "auth": {"client_token_accessor": "..."},
#   "request": {"path": "secret/data/app/prod", "operation": "read"},
#   "time": "2026-07-09T..."
# }

Audit log là yêu cầu compliance ISO 27001, SOC 2. Vault built-in từ ngày đầu.

11. Auto-unseal khi VPS restart

Mỗi lần VPS restart, Vault sealed - phải unseal lại thủ công. Cho production có 2 cách:

  • Auto-unseal với cloud KMS: AWS KMS, Cloudflare KMS - free tier OK. Vault auto-unseal khi start.
  • Shamir keys manual: bash script paste 3 key, chạy khi restart. Risk: leak key trong script.
# Auto-unseal với cloud KMS (Cloudflare Workers KV)
# Trong vault.hcl thêm:
seal "transit" {
  address = "https://kms.cloudflare.com"
  token = "cf-token"
  key_name = "vault-unseal"
}

12. Backup Vault data

# Snapshot raft hoặc tar storage file
sudo systemctl stop vault
sudo tar -czf /tmp/vault-backup-$(date +%F).tar.gz /opt/vault
sudo systemctl start vault

# Upload S3
rclone copy /tmp/vault-backup-*.tar.gz r2:my-bucket/vault/

# Restore khi cần
sudo systemctl stop vault
sudo tar -xzf /tmp/vault-backup-2026-07-09.tar.gz -C /
sudo systemctl start vault
# Vault sealed, unseal lại với 3 key
Cloud VPS cho vibe coder

Secret management pro chỉ 199k/tháng VPS

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Vault chạy ổn định trên 2GB RAM cho startup 3-10 dev.

Xem 8 cấu hình Cloud VPS →

FAQ

Vault có miễn phí cho startup không?

Có. Vault Community Edition open source MIT, mọi feature core đều free khi self-host. Vault Enterprise paid chỉ thêm HSM, MFA SAML, replication multi-region - startup không cần.

Vault có thay được Doppler hoặc 1Password không?

Có cho secret management programmatic. Doppler/1Password mạnh hơn ở UX, sync .env tự động. Vault mạnh hơn ở dynamic secret, policy phức tạp, audit log.

VPS RAM tối thiểu cho Vault là bao nhiêu?

1GB chạy được cho startup small. 2GB recommended. Vault ăn ~150MB RAM idle, lên ~300MB khi nhiều client. Storage file thì disk size quan trọng hơn RAM.

Mất tất cả unseal key thì làm sao?

Mất key thì secret trong Vault không recover được. Phải tạo Vault mới, re-input secret từ source khác (1Password backup). Đó là lý do PHẢI lưu key ở 5 nơi khác nhau.

Token TTL hết hạn thì app crash không?

Có nếu không renew. Best practice: app renew token mỗi 50% TTL (vault.tokenRenewSelf API). Hoặc dùng Vault Agent sidecar tự handle.

Vault có hỗ trợ Kubernetes secret không?

Có. Vault có Kubernetes auth method, pod authenticate bằng service account token, lấy secret. Hoặc Vault Secrets Operator inject secret vào K8s Secret object tự động.

2009
15+ năm vận hành liên tục
10+
tập đoàn lớn tin dùng
100+
doanh nghiệp SMB Việt
30 ngày
đổi key lỗi miễn phí
Phần mềm bản quyền chính hãng chúng tôi cung cấp
Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666