cover

架構概覽

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 .envCD 從 Secret Store 注入 secrets 到 runner。Production 透過 docker-compose 的 environment 或 Docker Secrets 注入到容器內。所有 secrets 的存取都有 audit log。

核心概念

  1. Config vs Secrets 分類:先搞清楚哪些是 config、哪些是 secrets。Config 是不含敏感資訊的設定值(PORT=8000LOG_LEVEL=infoAPI_BASE_URL=https://api.example.com),可以進 Git、可以寫在文件裡。Secrets 是外洩後會造成安全風險的資訊(資料庫密碼、API key、JWT signing key、TLS private key),絕對不能進 Git。灰色地帶的判斷原則:如果這個值被第三方知道後,可能被用來存取系統或冒充身份,就是 secret。

  2. .env 管理規則.env 檔案是最常見的 secrets 管理方式,但也最容易出事。規則:(1).env 必須在 .gitignore 裡,永遠不進 Git。(2)提供 .env.example 作為範本,只寫 key 不寫 value(DB_PASSWORD=)。(3)不同環境(dev/staging/prod)用不同的 .env 檔案,不要共用。(4).env 檔案的權限設為 600(只有 owner 可讀寫),避免其他使用者讀取。

  3. 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 中以環境變數注入,不會出現在日誌裡。

  4. 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、HarborRobot 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.example

docker-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.txt

GitLab 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:
    - tags

Secrets 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-secretspre-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

延伸閱讀