Le Duy Khuong

Chuỗi: vaultwarden-secret-management · Phần 1

Năng suất & công cụ dev

Tại sao cần Secret Manager? — Vượt ra ngoài .env files

Secret dựa trên file là quả bom hẹn giờ. Tìm hiểu tại sao .env thất bại khi scale và khi nào cần chuyển sang vault tập trung.

2026-03-208 phút đọcVI

Phần 1 của 520% hoàn thành

Tại sao cần Secret Manager?

Hầu hết các dự án phần mềm — từ side project cá nhân cho đến hệ thống enterprise — đều cần lưu trữ thông tin nhạy cảm: database password, API key, JWT secret, SSH private key, certificate... Và trong phần lớn trường hợp, cách tiếp cận phổ biến nhất vẫn là file .env nằm ngay trong thư mục dự án.

Khi bạn chỉ có một dự án, một server, một người duy nhất quản lý — .env file hoạt động tốt. Nó đơn giản, trực quan, hầu hết mọi framework đều hỗ trợ sẵn (dotenv, python-dotenv, godotenv...). Nhưng khi hệ thống bắt đầu scale — thêm service, thêm môi trường (dev/staging/production), thêm thành viên trong team — thì .env file trở thành một quả bom hẹn giờ về bảo mật.

Bài viết này phân tích cụ thể tại sao file-based secrets là vấn đề, threat model thực tế mà bạn cần quan tâm, và khi nào nên chuyển sang một giải pháp quản lý bí mật (secret manager) chuyên dụng.


Vấn đề với file-based secrets

1. .env file trên disk — plaintext và dễ commit nhầm

File .envplaintext thuần túy. Bất kỳ ai có quyền đọc file trên hệ thống đều thấy toàn bộ nội dung. Đây không phải vấn đề lý thuyết — nó xảy ra thường xuyên trong thực tế:

# File .env điển hình
DB_PASSWORD=super_secret_password_123
JWT_SECRET=my-jwt-signing-key
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Dù bạn đã thêm .env vào .gitignore, sai lầm vẫn xảy ra:

  • Một thành viên mới trong team quên kiểm tra .gitignore trước khi commit
  • Ai đó rename file thành .env.production nhưng quên thêm pattern mới vào .gitignore
  • Một CI/CD script copy .env vào build artifact
  • git add . trong lúc vội — push lên remote trước khi nhận ra

Theo báo cáo của GitGuardian (2024), hơn 12.8 triệu secrets mới bị phát hiện trên các public repository trong một năm. Con số thực tế trên private repositories còn lớn hơn nhiều.

Một khi secret đã được commit — dù bạn xóa file và commit lại — nó vẫn tồn tại trong git history mãi mãi, trừ khi bạn thực hiện git filter-branch hoặc dùng công cụ như BFG Repo-Cleaner. Và nếu đã push lên remote, bạn phải giả định rằng secret đó đã bị compromise.

2. docker inspect expose environment variables

Khi bạn truyền secrets qua environment variables trong Docker (dù qua -e flag hay env_file trong docker-compose.yml), chúng không được mã hóa trong runtime:

# Bất kỳ ai có Docker socket access đều thấy secrets
docker inspect <container_id> --format='{{json .Config.Env}}'
 
# Output — toàn bộ env vars, bao gồm secrets
["DB_PASSWORD=super_secret_password_123", "JWT_SECRET=my-jwt-signing-key", ...]

Điều này có nghĩa:

  • Bất kỳ process nào có quyền truy cập Docker daemon socket (/var/run/docker.sock) đều đọc được secrets
  • Monitoring tools, log aggregators, hoặc sidecar containers nếu được mount Docker socket sẽ thấy toàn bộ
  • Trong Kubernetes, kubectl describe pod cũng expose env vars tương tự nếu bạn dùng env thay vì secretRef

Docker secrets (docker secret) giải quyết một phần vấn đề này, nhưng chỉ hoạt động trong Docker Swarm mode — không khả dụng cho docker compose thông thường.

3. Không có audit trail

Với file .env, bạn không biết:

  • Ai đã đọc secret nào, vào lúc nào?
  • Secret đã được copy đến đâu?
  • Ai đã thay đổi giá trị, và thay đổi từ giá trị nào sang giá trị nào?
  • Có bao nhiêu bản copy của secret đang tồn tại?

Trong môi trường enterprise hoặc khi cần tuân thủ compliance (SOC 2, ISO 27001, PCI DSS), việc thiếu audit trail là không thể chấp nhận được. Nhưng ngay cả với team nhỏ, khả năng truy vết cũng quan trọng — khi xảy ra sự cố bảo mật, câu hỏi đầu tiên luôn là: "Ai có quyền truy cập cái gì, và từ bao giờ?"

4. Rotation thủ công — quên, sai, không nhất quán

Secret rotation — việc thay đổi password, key theo chu kỳ — là best practice cơ bản trong bảo mật. Nhưng với file-based approach:

Quy trình rotation thủ công:
1. Tạo password mới trên service provider (database, API, v.v.)
2. SSH vào từng server
3. Sửa file .env trên từng server
4. Restart từng service
5. Verify service hoạt động bình thường
6. Lặp lại cho mọi môi trường (dev, staging, production)
7. Cập nhật CI/CD variables
8. Thông báo team members cập nhật local .env

Quy trình này không scale. Với 5 services, 3 môi trường, 10 secrets mỗi service — bạn có 150 nơi cần cập nhật. Thực tế, hầu hết team sẽ:

  • Không rotate secrets theo chu kỳ (quá phiền)
  • Rotate thiếu sót (quên một server, quên CI/CD)
  • Gây downtime vì cập nhật không đồng bộ

5. Phân tán — local, VPS, CI/CD — 3 nơi khác nhau

Một secret điển hình tồn tại ở ít nhất 3 nơi cùng lúc:

Vị tríDạng lưu trữRủi ro
Máy developer (local)File .env trên diskLaptop bị mất/đánh cắp
VPS/ServerFile .env hoặc env varsServer bị compromise
CI/CD (GitHub Actions, GitLab CI)Repository secrets / variablesToken leak, misconfigured permissions

Mỗi bản copy là một attack surface riêng biệt. Và không có cơ chế nào đảm bảo ba nơi này luôn đồng bộ — bạn hoàn toàn có thể có production chạy với password cũ trong khi CI/CD đã dùng password mới.

6. Không có access control

File .env thường chứa tất cả secrets cho một service — database password, API keys, encryption keys, third-party credentials — trong cùng một file. Mọi người có quyền đọc file đều thấy tất cả.

Nguyên tắc Principle of Least Privilege yêu cầu mỗi người/process chỉ được truy cập đúng những gì họ cần. Nhưng với .env file:

  • Backend developer thấy cả database password lẫn Stripe secret key
  • CI/CD pipeline cho testing thấy cả production credentials
  • Junior developer mới join team nhận được toàn bộ secrets giống senior

Không có cách nào phân quyền ở mức từng secret — hoặc bạn có quyền đọc file, hoặc không.


Threat model đơn giản

Trước khi quyết định giải pháp, hãy xác định threat model — ai có thể truy cập secrets của bạn, và trong kịch bản nào?

Kịch bản 1: Laptop bị mất hoặc bị đánh cắp

Nếu disk không được mã hóa (FileVault/BitLocker tắt), kẻ tấn công có toàn bộ .env files trên máy. Ngay cả khi disk đã mã hóa, nếu máy đang ở trạng thái sleep (không shutdown), encryption key có thể vẫn nằm trong RAM.

Hậu quả: Toàn bộ secrets cho mọi dự án trên máy bị lộ.

Kịch bản 2: VPS bị hack

Attacker exploit vulnerability trong ứng dụng web, leo thang đặc quyền, đọc file .env trên server.

# Sau khi có shell access trên server
cat /opt/app/.env
# → Toàn bộ database credentials, API keys, encryption keys
 
# Hoặc đọc từ process environment
cat /proc/<pid>/environ | tr '\0' '\n'

Hậu quả: Attacker có database access, có thể pivot sang các hệ thống khác qua API keys.

Kịch bản 3: CI/CD token leak

GitHub Actions workflow logs có thể vô tình in ra secrets nếu developer không cẩn thận. Hoặc một third-party GitHub Action bị compromise — nó có quyền đọc tất cả repository secrets.

# Nguy hiểm — third-party action có thể đọc secrets
- uses: random-user/some-action@v1
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

Hậu quả: Secrets bị exfiltrate qua third-party code mà bạn không kiểm soát.

Kịch bản 4: Internal threat — thành viên rời team

Khi một thành viên rời team, họ có thể vẫn giữ bản copy của .env file trên máy cá nhân. Nếu bạn không rotate tất cả secrets sau khi họ rời đi, họ vẫn có quyền truy cập hệ thống.

Hậu quả: Cựu thành viên vẫn có thể truy cập database, API, và các hệ thống khác.


OWASP Secret Management Cheat Sheet — 5 nguyên tắc chính

OWASP (Open Web Application Security Project) cung cấp Secrets Management Cheat Sheet với các nguyên tắc quan trọng:

Nguyên tắc 1: Tập trung hóa quản lý secrets

Tất cả secrets PHẢI được lưu trữ tại một nơi duy nhất (single source of truth). Applications truy xuất secrets từ vault khi cần, không lưu local copy.

Nguyên tắc 2: Mã hóa at rest và in transit

Secrets phải được mã hóa khi lưu trữ (encryption at rest) và mã hóa khi truyền tải (encryption in transit — TLS/mTLS). Plaintext secrets trên disk là vi phạm nguyên tắc cơ bản.

Nguyên tắc 3: Access control chi tiết

Áp dụng Principle of Least Privilege — mỗi ứng dụng, mỗi người chỉ được truy cập đúng secrets mà họ cần. Phân quyền ở mức từng secret, không phải từng file.

Nguyên tắc 4: Audit logging toàn diện

Mọi thao tác đọc/ghi/xóa secrets đều phải được ghi log — ai, cái gì, khi nào, từ đâu. Logs phải tamper-resistant và được giám sát.

Nguyên tắc 5: Rotation tự động

Secrets phải được rotate theo chu kỳ và có cơ chế tự động. Khi xảy ra sự cố (suspected breach), phải có khả năng rotate ngay lập tức (emergency rotation).


So sánh approaches

Tiêu chíFile-based (.env)Vault-based (Vaultwarden, HashiCorp Vault)Cloud KMS (AWS SM, GCP SM)
Chi phíMiễn phíMiễn phí (self-host)Tính phí theo API call + storage
Setup phức tạpKhông cần setupTrung bình (Docker deploy)Thấp (managed service)
Mã hóa at restKhông (plaintext)Có (AES-256)Có (HSM-backed)
Access controlFile-levelPer-secret, per-userPer-secret, IAM-integrated
Audit trailKhôngCó (access logs)Có (CloudTrail/Audit Log)
RotationThủ côngThủ công hoặc API-drivenTự động (native rotation)
AvailabilityPhụ thuộc diskPhụ thuộc vault uptime99.99% SLA
Vendor lock-inKhôngKhôngCao (per-cloud)
Offline accessCó (self-host)Không
Team collaborationCopy file qua Slack/emailShared vault, phân quyềnIAM roles
Compliance readinessKhông đạtĐạt (với config đúng)Đạt (built-in)

Khi nào dùng gì?

  • File-based: Side project cá nhân, prototype, hackathon — khi tốc độ phát triển quan trọng hơn bảo mật
  • Vault-based: Team nhỏ-vừa, self-hosted infrastructure, cần kiểm soát data sovereignty
  • Cloud KMS: Enterprise, multi-region, cần SLA cao, đã dùng cloud provider

Khi nào cần chuyển sang vault?

Đây là các dấu hiệu nhận biết cho thấy bạn đã outgrow file-based secrets:

Dấu hiệu định lượng

  • >10 secrets trong một dự án — quản lý thủ công bắt đầu error-prone
  • >1 môi trường (dev + staging + production) — sync thủ công giữa environments
  • >1 người truy cập secrets — không có cách phân quyền
  • >1 server chạy cùng ứng dụng — phải copy .env sang nhiều nơi

Dấu hiệu định tính

  • Bạn đã từng commit nhầm secrets vào git (dù chỉ 1 lần)
  • Team member rời đi và bạn không chắc đã rotate hết secrets
  • Bạn không biết secret nào đang active, secret nào đã expired
  • Compliance hoặc audit yêu cầu bạn chứng minh ai có quyền truy cập cái gì
  • Bạn muốn rotate secrets nhưng ngại vì sợ downtime

Ngưỡng thực tế

Nếu bạn đánh dấu 3 hoặc nhiều hơn trong các dấu hiệu trên — đã đến lúc chuyển sang vault. Chi phí thiết lập (1-2 giờ cho Vaultwarden) nhỏ hơn rất nhiều so với chi phí xử lý một sự cố bảo mật.


Tóm tắt

Vấn đềFile-basedVault-based
Lưu trữPlaintext trên diskMã hóa (AES-256)
Access controlAll-or-nothingPer-secret, per-user
Audit trailKhông cóBuilt-in logging
RotationThủ công, error-proneAPI-driven, có thể tự động
Phân tánNhiều bản copySingle source of truth
ComplianceKhông đạtĐạt yêu cầu cơ bản

Kết luận: File-based secrets là acceptable cho dự án cá nhân nhỏ. Nhưng khi bạn có nhiều hơn một người, một server, hoặc một môi trường — vault-based approach là đầu tư cần thiết. Chi phí setup thấp (đặc biệt với giải pháp self-hosted như Vaultwarden), nhưng giá trị bảo vệ là rất lớn.

LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.