Docker Secrets 敏感資料管理

Docker Secrets Sensitive Data Management

Docker Secrets 概述

Docker Secrets 是 Docker 提供的一種安全機制,專門用於管理容器化應用程式中的敏感資料,例如密碼、API 金鑰、TLS 憑證等。與將敏感資訊直接寫入環境變數或映像檔相比,Docker Secrets 提供了更安全的方式來處理這些機密資料。

主要特點

  • 加密傳輸:Secrets 在節點之間透過 TLS 加密傳輸
  • 加密儲存:Secrets 在 Raft log 中以加密形式儲存
  • 最小權限原則:只有被授權的服務才能存取特定的 Secret
  • 記憶體掛載:Secrets 以 tmpfs 檔案系統掛載到容器中,不會寫入磁碟

Swarm Mode 需求

Docker Secrets 原生功能需要 Docker Swarm Mode 才能使用。首先,您需要初始化 Swarm 叢集:

1
2
3
4
5
# 初始化 Swarm(單節點模式)
docker swarm init

# 如果有多個網路介面,需指定廣播地址
docker swarm init --advertise-addr <IP_ADDRESS>

確認 Swarm 狀態:

1
2
docker info | grep Swarm
# 輸出應為:Swarm: active

建立 Secret

Docker 提供兩種方式建立 Secret:

方法一:從標準輸入建立

1
2
3
4
5
# 建立資料庫密碼 Secret
echo "my_super_secure_password" | docker secret create db_password -

# 建立 API 金鑰 Secret
printf "api_key_12345" | docker secret create api_key -

注意:使用 echo 會在字串末尾加入換行符,使用 printf 則不會。

方法二:從檔案建立

1
2
3
# 從檔案建立 Secret
docker secret create ssl_certificate ./server.crt
docker secret create ssl_private_key ./server.key

查看已建立的 Secrets

1
2
3
4
5
# 列出所有 Secrets
docker secret ls

# 查看 Secret 詳細資訊(不會顯示實際內容)
docker secret inspect db_password

使用 Secret

Secrets 透過服務(Service)來使用,而非直接用於容器:

1
2
3
4
5
6
7
8
# 建立服務並掛載 Secret
docker service create \
  --name my_mysql \
  --secret db_password \
  --secret db_root_password \
  -e MYSQL_PASSWORD_FILE=/run/secrets/db_password \
  -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_root_password \
  mysql:8.0

指定 Secret 掛載路徑與權限

1
2
3
4
docker service create \
  --name my_app \
  --secret source=api_key,target=/app/config/api_key,mode=0400 \
  my_app_image:latest

參數說明:

  • source:Secret 名稱
  • target:容器內的掛載路徑
  • mode:檔案權限(八進位格式)

Secret 檔案存取

在容器內部,Secrets 預設掛載於 /run/secrets/ 目錄:

1
2
3
4
5
6
7
8
# 進入容器查看 Secret
docker exec -it <container_id> sh

# 列出所有 Secrets
ls -la /run/secrets/

# 查看 Secret 內容
cat /run/secrets/db_password

應用程式讀取範例

Python 範例

1
2
3
4
5
6
7
8
def get_secret(secret_name):
    try:
        with open(f'/run/secrets/{secret_name}', 'r') as secret_file:
            return secret_file.read().strip()
    except IOError:
        return None

db_password = get_secret('db_password')

Node.js 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const fs = require('fs');

function getSecret(secretName) {
    try {
        return fs.readFileSync(`/run/secrets/${secretName}`, 'utf8').trim();
    } catch (err) {
        return null;
    }
}

const dbPassword = getSecret('db_password');

更新與輪換 Secret

Docker Secrets 是不可變的(immutable),無法直接更新內容。輪換 Secret 需要建立新的 Secret 並更新服務:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. 建立新的 Secret
echo "new_secure_password" | docker secret create db_password_v2 -

# 2. 更新服務使用新 Secret
docker service update \
  --secret-rm db_password \
  --secret-add source=db_password_v2,target=db_password \
  my_mysql

# 3. 確認服務正常運作後,刪除舊 Secret
docker secret rm db_password

Docker Compose 整合

在 Docker Compose(Swarm 模式)中使用 Secrets:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# docker-compose.yml
version: "3.9"

services:
  db:
    image: mysql:8.0
    secrets:
      - db_password
      - db_root_password
    environment:
      MYSQL_PASSWORD_FILE: /run/secrets/db_password
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_USER: app_user
      MYSQL_DATABASE: app_db

  web:
    image: my_web_app:latest
    secrets:
      - source: api_key
        target: /app/config/api_key
        mode: 0400
    depends_on:
      - db

secrets:
  db_password:
    external: true
  db_root_password:
    external: true
  api_key:
    file: ./secrets/api_key.txt

部署到 Swarm:

1
2
3
4
5
6
# 先建立外部 Secrets
echo "password123" | docker secret create db_password -
echo "root_password456" | docker secret create db_root_password -

# 部署 Stack
docker stack deploy -c docker-compose.yml my_stack

非 Swarm 環境替代方案

如果您不使用 Swarm Mode,可考慮以下替代方案:

1. Docker Compose Secrets(開發環境)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# docker-compose.yml(非 Swarm 模式)
version: "3.9"

services:
  app:
    image: my_app:latest
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

注意:這種方式僅適用於開發環境,生產環境應使用專門的密鑰管理工具。

2. 外部密鑰管理工具

  • HashiCorp Vault:企業級密鑰管理解決方案
  • AWS Secrets Manager:AWS 雲端密鑰管理服務
  • Azure Key Vault:Azure 雲端密鑰管理服務
  • Google Secret Manager:GCP 雲端密鑰管理服務

3. 環境變數搭配 .env 檔案

1
2
3
# .env 檔案(加入 .gitignore)
DB_PASSWORD=my_password
API_KEY=my_api_key
1
2
3
4
5
6
# docker-compose.yml
services:
  app:
    image: my_app:latest
    env_file:
      - .env

最佳實踐

  1. 永遠不要將 Secrets 寫入映像檔

    • 避免在 Dockerfile 中使用 COPYADD 複製敏感檔案
    • 避免在 Dockerfile 中使用 ENV 設定敏感資訊
  2. 使用 _FILE 後綴的環境變數

    • 許多官方映像支援 *_FILE 環境變數來讀取 Secret 檔案
    • 例如:MYSQL_PASSWORD_FILEPOSTGRES_PASSWORD_FILE
  3. 最小權限原則

    • 只授予服務必要的 Secrets
    • 使用適當的檔案權限(如 0400
  4. 定期輪換 Secrets

    • 建立 Secret 輪換機制
    • 使用版本命名(如 db_password_v1db_password_v2
  5. 監控與稽核

    • 記錄 Secret 的存取與使用情況
    • 定期審查哪些服務使用了哪些 Secrets
  6. 開發與生產環境分離

    • 開發環境使用測試用的 Secrets
    • 生產環境使用專門的密鑰管理系統

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy