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秘密最佳实践
- 尽可能使用OIDC - 无长寿命凭证
- 在日志中掩码秘密 - CI系统应自动掩码
- 限制秘密范围 - 每个环境,每个分支
- 审计秘密访问 - 谁访问了什么何时
- 定期旋转 - 尤其是在团队变更后
快速决策树
我应该在哪里存储这个秘密?
- 生产数据库凭证 → Secrets Manager + 旋转
- 第三方服务的API密钥 → Secrets Manager
- 加密密钥 → HSM或Vault
- 开发凭证 → .env文件(git忽略)
- CI/CD部署凭证 → CI/CD秘密 + OIDC
- 服务间身份验证 → Vault动态秘密
- 用户提交的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