Files
ocdp-go/docs/security/security-implementation.md
mangomqy c5e51ed069 ocdp v1
2025-11-13 02:54:06 +00:00

11 KiB
Raw Blame History

🔒 安全方案文档

概述

本项目实现了一套完整的敏感信息保护方案确保密码、证书、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/镜像仓库密码

ClusterKubernetes 集群)

  • 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应该做

  1. 生产环境必须使用强密钥

    ENCRYPTION_KEY=$(openssl rand -base64 32)
    JWT_SECRET=$(openssl rand -base64 32)
    
  2. 妥善保管 .env 文件

    • 不要提交到 Git已加入 .gitignore
    • 使用密钥管理服务(如 AWS Secrets Manager, HashiCorp Vault
    • 定期轮换密钥
  3. 使用 HTTPS

    • 生产环境必须启用 TLS/SSL
    • 使用有效的 SSL 证书
  4. 定期审计

    • 定期检查访问日志
    • 监控异常访问

DON'T不应该做

  1. 不要在代码中硬编码敏感信息
  2. 不要将 .env 文件提交到版本控制
  3. 不要在日志中打印敏感数据
  4. 不要在前端缓存敏感信息
  5. 不要使用默认密钥用于生产环境

🧪 测试

加密/解密测试

cd backend
go test ./internal/pkg/crypto -v

集成测试

# 运行所有测试
make test

# 测试加密存储
go test ./internal/adapter/output/persistence/mock -v

📊 性能考虑

  • 加密开销AES-GCM 加密非常快,对性能影响可忽略
  • 内存使用:每次读取时解密,不在内存中缓存明文
  • 并发安全Repository 使用 RWMutex 保护并发访问

🆘 故障排查

问题:解密失败

原因ENCRYPTION_KEY 发生变化
解决方案

  1. 确保使用相同的加密密钥
  2. 如果密钥丢失,需要重新创建所有敏感数据

问题API 返回空密码

原因:密码未设置或解密失败
解决方案

  1. 检查 has_password 字段
  2. 查看后端日志确认是否有解密错误

📚 相关文档

🤝 贡献

如果发现安全问题,请:

  1. 不要公开披露
  2. 通过私密渠道联系维护者
  3. 提供详细的复现步骤

📄 许可证

本项目的安全方案遵循项目主许可证。