# 🔒 安全方案文档

## 概述

本项目实现了一套完整的敏感信息保护方案，确保密码、证书、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，同样的明文产生不同的密文

### 加密流程

```go
// 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 转换

```go
// 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 响应示例

```json
{
  "id": "registry-123",
  "name": "Harbor Production",
  "url": "https://harbor.example.com",
  "username": "admin",
  "password": "••••••••",
  "has_password": true
}
```

## 🔧 配置指南

### 1. 生成加密密钥

```bash
# 生成强加密密钥
openssl rand -base64 32

# 生成 JWT 密钥
openssl rand -base64 32
```

### 2. 创建 .env 文件

```bash
# 复制模板
cp backend/.env.example backend/.env

# 编辑配置
nano backend/.env
```

### 3. 必填配置项

```bash
# 生产环境必须修改这些配置！
ENCRYPTION_KEY=<your-encryption-key-from-openssl>
JWT_SECRET=<your-jwt-secret-from-openssl>
```

### 4. 可选：配置默认资源

```bash
# 默认用户
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
```

## 🚀 使用指南

### 启动应用

```bash
cd backend

# 开发模式（使用 Mock 存储）
make run-mock

# 生产模式（使用实际数据库）
ADAPTER_MODE=prod DATABASE_URL="postgresql://..." make run-prod
```

### 验证加密

```bash
# 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
# }
```

## 📝 前端集成

### 显示脱敏数据

```typescript
// 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. **生产环境必须使用强密钥**
   ```bash
   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. ❌ 不要使用默认密钥用于生产环境

## 🧪 测试

### 加密/解密测试

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

### 集成测试

```bash
# 运行所有测试
make test

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

## 📊 性能考虑

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

## 🆘 故障排查

### 问题：解密失败

**原因**：`ENCRYPTION_KEY` 发生变化  
**解决方案**：
1. 确保使用相同的加密密钥
2. 如果密钥丢失，需要重新创建所有敏感数据

### 问题：API 返回空密码

**原因**：密码未设置或解密失败  
**解决方案**：
1. 检查 `has_password` 字段
2. 查看后端日志确认是否有解密错误

## 📚 相关文档

- [AES-GCM 加密算法](https://en.wikipedia.org/wiki/Galois/Counter_Mode)
- [Go crypto 包文档](https://pkg.go.dev/crypto)
- [OWASP 安全编码实践](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)

## 🤝 贡献

如果发现安全问题，请：
1. 不要公开披露
2. 通过私密渠道联系维护者
3. 提供详细的复现步骤

## 📄 许可证

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

