秘密管理Skill secrets-management

这个技能是关于安全地存储、访问、旋转和保护秘密的全面指南,适用于实现秘密存储、配置秘密旋转、防止秘密泄露或审查凭证处理。关键词包括:秘密管理、云安全、DevOps、密码学、安全运维、CI/CD、云原生。

DevOps 0 次安装 0 次浏览 更新于 3/11/2026

name: 秘密管理 description: 安全的秘密管理全面指南,包括存储解决方案(Vault、AWS Secrets Manager、Azure Key Vault)、环境变量、秘密旋转、扫描工具和CI/CD管道安全。在实现秘密存储、配置秘密旋转、防止秘密泄露或审查凭证处理时使用。 allowed-tools: Read, Glob, Grep, Task, Bash

秘密管理

安全的秘密管理的全面指南,用于安全地存储、访问、旋转和保护秘密。

何时使用此技能

使用此技能时:

  • 选择秘密管理解决方案
  • 实现秘密旋转
  • 防止源代码中的秘密
  • 配置CI/CD管道秘密
  • 设置秘密扫描
  • 审查凭证处理
  • 从不安全秘密存储迁移

秘密管理解决方案

比较矩阵

解决方案 自托管 动态秘密 旋转 成本
HashiCorp Vault 免费(开源)/ $$
AWS Secrets Manager $
Azure Key Vault $
Google Secret Manager $
Doppler $$
环境变量 手动 免费

何时使用何种

用例 推荐解决方案
企业,多云 HashiCorp Vault
AWS原生应用 AWS Secrets Manager
Azure原生应用 Azure Key Vault
GCP原生应用 Google Secret Manager
简单应用 环境变量
开发 .env文件(永不提交!)

HashiCorp Vault

基本用法

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

# 存储一个秘密
vault kv put secret/myapp/database \
    username="dbuser" \
    password="supersecret"

# 读取秘密
vault kv get secret/myapp/database

# 获取特定字段
vault kv get -field=password secret/myapp/database

应用集成(C#)

using System.Text.Json;
using VaultSharp;
using VaultSharp.V1.AuthMethods.Token;

/// <summary>
/// HashiCorp Vault客户端用于秘密检索。
/// </summary>
public sealed class VaultClient
{
    private readonly IVaultClient _client;

    public VaultClient(string url, string token)
    {
        var authMethod = new TokenAuthMethodInfo(token);
        var settings = new VaultClientSettings(url, authMethod);
        _client = new VaultSharp.VaultClient(settings);
    }

    /// <summary>
    /// 从Vault KV v2获取秘密。
    /// </summary>
    public async Task<string> GetSecretAsync(string path, string key, CancellationToken cancellationToken = default)
    {
        var secret = await _client.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: path);
        return secret.Data.Data[key].ToString()!;
    }

    /// <summary>
    /// 获取数据库凭证。
    /// </summary>
    public async Task<DatabaseCredentials> GetDatabaseCredentialsAsync(CancellationToken cancellationToken = default)
    {
        return new DatabaseCredentials(
            Username: await GetSecretAsync("myapp/database", "username", cancellationToken),
            Password: await GetSecretAsync("myapp/database", "password", cancellationToken)
        );
    }
}

public sealed record DatabaseCredentials(string Username, string Password);

// 用法
var vault = new VaultClient(
    url: Environment.GetEnvironmentVariable("VAULT_ADDR")!,
    token: Environment.GetEnvironmentVariable("VAULT_TOKEN")!
);
var dbCreds = await vault.GetDatabaseCredentialsAsync();

动态数据库凭证

# 启用数据库秘密引擎
vault secrets enable database

# 配置PostgreSQL连接
vault write database/config/mydb \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb" \
    allowed_roles="readonly,readwrite" \
    username="vault" \
    password="vault-password"

# 创建角色
vault write database/roles/readonly \
    db_name=mydb \
    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-xxx, password=xxx, lease_id=xxx

详细Vault模式:Vault模式参考

AWS Secrets Manager

存储和检索秘密

using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using System.Text.Json;

/// <summary>
/// AWS Secrets Manager客户端。
/// </summary>
public sealed class AwsSecretsClient(IAmazonSecretsManager client)
{
    /// <summary>
    /// 从AWS Secrets Manager检索秘密。
    /// </summary>
    public async Task<T> GetSecretAsync<T>(string secretName, CancellationToken cancellationToken = default)
    {
        var response = await client.GetSecretValueAsync(
            new GetSecretValueRequest { SecretId = secretName },
            cancellationToken
        );

        return JsonSerializer.Deserialize<T>(response.SecretString)!;
    }
}

// DI用法
public sealed record DbCredentials(string Username, string Password);

// 在Startup/Program.cs
services.AddAWSService<IAmazonSecretsManager>();
services.AddSingleton<AwsSecretsClient>();

// 在应用代码中
var dbCreds = await secretsClient.GetSecretAsync<DbCredentials>("prod/myapp/database");
// 返回: DbCredentials { Username = "dbuser", Password = "secret" }

自动旋转

using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using System.Text.Json;

/// <summary>
/// 创建启用自动旋转的秘密。
/// </summary>
public static async Task CreateSecretWithRotationAsync(
    IAmazonSecretsManager client,
    string secretName,
    object secretValue,
    string rotationLambdaArn,
    int rotationDays = 30,
    CancellationToken cancellationToken = default)
{
    // 创建秘密
    await client.CreateSecretAsync(new CreateSecretRequest
    {
        Name = secretName,
        SecretString = JsonSerializer.Serialize(secretValue)
    }, cancellationToken);

    // 启用旋转(需要Lambda函数)
    await client.RotateSecretAsync(new RotateSecretRequest
    {
        SecretId = secretName,
        RotationLambdaARN = rotationLambdaArn,
        RotationRules = new RotationRulesType
        {
            AutomaticallyAfterDays = rotationDays
        }
    }, cancellationToken);
}

环境变量

最佳实践

# 设置环境变量(不在代码中!)
export DATABASE_URL="postgresql://user:pass@localhost/db"
export API_KEY="sk_live_xxx"

# 在systemd服务文件中
[Service]
Environment="DATABASE_URL=postgresql://user:pass@localhost/db"
EnvironmentFile=/etc/myapp/secrets.env

# 在Docker中
docker run -e DATABASE_URL="postgresql://..." myapp
# 或从文件
docker run --env-file ./secrets.env myapp

# 在Kubernetes中
kubectl create secret generic myapp-secrets \
    --from-literal=DATABASE_URL="postgresql://..." \
    --from-literal=API_KEY="sk_live_xxx"

应用中加载

using Microsoft.Extensions.Configuration;

/// <summary>
/// 从环境变量加载的应用配置。
/// </summary>
public sealed class AppConfig
{
    public required string DatabaseUrl { get; init; }
    public required string ApiKey { get; init; }
    public bool Debug { get; init; }
}

// 在Program.cs或Startup.cs
var configuration = new ConfigurationBuilder()
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>(optional: true)  // 用于开发
    .Build();

// 绑定到强类型配置
services.Configure<AppConfig>(options =>
{
    options.DatabaseUrl = configuration["DATABASE_URL"]
        ?? throw new InvalidOperationException("DATABASE_URL是必需的");
    options.ApiKey = configuration["API_KEY"]
        ?? throw new InvalidOperationException("API_KEY是必需的");
    options.Debug = bool.TryParse(configuration["DEBUG"], out var debug) && debug;
});

// 或使用选项模式
services.AddOptions<AppConfig>()
    .Bind(configuration.GetSection("App"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

// 在应用代码中
public class MyService(IOptions<AppConfig> config)
{
    private readonly AppConfig _config = config.Value;
}

.env文件安全

# .env(永不提交此文件!)
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=sk_live_xxx

# .env.example(提交此文件作为模板)
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=your-api-key-here
# .gitignore - 始终包括
.env
.env.local
.env.*.local
*.pem
*.key
secrets/

秘密旋转

旋转策略

using System.Security.Cryptography;

/// <summary>
/// 秘密旋转,带有重叠期,实现零停机时间旋转。
/// </summary>
public sealed class SecretRotator(ISecretsStore secrets, INotificationClient notifications)
{
    private static readonly TimeSpan GracePeriod = TimeSpan.FromHours(24);

    /// <summary>
    /// 旋转API密钥,带有重叠期。
    /// </summary>
    public async Task<string> RotateApiKeyAsync(string keyName, CancellationToken cancellationToken = default)
    {
        // 1. 生成新密钥
        var newKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
            .Replace('+', '-').Replace('/', '_').TrimEnd('=');

        // 2. 存储新密钥为待定
        await secrets.StoreAsync($"{keyName}_pending", newKey, cancellationToken);

        // 3. 更新主密钥(旧密钥仍然有效)
        var oldKey = await secrets.GetAsync(keyName, cancellationToken);
        await secrets.StoreAsync($"{keyName}_old", oldKey, cancellationToken);
        await secrets.StoreAsync(keyName, newKey, cancellationToken);

        // 4. 通知依赖服务
        await notifications.SendAsync(
            $"API key {keyName} rotated. Update your configuration.",
            cancellationToken
        );

        // 5. 安排旧密钥删除(宽限期)
        await secrets.ScheduleDeletionAsync($"{keyName}_old", GracePeriod, cancellationToken);

        return newKey;
    }

    /// <summary>
    /// 在旋转期间接受旧和新密钥。
    /// </summary>
    public async Task<bool> ValidateDuringRotationAsync(string keyName, string providedKey, CancellationToken cancellationToken = default)
    {
        var current = await secrets.GetAsync(keyName, cancellationToken);
        if (CryptographicOperations.FixedTimeEquals(
            System.Text.Encoding.UTF8.GetBytes(providedKey),
            System.Text.Encoding.UTF8.GetBytes(current)))
        {
            return true;
        }

        var old = await secrets.GetOrDefaultAsync($"{keyName}_old", cancellationToken);
        if (old is not null && CryptographicOperations.FixedTimeEquals(
            System.Text.Encoding.UTF8.GetBytes(providedKey),
            System.Text.Encoding.UTF8.GetBytes(old)))
        {
            return true;
        }

        return false;
    }
}

// 秘密和通知接口
public interface ISecretsStore
{
    Task<string> GetAsync(string key, CancellationToken cancellationToken);
    Task<string?> GetOrDefaultAsync(string key, CancellationToken cancellationToken);
    Task StoreAsync(string key, string value, CancellationToken cancellationToken);
    Task ScheduleDeletionAsync(string key, TimeSpan delay, CancellationToken cancellationToken);
}

public interface INotificationClient
{
    Task SendAsync(string message, CancellationToken cancellationToken);
}

旋转时间线

第0天:生成新密钥,部署到秘密管理器
        ├── 旧密钥:活跃
        └── 新密钥:待定

第1天:更新应用以使用新密钥
        ├── 旧密钥:活跃(宽限期)
        └── 新密钥:活跃

第7天:撤销旧密钥
        ├── 旧密钥:已撤销
        └── 新密钥:活跃

秘密扫描

提交前扫描

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

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

CI/CD扫描

# GitHub Actions
name: 安全扫描
on: [push, pull_request]

jobs:
  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0  # 完整历史记录用于扫描

      - name: Gitleaks扫描
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: TruffleHog扫描
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          extra_args: --only-verified

扫描工具比较

工具 优点 弱点
gitleaks 快速,良好正则模式 可能错过自定义格式
TruffleHog 验证秘密是活的 较慢,网络调用
detect-secrets 基线支持,插件 更多误报
git-secrets AWS模式内置 AWS重点

详细扫描设置:秘密扫描参考

CI/CD管道秘密

GitHub Actions

# 在仓库设置中存储秘密
# 通过 ${{ secrets.SECRET_NAME }} 访问

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 部署
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}
        run: |
          # 秘密作为环境变量可用
          ./deploy.sh

      # 用于OIDC身份验证(云优先)
      - name: 配置AWS凭证
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: us-east-1

GitLab CI

# 存储在设置 > CI/CD > 变量
# 标记为“掩码”和“受保护”

deploy:
  script:
    - echo "使用DB_PASSWORD=$DB_PASSWORD部署"  # 绝不要这样做!
    - ./deploy.sh
  variables:
    # 仅为此作业覆盖
    ENVIRONMENT: production

CI/CD秘密最佳实践

  1. 尽可能使用OIDC - 无长寿命凭证
  2. 在日志中掩码秘密 - CI系统应自动掩码
  3. 限制秘密范围 - 每个环境,每个分支
  4. 审计秘密访问 - 谁访问了什么何时
  5. 定期旋转 - 尤其是在团队变更后

快速决策树

我应该在哪里存储这个秘密?

  1. 生产数据库凭证 → Secrets Manager + 旋转
  2. 第三方服务的API密钥 → Secrets Manager
  3. 加密密钥 → HSM或Vault
  4. 开发凭证 → .env文件(git忽略)
  5. CI/CD部署凭证 → CI/CD秘密 + OIDC
  6. 服务间身份验证 → Vault动态秘密
  7. 用户提交的API密钥 → 加密数据库列

要避免的反模式

绝不要这样做

// 错误:硬编码秘密
const string ApiKey = "sk_live_abc123";
const string DatabaseUrl = "postgresql://admin:password123@prod.db.example.com/app";

// 错误:appsettings.json中的秘密(提交到git)
// {
//   "Database": {
//     "Password": "supersecret"
//   }
// }

// 错误:Docker镜像中的秘密
// COPY secrets.env /app/secrets.env

// 错误:记录秘密
_logger.LogInformation("连接密码: {Password}", password);

// 错误:错误消息中的秘密
throw new Exception($"连接失败: {connectionString}");

// 错误:URL中的秘密
await httpClient.GetAsync($"https://api.example.com?api_key={apiKey}");

要这样做

// 正确:环境变量
var apiKey = Environment.GetEnvironmentVariable("API_KEY")
    ?? throw new InvalidOperationException("API_KEY未配置");

// 正确:秘密管理器
var apiKey = await secretsManager.GetSecretAsync("api-key");

// 正确:配置使用用户秘密(开发)或Azure Key Vault(生产)
var apiKey = configuration["ApiKey"];

// 正确:掩码记录(使用结构化记录)
_logger.LogInformation("连接到数据库...");  // 无凭证

// 正确:通用错误消息
throw new InvalidOperationException("数据库连接失败");  // 无细节

// 正确:秘密在头部(用于API)
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", apiKey);
await httpClient.GetAsync("https://api.example.com");

安全清单

存储

  • [ ] 源代码中无硬编码秘密
  • [ ] 秘密存储在专用秘密管理器中
  • [ ] 配置使用环境变量
  • [ ] .env文件git忽略

访问控制

  • [ ] 秘密的最少权限访问
  • [ ] 启用审计日志记录
  • [ ] 秘密范围到环境
  • [ ] 定期访问审查

旋转

  • [ ] 定义旋转策略
  • [ ] 尽可能自动旋转
  • [ ] 旧秘密的宽限期
  • [ ] 旋转时通知

检测

  • [ ] 提交前钩子用于秘密扫描
  • [ ] CI/CD管道扫描
  • [ ] Git历史记录扫描
  • [ ] 定期仓库审计

CI/CD

  • [ ] 使用CI平台的秘密管理
  • [ ] OIDC用于云身份验证
  • [ ] 日志中掩码秘密
  • [ ] 限制秘密范围

参考

相关技能

技能 关系
cryptography 加密用于静态秘密
devsecops-practices CI/CD安全集成
authentication-patterns API密钥和令牌管理

版本历史

  • v1.0.0 (2025-12-26): 初始发布,包括Vault、云提供商、旋转、扫描

最后更新: 2025-12-26