密钥泄露是运维事故中后果最严重的类型之一。本文系统梳理服务器环境中密钥与敏感信息的全生命周期管理方案,涵盖环境变量、Vault、容器密钥、Git 安全、密钥轮换、配置中心、审计合规等核心话题,附带可落地的命令与配置示例。


1. 敏感信息管理概述

1.1 什么是敏感信息

在服务器运维语境下,敏感信息包括但不限于:

类别

示例

认证凭据

数据库密码、API Key、OAuth Secret

加密材料

TLS 证书私钥、SSH 私钥、GPG 私钥

连接字符串

Redis URL、MQTT Broker 地址含密码

配置数据

内网 IP 段、第三方服务 Account ID

业务数据

用户 PII、支付密钥、加密盐值

1.2 核心原则

  1. 最小暴露:仅在需要的组件、需要的时刻可见

  2. 静态加密:存储时必须加密(at-rest encryption)

  3. 传输加密:传输时必须走 TLS(in-transit encryption)

  4. 审计追踪:每一次访问和变更必须有日志

  5. 定期轮换:密钥有生命周期,到期必须更换

  6. 零信任:不因"内网"而降低安全标准


2. 环境变量管理

2.1 .env 文件基础

.env 是最常见也最基础的密钥管理方式。适用于开发和小规模部署。

# .env 文件示例
DB_HOST=10.0.1.50
DB_PORT=5432
DB_USER=app_rw
DB_PASSWORD=S3cur3P@ssw0rd!
REDIS_URL=redis://:r3d1s_s3cr3t@10.0.1.60:6379/0
JWT_SECRET=eyJhbGciOiJIUzI1NiJ9.dGVzdA
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

.gitignore 必须包含:

.env
.env.*
!.env.example

2.2 direnv —— 目录级环境变量管理

direnv 根据当前目录自动加载/卸载环境变量,比手动 source 更安全。

# 安装
sudo apt install direnv        # Debian/Ubuntu
brew install direnv             # macOS

# 在 shell 配置中 hook(以 bash 为例)
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
source ~/.bashrc

项目目录下创建 .envrc

# .envrc
dotenv_if_exists .env

# 也可以直接设置
export DB_PASSWORD="S3cur3P@ssw0rd!"
export APP_ENV="production"
# 激活
direnv allow .

# 验证
echo $DB_PASSWORD
# 输出: S3cur3P@ssw0rd!

# 离开目录后自动卸载
cd ..
echo $DB_PASSWORD
# 输出: (空)

安全增强 —— 使用 age 加密 .envrc:

# 安装 age
go install filippo.io/age/cmd/...@latest

# 生成密钥
age-keygen -o ~/.age/key.txt

# 加密
age -r age1xxxxxxxxx -o .envrc.age .envrc

# .envrc 中解密加载
# .envrc
age -d -i ~/.age/key.txt .envrc.age | dotenv_if_exists /dev/stdin

3. HashiCorp Vault

Vault 是企业级密钥管理的事实标准,提供集中式密钥存储、动态密钥生成、自动轮换和完整审计。

3.1 安装与启动

# 添加 GPG key 和仓库(Debian/Ubuntu)
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 vault

# 开发模式启动(仅用于测试)
vault server -dev -dev-root-token-id=root

# 生产模式配置文件 /etc/vault.d/vault.hcl

生产环境配置 vault.hcl

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-node-1"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/tls/server.crt"
  tls_key_file  = "/etc/vault.d/tls/server.key"
}

api_addr     = "https://vault.internal.example.com:8200"
cluster_addr = "https://vault.internal.example.com:8201"

telemetry {
  prometheus_retention_time = "30s"
  disable_hostname          = true
}

ui = true
# 启动服务
sudo systemctl enable vault
sudo systemctl start vault

# 初始化(首次)
export VAULT_ADDR="https://vault.internal.example.com:8200"
vault operator init -key-shares=5 -key-threshold=3
# ⚠️ 输出的 5 个 Unseal Key 和 Initial Root Token 必须安全分发给不同的人

# 解封
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>

3.2 Secret Engine

KV v2 —— 通用密钥存储:

# 启用 KV v2 引擎
vault secrets enable -path=secret kv-v2

# 写入密钥
vault kv put secret/myapp/db \
  host="10.0.1.50" \
  port="5432" \
  username="app_rw" \
  password="S3cur3P@ssw0rd!"

# 读取
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db

# 版本管理
vault kv get -version=2 secret/myapp/db
vault kv rollback -version=1 secret/myapp/db
vault kv metadata delete secret/myapp/db

Transit Engine —— 加密即服务:

# 启用
vault secrets enable transit

# 创建加密密钥
vault write -f transit/keys/myapp-key

# 加密
vault write transit/encrypt/myapp-key \
  plaintext=$(base64 <<< "敏感数据需要加密存储")

# 解密
vault write transit/decrypt/myapp-key \
  ciphertext="vault:v1:xxxxxx"

3.3 认证方法

# AppRole(推荐用于机器对机器认证)
vault auth enable approle

# 创建角色
vault write auth/approle/role/myapp \
  secret_id_ttl=10m \
  token_num_uses=10 \
  token_ttl=20m \
  token_max_ttl=30m \
  secret_id_num_uses=40 \
  policies="myapp-policy"

# 获取 Role ID
vault read auth/approle/role/myapp/role-id

# 获取 Secret ID
vault write -f auth/approle/role/myapp/secret-id

# 使用 AppRole 登录
vault write auth/approle/login \
  role_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  secret_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Kubernetes 认证(K8s Pod 直接认证):

# 启用
vault auth enable kubernetes

# 配置(在 Vault Pod 中执行)
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443"

3.4 策略(Policy)

# myapp-policy.hcl
path "secret/data/myapp/*" {
  capabilities = ["read", "list"]
}

path "secret/data/myapp/db" {
  capabilities = ["read", "list", "create", "update"]
}

path "transit/encrypt/myapp-key" {
  capabilities = ["update"]
}

path "transit/decrypt/myapp-key" {
  capabilities = ["update"]
}

# 禁止访问其他路径
path "secret/data/*" {
  capabilities = ["deny"]
}
vault policy write myapp-policy myapp-policy.hcl

3.5 动态密钥(Dynamic Secrets)

动态密钥是 Vault 最强大的特性 —— 每次请求都生成独立的临时凭据,用完即废。

PostgreSQL 动态密钥:

# 配置数据库连接
vault write database/config/my-postgres \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@10.0.1.50:5432/mydb" \
  allowed_roles="readonly" \
  username="vault_admin" \
  password="vault_admin_password"

# 创建角色
vault write database/roles/readonly \
  db_name=my-postgres \
  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"

# 获取临时凭据
vault read database/creds/readonly
# 输出:
#   username: v-token-readonly-abc123
#   password: A1b2-C3d4-E5f6-...
#   ttl: 1h

4. Docker Secrets

Docker Swarm 内置密钥管理,密钥以 tmpfs 挂载到容器,不写入镜像层。

4.1 创建与使用

# 创建密钥
echo "S3cur3P@ssw0rd!" | docker secret create db_password -

# 从文件创建
docker secret create tls_cert /path/to/cert.pem

# 列出密钥
docker secret ls

# 在 service 中使用
docker service create \
  --name myapp \
  --secret db_password \
  --secret tls_cert \
  myapp:latest

# 容器内密钥挂载在 /run/secrets/
# 应用读取: cat /run/secrets/db_password

4.2 Docker Compose 集成

# docker-compose.yml(Swarm 模式)
version: "3.8"

services:
  app:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
      API_KEY_FILE: /run/secrets/api_key

secrets:
  db_password:
    external: true
  api_key:
    external: true

4.3 非 Swarm 环境替代方案

# docker-compose.yml(非 Swarm,使用 bind mount + tmpfs)
version: "3.8"

services:
  app:
    image: myapp:latest
    tmpfs:
      - /run/secrets
    volumes:
      - ./secrets/db_password.txt:/run/secrets/db_password:ro
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

5. Kubernetes Secrets

5.1 原生 Secret

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: production
type: Opaque
stringData:
  DB_PASSWORD: "S3cur3P@ssw0rd!"
  API_KEY: "ak_xxxxxxxxxxxxxxxx"
  REDIS_URL: "redis://:p@ss@redis:6379/0"
# 创建
kubectl apply -f secret.yaml

# 使用方式 1:环境变量
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: myapp-secrets
        key: DB_PASSWORD
# 使用方式 2:Volume 挂载
volumes:
  - name: secret-volume
    secret:
      secretName: myapp-secrets
containers:
  - name: app
    volumeMounts:
      - name: secret-volume
        mountPath: /etc/secrets
        readOnly: true

5.2 加密静态数据(EncryptionConfiguration)

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}
# 生成加密 key
head -c 32 /dev/urandom | base64

# 重启 API Server 应用配置
# 在 kube-apiserver 启动参数中添加:
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml

# 加密已有的 Secrets
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

5.3 External Secrets Operator(ESO)

ESO 将外部密钥管理系统(Vault、AWS SM、Azure KV 等)与 K8s Secret 同步。

# 安装
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace
# SecretStore 引用 Vault
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "myapp"
          serviceAccountRef:
            name: "myapp-sa"
# ExternalSecret 同步
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-external-secret
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: myapp-secrets
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: secret/myapp/db
        property: password
    - secretKey: API_KEY
      remoteRef:
        key: secret/myapp/api
        property: key

6. Git 安全:防止密钥泄露

6.1 git-secrets(AWS 开源)

# 安装
git clone https://github.com/awslabs/git-secrets.git
cd git-secrets && sudo make install

# 在仓库中初始化
cd /path/to/repo
git secrets --install

# 注册 AWS 默认规则
git secrets --register-aws

# 自定义规则
git secrets --add 'password\s*=\s*.+'
git secrets --add 'PRIVATE KEY'
git secrets --add 'sk-[a-zA-Z0-9]{20,}'

# 扫描已有提交历史
git secrets --scan-history

# 扫描指定文件
git secrets --scan /path/to/file

6.2 BFG Repo-Cleaner —— 清理已泄露的密钥

当密钥已经提交到 Git 历史,必须彻底清除:

# 安装
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar

# 方法 1:删除包含特定文本的文件
java -jar bfg-1.14.0.jar --delete-files "*.env" repo.git

# 方法 2:替换敏感文本
echo "S3cur3P@ssw0rd!" > passwords.txt
java -jar bfg-1.14.0.jar --replace-text passwords.txt repo.git

# 清理引用
cd repo.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive

# 强制推送(⚠️ 协调所有开发者重新 clone)
git push --force --all
git push --force --tags

6.3 pre-commit 钩子自动化

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

  - repo: https://github.com/awslabs/git-secrets
    rev: master
    hooks:
      - id: git-secrets
# 安装 pre-commit
pip install pre-commit
pre-commit install

# 手动运行
pre-commit run --all-files

6.4 Gitleaks CI 集成

# .github/workflows/secret-scan.yml
name: Secret Scan
on: [push, pull_request]

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

7. 密钥轮换

7.1 为什么需要轮换

  • 降低泄露窗口期影响

  • 满足合规要求(PCI-DSS 要求至少 90 天)

  • 人员变动后清理权限

  • 应对零日漏洞

7.2 自动化轮换(Vault Agent Template)

# vault-agent-config.hcl
pid_file = "/var/run/vault/agent.pid"

vault {
  address = "https://vault.internal.example.com:8200"
}

auto_auth {
  method "approle" {
    mount_path = "auth/approle"
    config = {
      role_id_file_path                   = "/etc/vault/role-id"
      secret_id_file_path                 = "/etc/vault/secret-id"
      remove_secret_id_file_after_reading = true
    }
  }

  sink "file" {
    config = {
      path = "/var/run/vault/.vault-token"
    }
  }
}

template {
  source      = "/etc/vault/templates/db.ctmpl"
  destination = "/etc/app/secrets/db.json"
  perms       = 0600
  command     = "systemctl reload myapp"
}

Template 文件 db.ctmpl

{{ with secret "database/creds/readonly" }}
{
  "username": "{{ .Data.username }}",
  "password": "{{ .Data.password }}",
  "ttl": "{{ .Data.ttl }}"
}
{{ end }}

7.3 零停机轮换策略

# 双密钥过渡方案(以数据库密码为例)

# 阶段 1:数据库创建新密码,新旧并存
ALTER USER app_rw PASSWORD 'NewP@ssw0rd!';
-- 同时确保旧密码仍可登录(PostgreSQL 15+ 支持 VALID UNTIL)

# 阶段 2:更新应用配置指向新密码
# Kubernetes 滚动更新
kubectl rollout restart deployment/myapp -n production

# 阶段 3:确认所有 Pod 已使用新密码
kubectl logs -l app=myapp --tail=20 -n production | grep "connected"

# 阶段 4:撤销旧密码
ALTER USER app_rw PASSWORD 'NewP@ssw0rd!';  -- 确认只保留新密码

Kubernetes 滚动更新策略:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0    # 确保零停机
  template:
    spec:
      containers:
        - name: app
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: DB_PASSWORD

8. 配置中心

8.1 Nacos

Nacos 支持配置管理和密钥管理,适用于微服务架构。

# Docker 部署
docker run -d --name nacos \
  -e MODE=standalone \
  -e NACOS_AUTH_ENABLE=true \
  -p 8848:8848 \
  nacos/nacos-server:v2.3.0
# 通过 API 创建加密配置
curl -X POST "http://nacos:8848/nacos/v1/cs/configs" \
  -d "dataId=app-secrets.yaml" \
  -d "group=PRODUCTION_GROUP" \
  -d "tenant=my-namespace" \
  -d "type=yaml" \
  --data-urlencode "content=db:
  password: 'ENC(S3cur3P@ssw0rd!)'
api:
  key: 'ENC(ak_xxxxxxxxxxxxxxxx)'" \
  -d "encryptedDataKey=encryption-key-id"

应用端配置解密(jasypt 集成):

# application.yml
spring:
  cloud:
    nacos:
      config:
        server-addr: nacos:8848
        namespace: my-namespace
        group: PRODUCTION_GROUP

jasypt:
  encryptor:
    password: ${JASYPT_PASSWORD}
    algorithm: PBEWithMD5AndDES

8.2 Apollo

# Apollo 配置(敏感信息用数据库加密存储)
# apollo-portal 的 application.properties
spring.datasource.url=jdbc:mysql://mysql:3306/ApolloPortalDB
spring.datasource.username=apollo
spring.datasource.password=${APOLLO_PORTAL_DB_PASSWORD}
// 应用端读取加密配置
@Value("${app.secret.api-key}")
private String apiKey;  // Apollo 自动解密

8.3 Consul Template

# consul-template-config.hcl
consul {
  address = "consul.internal.example.com:8500"
}

vault {
  address = "https://vault.internal.example.com:8200"
  renew_token = true
}

template {
  source      = "/etc/consul-template/templates/nginx.ctmpl"
  destination = "/etc/nginx/conf.d/app.conf"
  command     = "nginx -s reload"
}

template {
  source      = "/etc/consul-template/templates/app.ctmpl"
  destination = "/etc/app/config.json"
  perms       = 0600
  command     = "systemctl restart myapp"
}

9. 审计与合规

9.1 Vault 审计日志

# 启用审计日志(至少启用两个后端以确保冗余)
vault audit enable file file_path=/var/log/vault/audit.log
vault audit enable syslog tag="vault"

# 审计日志示例输出(已脱敏)
{
  "type": "request",
  "auth": {
    "client_token": "hmac-sha256:xxxxx",
    "accessor": "hmac-sha256:xxxxx",
    "display_name": "approle-myapp",
    "policies": ["myapp-policy"]
  },
  "request": {
    "operation": "read",
    "path": "secret/data/myapp/db",
    "remote_address": "10.0.1.100"
  },
  "response": {
    "data": {
      "data": {
        "password": "hmac-sha256:xxxxx"
      }
    }
  }
}

9.2 合规检查清单

# 定期审计脚本
#!/bin/bash

echo "=== 密钥管理合规审计 $(date +%Y-%m-%d) ==="

# 1. 检查是否有硬编码密钥
echo "[1] 扫描代码中的硬编码密钥..."
gitleaks detect --source /path/to/repos --report-format json --report-path /tmp/gitleaks.json

# 2. 检查 Secret 是否加密存储
echo "[2] 检查 K8s Secret 加密状态..."
kubectl get secrets --all-namespaces -o json | jq '.items[] | select(.metadata.annotations["encryption"] == null) | .metadata.name'

# 3. 检查密钥年龄
echo "[3] 检查过期密钥..."
vault list secret/metadata/myapp/ | while read key; do
  created=$(vault kv metadata get -format=json "secret/myapp/$key" | jq '.data.versions | to_entries | last.value.created_time')
  echo "  $key: created $created"
done

# 4. 检查 TLS 证书过期
echo "[4] TLS 证书过期检查..."
for cert in /etc/ssl/certs/*.pem; do
  expiry=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2)
  echo "  $cert: expires $expiry"
done

9.3 敏感信息分类分级

级别

定义

处理要求

L1 - 绝密

核心加密密钥、根证书

HSM 存储,双人审批,操作审计

L2 - 机密

数据库密码、API Key

Vault 存储,自动轮换,访问审计

L3 - 私密

内部服务地址、配置参数

加密存储,权限控制

L4 - 内部

非敏感配置

标准访问控制


10. 最佳实践与 SOP

10.1 密钥管理 SOP

┌─────────────────────────────────────────────────────┐
│               密钥生命周期 SOP                       │
├─────────────────────────────────────────────────────┤
│ 1. 生成 → 使用密码学安全随机数生成                     │
│ 2. 存储 → 加密存储于 Vault/Secret Manager             │
│ 3. 分发 → 通过安全通道分发,禁止明文传输               │
│ 4. 使用 → 最小权限原则,按需申请                       │
│ 5. 轮换 → 自动化定期轮换(建议 90 天以内)             │
│ 6. 撤销 → 泄露时立即撤销,触发应急流程                 │
│ 7. 审计 → 全程日志记录,定期审查                       │
└─────────────────────────────────────────────────────┘

10.2 应急响应流程

发现泄露 → 评估范围 → 立即撤销 → 生成新密钥
    ↓
更新所有引用 → 验证服务 → 监控异常 → 复盘改进
    ↓
根因分析 → 更新 SOP → 团队培训

应急命令速查:

# 立即撤销 Vault 中的密钥
vault kv metadata delete secret/myapp/leaked-key

# 撤销所有 Token
vault token revoke -prefix auth/approle/role/myapp

# 轮换数据库密码(PostgreSQL)
ALTER USER compromised_user PASSWORD 'new_password';

# 旋转 AWS Access Key
aws iam delete-access-key --user-name app-user --access-key-id AKIAXXXXXXXX
aws iam create-access-key --user-name app-user

# 使 K8s Secret 失效并重新创建
kubectl delete secret myapp-secrets -n production
kubectl apply -f new-secret.yaml

10.3 工具选型建议

场景

推荐方案

备选

单机/小团队

.env + direnv + git-secrets

SOPS + age

Docker Swarm

Docker Secrets

Vault Agent

Kubernetes

ESO + Vault

Sealed Secrets

多云环境

Vault

AWS SM / Azure KV

配置中心

Nacos(加密)

Apollo / Consul

Git 防泄露

gitleaks + pre-commit

git-secrets + truffleHog

证书管理

cert-manager + Vault PKI

Let's Encrypt

10.4 检查清单

  • 所有密钥存储于加密系统,禁止明文存放

  • .gitignore 已排除 .env、密钥文件、证书

  • Git 历史中无泄露的密钥(gitleaks 扫描通过)

  • 密钥有自动轮换机制,轮换周期 ≤ 90 天

  • 审计日志已启用,保留期 ≥ 180 天

  • 最小权限原则已落实,无超权访问

  • 应急流程已文档化并演练过

  • CI/CD 流水线包含密钥扫描步骤

  • 容器镜像中无密钥层(多阶段构建 + 扫描)

  • 传输层全链路 TLS,内部服务 mTLS


总结:密钥管理不是一次性工程,而是持续的安全实践。从环境变量起步,逐步引入 Vault 和自动化轮换,在 Git 流程中嵌入扫描门禁,配合审计和应急流程,才能构建真正可靠的密钥管理体系。