
架構概覽
flowchart TD App[應用服務] -->|讀取 secrets| SM[Secrets Manager<br/>CI Variables / .env.vault] SM --> Encrypted[(加密儲存<br/>Encrypted Storage)] Admin[Admin] -->|定期輪換| SM SM -->|注入| CI[CI Pipeline<br/>Protected Variables] SM -->|注入| Compose[docker-compose<br/>env_file / secrets] Compose --> Container[應用容器] CI --> Container Audit[Audit Log] -.->|追蹤存取紀錄| SM Admin -->|90 天| Rotate[Secrets Rotation<br/>密碼 / API Key / 憑證] Rotate --> SM
Secrets & Config:敏感資訊的管理不能靠運氣
每個應用服務都需要 config(資料庫連線字串、API endpoint、功能開關)和 secrets(密碼、API key、TLS 憑證)。這兩者的管理方式不同:config 可以公開、可以進 Git;secrets 絕對不能進 Git、不能出現在日誌裡。很多團隊在初期用 .env 檔案管理所有東西,直到某天 credential 被推到 Git、或者某個離職員工還知道所有密碼,才發現問題有多嚴重。這篇文章定義 secrets 和 config 的分類、儲存方式、注入方式、以及出事時的處理流程。
架構概覽
flowchart TD Dev[Developer] -->|local .env| LocalApp[Local Development] Git[Git Repo] -->|config only| CI[CI Pipeline] CI -->|inject secrets| Runner[CI Runner] SecretStore[Secret Store\n.env.vault / CI Variables\n/ Docker Secrets] -->|inject| Runner SecretStore -->|inject| ProdHost[Production Host] ProdHost --> Compose[docker-compose\nenvironment / secrets] Compose --> App[Application Container] Admin[Admin] -->|rotate| SecretStore Monitor[Audit Log] -->|track access| SecretStore
開發環境用 local .env,CD 從 Secret Store 注入 secrets 到 runner。Production 透過 docker-compose 的 environment 或 Docker Secrets 注入到容器內。所有 secrets 的存取都有 audit log。
核心概念
-
Config vs Secrets 分類:先搞清楚哪些是 config、哪些是 secrets。Config 是不含敏感資訊的設定值(
PORT=8000、LOG_LEVEL=info、API_BASE_URL=https://api.example.com),可以進 Git、可以寫在文件裡。Secrets 是外洩後會造成安全風險的資訊(資料庫密碼、API key、JWT signing key、TLS private key),絕對不能進 Git。灰色地帶的判斷原則:如果這個值被第三方知道後,可能被用來存取系統或冒充身份,就是 secret。 -
.env 管理規則:
.env檔案是最常見的 secrets 管理方式,但也最容易出事。規則:(1).env必須在.gitignore裡,永遠不進 Git。(2)提供.env.example作為範本,只寫 key 不寫 value(DB_PASSWORD=)。(3)不同環境(dev/staging/prod)用不同的.env檔案,不要共用。(4).env檔案的權限設為 600(只有 owner 可讀寫),避免其他使用者讀取。 -
Secrets 注入方式:有幾種方式把 secrets 注入到應用容器:(1)docker-compose 的
environment段落:最簡單,但 secrets 會出現在docker inspect的輸出裡。(2)Docker Secrets(docker secret create):secrets 以檔案形式 mount 到容器的/run/secrets/目錄,不會出現在inspect裡,但只在 Swarm mode 可用。(3).env檔案搭配env_file指令:方便管理,但要確保.env檔案的權限和位置。(4)CI/CD Variables:GitLab CI 的 Protected/Masked Variables,在 pipeline 中以環境變數注入,不會出現在日誌裡。 -
Secrets Rotation(輪換):密碼和 API key 不是設定一次就永遠不變。定期輪換 secrets 可以降低外洩後的影響範圍。建議的輪換頻率:資料庫密碼每 90 天、API key 每 180 天、TLS 憑證在到期前 30 天自動續期。輪換時要確保新舊 credential 有一段共存期(graceful rotation),避免服務中斷。
使用情境
-
新成員 Onboarding:新開發者 clone repo 後,看到
.env.example知道需要哪些設定值,向 admin 申請各環境的 secrets。Admin 從 Secret Store 建立新的 credentials,不需要把現有的密碼傳給新人。如果新人只開發前端,就只給前端相關的 API key,不給資料庫密碼。 -
成員離職 Offboarding:員工離職後,立即 rotate 該員工有權限存取的所有 secrets。這包括資料庫密碼、CI/CD 的 deploy token、Harbor 的 Robot Account token、以及任何個人帳號的 API key。如果不 rotate,離職員工手上的密碼還是有效的。
-
Secrets 外洩應急:某個
.env檔案被意外 commit 到 Git。處理流程:(1)立即 rotate 所有外洩的 secrets。(2)從 Git history 移除(git filter-repo或 BFG Repo-Cleaner)。(3)通知所有相關人員。(4)檢查 audit log 確認是否有未授權存取。注意:即使從 Git history 移除,如果 repo 曾經是公開的,就要假設 secrets 已經被讀取。
實作範例 / 設定範例
.env.example 範本
# .env.example - 所有需要的環境變數,value 留空
# 複製為 .env 後填入實際值
# === Application ===
APP_PORT=8000
APP_ENV=development
LOG_LEVEL=info
# === Database ===
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=
DB_PASSWORD=
# === Redis ===
REDIS_URL=redis://localhost:6379
# === External APIs ===
PAYMENT_API_KEY=
PAYMENT_API_SECRET=
SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
# === Auth ===
JWT_SECRET=
SESSION_SECRET=.gitignore 設定
# Secrets - 永遠不進 Git
.env
.env.local
.env.*.local
*.pem
*.key
credentials.json
service-account.json
# 允許 .env.example 進 Git
!.env.exampledocker-compose 中的 Secrets 注入
# docker-compose.yml
version: "3.8"
services:
api:
image: registry.example.com/myapp/api:v1.2.0
restart: unless-stopped
env_file:
- .env # 從 .env 檔案載入
environment:
# Config(非敏感)可以直接寫
- APP_PORT=8000
- LOG_LEVEL=info
# Secrets 從 .env 檔案載入,不要寫在這裡
ports:
- "8000:8000"
# 如果使用 Docker Swarm,可以用 Docker Secrets
# api:
# image: registry.example.com/myapp/api:v1.2.0
# secrets:
# - db_password
# - jwt_secret
# environment:
# - DB_PASSWORD_FILE=/run/secrets/db_password
# secrets:
# db_password:
# file: ./secrets/db_password.txt
# jwt_secret:
# file: ./secrets/jwt_secret.txtGitLab CI Variables 設定
# .gitlab-ci.yml
deploy:
stage: deploy
variables:
# 非敏感 config 可以寫在這裡
APP_ENV: production
LOG_LEVEL: warn
script:
# 敏感 secrets 透過 GitLab CI Variables(Settings > CI/CD > Variables)注入
# $DB_PASSWORD, $JWT_SECRET 等在 GitLab UI 設定,標記為 Protected + Masked
- echo "DB_PASSWORD=$DB_PASSWORD" >> .env
- echo "JWT_SECRET=$JWT_SECRET" >> .env
- docker compose --env-file .env up -d
- rm .env # 部署完立即刪除 .env
only:
- tagsSecrets Rotation 腳本
#!/bin/bash
# scripts/rotate-db-password.sh
set -euo pipefail
NEW_PASSWORD=$(openssl rand -base64 32)
DB_HOST=${DB_HOST:-localhost}
DB_USER=${DB_USER:-myapp}
echo "=== Rotating DB password for $DB_USER ==="
# 1. 在 PostgreSQL 更新密碼
PGPASSWORD="$OLD_DB_PASSWORD" psql -h "$DB_HOST" -U postgres -c \
"ALTER USER $DB_USER WITH PASSWORD '$NEW_PASSWORD';"
# 2. 更新 .env 檔案
sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$NEW_PASSWORD/" /opt/myapp/.env
# 3. 重啟應用服務(載入新密碼)
cd /opt/myapp && docker compose restart api worker
# 4. 驗證
sleep 5
curl -sf http://localhost:8000/health || echo "WARNING: Health check failed!"
# 5. 更新 GitLab CI Variable(如果有用的話)
# gitlab-ci-variable-update.sh "$NEW_PASSWORD"
echo "=== Password rotation complete ==="常見問題與風險
-
Secrets 進了 Git:最常見的事故。一旦 push 到遠端 repo(尤其是公開 repo),就要假設 secrets 已經外洩。即使用
git revert也沒用,因為 Git history 裡還是有。避免方式:.gitignore加上所有 secrets 檔案。安裝git-secrets或pre-commit hook在 commit 前掃描是否有 secrets pattern(例如password=、sk-、-----BEGIN PRIVATE KEY-----)。 -
所有環境共用 secrets:dev 和 prod 用同一組資料庫密碼,開發者在 dev 測試時不小心連到 prod 資料庫。避免方式:每個環境有獨立的 secrets,prod 的 secrets 只有少數人能存取。CI/CD 的 Protected Variables 設定只在 protected branch 上可用。
-
Secrets 出現在日誌裡:應用程式的 error log 印出了完整的 DB connection string(包含密碼),或 CI pipeline 的日誌顯示了環境變數。避免方式:應用程式在 log 時要過濾敏感欄位。GitLab CI Variables 標記為
Masked,值會在日誌中被遮蔽。 -
離職員工知道密碼:員工離職後沒有 rotate secrets,離職員工仍然可以用記下來的密碼存取系統。避免方式:Offboarding checklist 包含「rotate 所有該員工有權限的 secrets」,並且 secrets 的存取權限和個人帳號綁定(而不是團隊共用帳號)。
優點
- 明確區分 config 和 secrets,降低外洩風險
.env.example讓新成員快速上手,不需要口頭傳遞設定- Rotation 機制降低 credential 長期暴露的風險
缺點 / 限制
- 純
.env管理沒有版本歷史和 audit log - Docker Secrets 只在 Swarm mode 可用,單機 Compose 無法使用
- 企業級需求(自動 rotation、fine-grained audit)可能需要 HashiCorp Vault 或 AWS Secrets Manager