11 KiB
11 KiB
🔒 安全方案文档
概述
本项目实现了一套完整的敏感信息保护方案,确保密码、证书、Token 等敏感数据在存储和传输过程中的安全性。
🎯 解决的安全问题
1. 硬编码敏感信息
问题:代码中硬编码了 Harbor 密码、K8s 证书等敏感信息
解决方案:全部移至环境变量,通过 .env 文件配置
2. 明文存储密码和证书
问题:数据库中以明文存储敏感数据
解决方案:使用 AES-256-GCM 加密存储
3. API 响应泄露敏感信息
问题:API 返回完整的密码和证书数据
解决方案:自动脱敏,仅返回掩码(••••••••)
4. 前端显示敏感信息
问题:前端表单可能显示原始密码
解决方案:显示掩码,修改时仅支持覆盖
🏗️ 架构设计
┌─────────────────────────────────────────────────────────────┐
│ 前端 (Frontend) │
│ • 显示脱敏数据(••••••••) │
│ • 修改时仅支持覆盖,不能查看原值 │
└─────────────────────────────────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────────┐
│ REST API (Handler 层) │
│ • 接收请求中的明文敏感数据 │
│ • 响应时自动脱敏(调用 ToRegistryResponse/ToClusterResponse) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service 层(业务逻辑) │
│ • 处理业务逻辑 │
│ • 不关心加密/解密细节 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Repository 层(数据持久化) │
│ • Create/Update: 自动加密敏感数据后存储 │
│ • GetByID/List: 自动解密敏感数据后返回 │
│ • 使用 AES-256-GCM 加密算法 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Database(加密存储) │
│ • 密码:加密存储(Base64 编码的密文) │
│ • 证书:加密存储(Base64 编码的密文) │
│ • Token:加密存储(Base64 编码的密文) │
└─────────────────────────────────────────────────────────────┘
🔐 加密实现
加密算法
- 算法:AES-256-GCM (Galois/Counter Mode)
- 密钥长度:256 bits (由用户提供的密钥通过 SHA256 派生)
- 认证加密:提供数据机密性和完整性
- 随机 Nonce:每次加密使用随机 nonce,同样的明文产生不同的密文
加密流程
// 1. 用户配置加密密钥
encryptor := crypto.NewAESEncryptor(config.EncryptionKey)
// 2. Repository 创建时注入加密器
clusterRepo := mock.NewClusterRepositoryMock(encryptor)
registryRepo := mock.NewRegistryRepositoryMock(encryptor)
// 3. 存储时自动加密
func (r *RegistryRepositoryMock) Create(ctx context.Context, registry *entity.Registry) error {
encrypted := r.encryptRegistry(registry) // 加密敏感字段
r.registries[registry.ID] = encrypted
return nil
}
// 4. 读取时自动解密
func (r *RegistryRepositoryMock) GetByID(ctx context.Context, id string) (*entity.Registry, error) {
registry := r.registries[id]
return r.decryptRegistry(registry), nil // 解密敏感字段
}
加密的字段
Registry(镜像仓库)
- ✅
Password- Harbor/镜像仓库密码
Cluster(Kubernetes 集群)
- ✅
CAData- CA 证书 - ✅
CertData- 客户端证书 - ✅
KeyData- 客户端密钥 - ✅
Token- Bearer Token
🎭 脱敏显示
DTO 转换
// Registry 响应 - 自动脱敏
func ToRegistryResponse(registry *entity.Registry) *RegistryResponse {
response := &RegistryResponse{
Username: registry.Username, // 用户名不脱敏
Password: crypto.MaskSensitiveData(registry.Password), // 密码脱敏
HasPassword: registry.Password != "",
}
return response
}
// Cluster 响应 - 自动脱敏
func ToClusterResponse(cluster *entity.Cluster) *ClusterResponse {
response := &ClusterResponse{
CAData: crypto.MaskSensitiveData(cluster.CAData), // ••••••••
CertData: crypto.MaskSensitiveData(cluster.CertData), // ••••••••
KeyData: crypto.MaskSensitiveData(cluster.KeyData), // ••••••••
Token: crypto.MaskSensitiveData(cluster.Token), // ••••••••
HasCAData: cluster.CAData != "",
HasCertData: cluster.CertData != "",
}
return response
}
API 响应示例
{
"id": "registry-123",
"name": "Harbor Production",
"url": "https://harbor.example.com",
"username": "admin",
"password": "••••••••",
"has_password": true
}
🔧 配置指南
1. 生成加密密钥
# 生成强加密密钥
openssl rand -base64 32
# 生成 JWT 密钥
openssl rand -base64 32
2. 创建 .env 文件
# 复制模板
cp backend/.env.example backend/.env
# 编辑配置
nano backend/.env
3. 必填配置项
# 生产环境必须修改这些配置!
ENCRYPTION_KEY=<your-encryption-key-from-openssl>
JWT_SECRET=<your-jwt-secret-from-openssl>
4. 可选:配置默认资源
# 默认用户
DEFAULT_USER_USERNAME=admin
DEFAULT_USER_PASSWORD=your-secure-password
DEFAULT_USER_EMAIL=admin@example.com
# 默认集群
DEFAULT_CLUSTER_NAME=Production K8s
DEFAULT_CLUSTER_HOST=https://k8s.example.com:6443
DEFAULT_CLUSTER_CA_DATA=<base64-encoded-ca-cert>
DEFAULT_CLUSTER_CERT_DATA=<base64-encoded-client-cert>
DEFAULT_CLUSTER_KEY_DATA=<base64-encoded-client-key>
# 默认镜像仓库
DEFAULT_REGISTRY_NAME=Harbor Production
DEFAULT_REGISTRY_URL=https://harbor.example.com
DEFAULT_REGISTRY_USERNAME=admin
DEFAULT_REGISTRY_PASSWORD=your-harbor-password
🚀 使用指南
启动应用
cd backend
# 开发模式(使用 Mock 存储)
make run-mock
# 生产模式(使用实际数据库)
ADAPTER_MODE=prod DATABASE_URL="postgresql://..." make run-prod
验证加密
# 1. 创建一个 Registry
curl -X POST http://localhost:8080/api/v1/registries \
-H "Content-Type: application/json" \
-d '{
"name": "Test Registry",
"url": "https://registry.example.com",
"username": "admin",
"password": "MySecretPassword123"
}'
# 2. 获取 Registry(密码已脱敏)
curl http://localhost:8080/api/v1/registries/<registry-id>
# 响应示例:
# {
# "password": "••••••••", // 已脱敏
# "has_password": true
# }
📝 前端集成
显示脱敏数据
// Registry 表单
<Input
type="password"
value={registry.password} // 显示为 ••••••••
placeholder="修改密码(留空保持不变)"
onChange={(e) => setPassword(e.target.value)}
/>
// 说明文字
{registry.has_password && (
<p className="text-sm text-gray-500">
当前已设置密码(加密存储)。输入新密码以覆盖。
</p>
)}
修改策略
- 查看时:显示掩码
••••••••,不显示实际值 - 修改时:
- 输入新值 → 覆盖原值
- 留空 → 保持原值不变
- 无法查看原值
🔒 安全最佳实践
✅ DO(应该做)
-
生产环境必须使用强密钥
ENCRYPTION_KEY=$(openssl rand -base64 32) JWT_SECRET=$(openssl rand -base64 32) -
妥善保管 .env 文件
- 不要提交到 Git(已加入
.gitignore) - 使用密钥管理服务(如 AWS Secrets Manager, HashiCorp Vault)
- 定期轮换密钥
- 不要提交到 Git(已加入
-
使用 HTTPS
- 生产环境必须启用 TLS/SSL
- 使用有效的 SSL 证书
-
定期审计
- 定期检查访问日志
- 监控异常访问
❌ DON'T(不应该做)
- ❌ 不要在代码中硬编码敏感信息
- ❌ 不要将
.env文件提交到版本控制 - ❌ 不要在日志中打印敏感数据
- ❌ 不要在前端缓存敏感信息
- ❌ 不要使用默认密钥用于生产环境
🧪 测试
加密/解密测试
cd backend
go test ./internal/pkg/crypto -v
集成测试
# 运行所有测试
make test
# 测试加密存储
go test ./internal/adapter/output/persistence/mock -v
📊 性能考虑
- 加密开销:AES-GCM 加密非常快,对性能影响可忽略
- 内存使用:每次读取时解密,不在内存中缓存明文
- 并发安全:Repository 使用 RWMutex 保护并发访问
🆘 故障排查
问题:解密失败
原因:ENCRYPTION_KEY 发生变化
解决方案:
- 确保使用相同的加密密钥
- 如果密钥丢失,需要重新创建所有敏感数据
问题:API 返回空密码
原因:密码未设置或解密失败
解决方案:
- 检查
has_password字段 - 查看后端日志确认是否有解密错误
📚 相关文档
🤝 贡献
如果发现安全问题,请:
- 不要公开披露
- 通过私密渠道联系维护者
- 提供详细的复现步骤
📄 许可证
本项目的安全方案遵循项目主许可证。