HashiCorp Vault密钥管理:服务器敏感信息集中管理与动态数据库凭证实战

密码和密钥散落在.env文件、配置文件、代码仓库中,是企业安全的最大隐患之一。HashiCorp Vault提供集中化密钥管理、动态凭证生成和完整审计日志,是保护敏感信息的工业级解决方案。

一、Vault解决的核心问题

  • 密钥集中管理:所有密码、Token、证书统一存储在Vault,不再散落各处
  • 动态凭证:应用启动时从Vault获取临时数据库密码(TTL 1小时),无需在代码中存储静态密码
  • 访问控制:细粒度的策略控制,不同应用只能访问自己需要的密钥
  • 完整审计:所有密钥访问操作记录日志,满足合规要求
  • 密钥轮换:到期自动轮换,减少密钥泄露的影响范围

二、Docker部署Vault

<code">mkdir -p /srv/vault/{config,data,logs} && cd /srv/vault
<code"># config/vault.hcl
ui = true

storage "file" {
  path = "/vault/data"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = false
  tls_cert_file = "/vault/tls/fullchain.pem"
  tls_key_file  = "/vault/tls/privkey.pem"
}

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

log_level = "INFO"
log_file  = "/vault/logs/vault.log"

# 审计日志
audit {
  type = "file"
  options = {
    file_path = "/vault/logs/audit.log"
  }
}
<code"># docker-compose.yml
version: '3.8'

services:
  vault:
    image: hashicorp/vault:1.16
    restart: unless-stopped
    cap_add:
      - IPC_LOCK    # 防止密钥被swap到磁盘
    environment:
      VAULT_ADDR: https://127.0.0.1:8200
    volumes:
      - ./config:/vault/config:ro
      - ./data:/vault/data
      - ./logs:/vault/logs
      - /etc/letsencrypt/live/vault.yourdomain.com:/vault/tls:ro
    ports:
      - "127.0.0.1:8200:8200"
    command: vault server -config=/vault/config/vault.hcl
    healthcheck:
      test: ["CMD", "vault", "status"]
      interval: 30s
      timeout: 10s
      retries: 3
<code">docker compose up -d

三、初始化Vault

<code"># 进入容器初始化Vault(只需执行一次)
export VAULT_ADDR='https://127.0.0.1:8200'
export VAULT_SKIP_VERIFY=true   # 自签名证书时使用

docker exec -it vault-vault-1 vault operator init \
    -key-shares=5 \       # 生成5份解封密钥
    -key-threshold=3      # 需要其中3份才能解封

# 输出(务必安全保存!):
# Unseal Key 1: abc123...
# Unseal Key 2: def456...
# Unseal Key 3: ghi789...
# Unseal Key 4: jkl012...
# Unseal Key 5: mno345...
# Initial Root Token: hvs.XXXXXXXXXXXXXX

# 解封Vault(每次重启后需要执行3次,各使用不同的密钥)
docker exec -it vault-vault-1 vault operator unseal  # 输入密钥1
docker exec -it vault-vault-1 vault operator unseal  # 输入密钥2
docker exec -it vault-vault-1 vault operator unseal  # 输入密钥3

# 使用Root Token登录
export VAULT_TOKEN="hvs.XXXXXXXXXXXXXX"
docker exec -e VAULT_TOKEN=$VAULT_TOKEN vault-vault-1 vault status

四、KV密钥存储

<code"># 启用KV v2密钥引擎
vault secrets enable -path=secret kv-v2

# 存储应用密钥
vault kv put secret/myapp/database \
    host="数据库内网IP" \
    port="3306" \
    name="appdb" \
    username="appuser" \
    password="数据库密码"

vault kv put secret/myapp/redis \
    host="127.0.0.1" \
    port="6379" \
    password="Redis密码"

vault kv put secret/myapp/stripe \
    secret_key="sk_live_xxxxx" \
    webhook_secret="whsec_xxxxx"

# 读取密钥
vault kv get secret/myapp/database
vault kv get -field=password secret/myapp/database

# 查看历史版本
vault kv metadata get secret/myapp/database

五、动态数据库凭证

<code"># 启用数据库密钥引擎
vault secrets enable database

# 配置MySQL连接
vault write database/config/myapp-mysql \
    plugin_name=mysql-database-plugin \
    connection_url="{{username}}:{{password}}@tcp(数据库内网IP:3306)/" \
    allowed_roles="myapp-readonly,myapp-readwrite" \
    username="vault-manager" \
    password="管理用户密码"

# 在MySQL中创建Vault管理用户
# mysql -u root -p -e "
# CREATE USER 'vault-manager'@'%' IDENTIFIED BY '管理用户密码';
# GRANT CREATE USER, GRANT OPTION ON *.* TO 'vault-manager'@'%';
# GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'vault-manager'@'%' WITH GRANT OPTION;
# FLUSH PRIVILEGES;"

# 创建只读角色(TTL 1小时,最长8小时)
vault write database/roles/myapp-readonly \
    db_name=myapp-mysql \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON appdb.* TO '{{name}}'@'%';" \
    revocation_statements="DROP USER IF EXISTS '{{name}}'@'%';" \
    default_ttl=1h \
    max_ttl=8h

# 读写角色
vault write database/roles/myapp-readwrite \
    db_name=myapp-mysql \
    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO '{{name}}'@'%';" \
    revocation_statements="DROP USER IF EXISTS '{{name}}'@'%';" \
    default_ttl=1h \
    max_ttl=8h

# 获取动态凭证(每次获取都生成新的临时用户)
vault read database/creds/myapp-readonly
# 输出:
# lease_duration: 1h
# username: v-appuser-AbCdEf12345
# password: 临时随机密码

六、策略管理(访问控制)

<code"># 创建应用专用策略(只能读取自己的密钥)
cat > /tmp/myapp-policy.hcl << 'EOF'
# 允许读取应用密钥
path "secret/data/myapp/*" {
    capabilities = ["read", "list"]
}

# 允许获取动态数据库凭证
path "database/creds/myapp-readwrite" {
    capabilities = ["read"]
}

# 允许续签自己的Token
path "auth/token/renew-self" {
    capabilities = ["update"]
}
EOF

vault policy write myapp-policy /tmp/myapp-policy.hcl

# 创建应用Token(绑定策略)
vault token create \
    -policy=myapp-policy \
    -ttl=24h \
    -renewable=true \
    -display-name=myapp-production

七、PHP应用集成Vault

<code"><?php // 从Vault获取数据库密码(动态凭证) class VaultClient { private string $vaultAddr; private string $token; public function __construct(string $vaultAddr, string $token) { $this->vaultAddr = rtrim($vaultAddr, '/');
        $this->token     = $token;
    }

    public function getDatabaseCredentials(): array {
        $response = $this->request('GET', '/v1/database/creds/myapp-readwrite');

        return [
            'username' => $response['data']['username'],
            'password' => $response['data']['password'],
            'lease_id' => $response['lease_id'],
            'ttl'      => $response['lease_duration'],
        ];
    }

    public function getSecret(string $path): array {
        $response = $this->request('GET', "/v1/secret/data/{$path}");
        return $response['data']['data'];
    }

    private function request(string $method, string $path): array {
        $ch = curl_init("{$this->vaultAddr}{$path}");
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_HTTPHEADER     => ["X-Vault-Token: {$this->token}"],
            CURLOPT_TIMEOUT        => 5,
        ]);
        $body = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($code !== 200) {
            throw new RuntimeException("Vault请求失败:HTTP {$code}");
        }

        return json_decode($body, true);
    }
}

// 使用示例
$vault = new VaultClient(
    'https://vault.yourdomain.com:8200',
    $_ENV['VAULT_TOKEN']
);

// 获取临时数据库凭证
$creds = $vault->getDatabaseCredentials();
$pdo = new PDO(
    "mysql:host=数据库IP;dbname=appdb;charset=utf8mb4",
    $creds['username'],
    $creds['password']
);

// 获取Stripe密钥
$stripe = $vault->getSecret('myapp/stripe');
\Stripe\Stripe::setApiKey($stripe['secret_key']);

八、总结

Vault将密钥管理从"散落在各种配置文件里"升级为"集中管控、动态生成、全程审计"的企业级方案。动态数据库凭证是最有价值的功能——即使某个应用的Vault Token泄露,攻击者获取的数据库密码也只有1小时有效期。IDC.Net的香港VPS提供完整的安全基础设施,配合Vault可以建立符合金融和医疗行业合规要求的密钥管理体系。

THE END