667 lines
18 KiB
Markdown
667 lines
18 KiB
Markdown
|
|
# 安全解决方案(P0问题修复)
|
|||
|
|
|
|||
|
|
> 版本:v1.0
|
|||
|
|
> 日期:2026-03-18
|
|||
|
|
> 目的:系统性解决评审发现的安全P0问题
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 计费数据防篡改机制
|
|||
|
|
|
|||
|
|
### 1.1 当前问题
|
|||
|
|
|
|||
|
|
- 只有 usage_records 表,缺乏完整性校验
|
|||
|
|
- 无防篡改审计日志
|
|||
|
|
- 无法追溯数据变更
|
|||
|
|
|
|||
|
|
### 1.2 解决方案
|
|||
|
|
|
|||
|
|
#### 1.2.1 双重记账设计
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 双重记账:借方和贷方必须平衡
|
|||
|
|
class DoubleEntryBilling:
|
|||
|
|
def record_billing(self, transaction: Transaction):
|
|||
|
|
# 1. 借方:用户账户余额
|
|||
|
|
self.debit(
|
|||
|
|
account_type='user_balance',
|
|||
|
|
account_id=transaction.user_id,
|
|||
|
|
amount=transaction.amount,
|
|||
|
|
currency=transaction.currency
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 2. 贷方:收入账户
|
|||
|
|
self.credit(
|
|||
|
|
account_type='revenue',
|
|||
|
|
account_id='platform_revenue',
|
|||
|
|
amount=transaction.amount,
|
|||
|
|
currency=transaction.currency
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 3. 验证平衡
|
|||
|
|
assert self.get_balance('user', transaction.user_id) + \
|
|||
|
|
self.get_balance('revenue', 'platform_revenue') == 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2.2 审计日志表
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- PostgreSQL 版本:计费审计日志表
|
|||
|
|
CREATE TABLE IF NOT EXISTS billing_audit_log (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
record_id BIGINT NOT NULL,
|
|||
|
|
table_name VARCHAR(50) NOT NULL,
|
|||
|
|
operation VARCHAR(20) NOT NULL,
|
|||
|
|
old_value JSONB,
|
|||
|
|
new_value JSONB,
|
|||
|
|
operator_id BIGINT NOT NULL,
|
|||
|
|
operator_ip INET,
|
|||
|
|
operator_role VARCHAR(50),
|
|||
|
|
request_id VARCHAR(64),
|
|||
|
|
record_hash CHAR(64) NOT NULL,
|
|||
|
|
previous_hash CHAR(64),
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_log_record_id
|
|||
|
|
ON billing_audit_log (record_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_log_operator_id
|
|||
|
|
ON billing_audit_log (operator_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_log_created_at
|
|||
|
|
ON billing_audit_log (created_at DESC);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_billing_audit_log_request_id
|
|||
|
|
ON billing_audit_log (request_id);
|
|||
|
|
|
|||
|
|
-- PostgreSQL 触发器:自动记录变更(示例)
|
|||
|
|
CREATE OR REPLACE FUNCTION fn_audit_supply_usage_update()
|
|||
|
|
RETURNS trigger
|
|||
|
|
LANGUAGE plpgsql
|
|||
|
|
AS $$
|
|||
|
|
DECLARE
|
|||
|
|
v_prev_hash CHAR(64);
|
|||
|
|
BEGIN
|
|||
|
|
SELECT record_hash
|
|||
|
|
INTO v_prev_hash
|
|||
|
|
FROM billing_audit_log
|
|||
|
|
WHERE record_id = OLD.id
|
|||
|
|
ORDER BY id DESC
|
|||
|
|
LIMIT 1;
|
|||
|
|
|
|||
|
|
INSERT INTO billing_audit_log (
|
|||
|
|
record_id,
|
|||
|
|
table_name,
|
|||
|
|
operation,
|
|||
|
|
old_value,
|
|||
|
|
new_value,
|
|||
|
|
operator_id,
|
|||
|
|
operator_ip,
|
|||
|
|
operator_role,
|
|||
|
|
request_id,
|
|||
|
|
record_hash,
|
|||
|
|
previous_hash
|
|||
|
|
)
|
|||
|
|
VALUES (
|
|||
|
|
OLD.id,
|
|||
|
|
'supply_usage_records',
|
|||
|
|
'UPDATE',
|
|||
|
|
to_jsonb(OLD),
|
|||
|
|
to_jsonb(NEW),
|
|||
|
|
COALESCE(NULLIF(current_setting('app.operator_id', true), ''), '0')::BIGINT,
|
|||
|
|
NULLIF(current_setting('app.operator_ip', true), '')::INET,
|
|||
|
|
NULLIF(current_setting('app.operator_role', true), ''),
|
|||
|
|
NULLIF(current_setting('app.request_id', true), ''),
|
|||
|
|
encode(digest(to_jsonb(NEW)::text, 'sha256'), 'hex'),
|
|||
|
|
v_prev_hash
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
RETURN NEW;
|
|||
|
|
END;
|
|||
|
|
$$;
|
|||
|
|
|
|||
|
|
DROP TRIGGER IF EXISTS trg_usage_before_update ON supply_usage_records;
|
|||
|
|
CREATE TRIGGER trg_usage_before_update
|
|||
|
|
BEFORE UPDATE ON supply_usage_records
|
|||
|
|
FOR EACH ROW
|
|||
|
|
EXECUTE FUNCTION fn_audit_supply_usage_update();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2.3 实时对账机制
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class BillingReconciliation:
|
|||
|
|
def hourly_reconciliation(self):
|
|||
|
|
"""小时级对账"""
|
|||
|
|
# 1. 获取计费记录
|
|||
|
|
billing_records = self.get_billing_records(
|
|||
|
|
start_time=self.hour_ago,
|
|||
|
|
end_time=datetime.now()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 2. 获取用户消费记录
|
|||
|
|
usage_records = self.get_usage_records(
|
|||
|
|
start_time=self.hour_ago,
|
|||
|
|
end_time=datetime.now()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 3. 比对
|
|||
|
|
discrepancies = []
|
|||
|
|
for billing, usage in zip(billing_records, usage_records):
|
|||
|
|
if not self.is_match(billing, usage):
|
|||
|
|
discrepancies.append({
|
|||
|
|
'billing_id': billing.id,
|
|||
|
|
'usage_id': usage.id,
|
|||
|
|
'difference': billing.amount - usage.amount
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 4. 告警
|
|||
|
|
if discrepancies:
|
|||
|
|
self.send_alert('billing_discrepancy', discrepancies)
|
|||
|
|
|
|||
|
|
def real_time_verification(self):
|
|||
|
|
"""实时验证(请求级别)"""
|
|||
|
|
# 每个请求完成后立即验证
|
|||
|
|
request = self.get_current_request()
|
|||
|
|
expected_cost = self.calculate_cost(request.usage)
|
|||
|
|
actual_cost = self.get_billing_record(request.id).amount
|
|||
|
|
|
|||
|
|
# 允许0.1%误差
|
|||
|
|
if abs(expected_cost - actual_cost) > expected_cost * 0.001:
|
|||
|
|
raise BillingAccuracyError(f"计费差异: {expected_cost} vs {actual_cost}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 跨租户隔离强化
|
|||
|
|
|
|||
|
|
### 2.1 当前问题
|
|||
|
|
|
|||
|
|
- team_id 和 organization_id 字段存在
|
|||
|
|
- 但缺乏强制验证和行级安全
|
|||
|
|
|
|||
|
|
### 2.2 解决方案
|
|||
|
|
|
|||
|
|
#### 2.2.1 强制租户上下文验证
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class TenantContextMiddleware:
|
|||
|
|
def process_request(self, request):
|
|||
|
|
# 1. 从Token提取租户ID
|
|||
|
|
tenant_id = self.extract_tenant_id(request.token)
|
|||
|
|
|
|||
|
|
# 2. 从URL/Header强制验证
|
|||
|
|
if request.tenant_id and request.tenant_id != tenant_id:
|
|||
|
|
raise TenantMismatchError()
|
|||
|
|
|
|||
|
|
# 3. 强制设置租户上下文
|
|||
|
|
request.tenant_id = tenant_id
|
|||
|
|
|
|||
|
|
# 4. 存储到请求上下文
|
|||
|
|
self.set_context('tenant_id', tenant_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2.2 数据库行级安全(RLS)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 启用行级安全
|
|||
|
|
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
|
|||
|
|
|
|||
|
|
-- 创建策略:用户只能访问自己的Key
|
|||
|
|
CREATE POLICY api_keys_tenant_isolation
|
|||
|
|
ON api_keys
|
|||
|
|
FOR ALL
|
|||
|
|
USING (tenant_id = current_setting('app.tenant_id')::BIGINT);
|
|||
|
|
|
|||
|
|
-- 对所有敏感表启用RLS
|
|||
|
|
ALTER TABLE billing_records ENABLE ROW LEVEL SECURITY;
|
|||
|
|
ALTER TABLE usage_records ENABLE ROW LEVEL SECURITY;
|
|||
|
|
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2.3 敏感操作二次验证
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class SensitiveOperationGuard:
|
|||
|
|
# 需要二次验证的操作
|
|||
|
|
SENSITIVE_ACTIONS = [
|
|||
|
|
'billing.write', # 写账单
|
|||
|
|
'admin.tenant_write', # 租户管理
|
|||
|
|
'provider.withdraw', # 供应方提现
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
def verify(self, user_id, action, context):
|
|||
|
|
if action not in self.SENSITIVE_ACTIONS:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 1. 检查用户权限级别
|
|||
|
|
user = self.get_user(user_id)
|
|||
|
|
if user.role == 'admin':
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 2. 检查是否需要二次验证
|
|||
|
|
if self.requires_mfa(action, context):
|
|||
|
|
# 发送验证码
|
|||
|
|
self.send_verification_code(user)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 3. 记录审计日志
|
|||
|
|
self.audit_log(user_id, action, context)
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 密钥轮换机制
|
|||
|
|
|
|||
|
|
### 3.1 当前问题
|
|||
|
|
|
|||
|
|
- API Key 无失效机制
|
|||
|
|
- 无法强制轮换
|
|||
|
|
- 无生命周期管理
|
|||
|
|
|
|||
|
|
### 3.2 解决方案
|
|||
|
|
|
|||
|
|
#### 3.2.1 密钥生命周期管理
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class APIKeyLifecycle:
|
|||
|
|
# 配置
|
|||
|
|
KEY_EXPIRY_DAYS = 90 # 有效期90天
|
|||
|
|
WARNING_DAYS = 14 # 提前14天提醒
|
|||
|
|
GRACE_PERIOD_DAYS = 7 # 宽限期7天
|
|||
|
|
MAX_KEYS_PER_USER = 10 # 每个用户最多10个Key
|
|||
|
|
|
|||
|
|
def generate_key(self, user_id, description) -> APIKey:
|
|||
|
|
# 1. 检查Key数量限制
|
|||
|
|
current_keys = self.count_user_keys(user_id)
|
|||
|
|
if current_keys >= self.MAX_KEYS_PER_USER:
|
|||
|
|
raise MaxKeysExceededError()
|
|||
|
|
|
|||
|
|
# 2. 生成Key
|
|||
|
|
key = self._generate_key_string()
|
|||
|
|
|
|||
|
|
# 3. 存储Key信息
|
|||
|
|
api_key = APIKey(
|
|||
|
|
key_hash=self.hash(key),
|
|||
|
|
key_prefix=key[:12], # 显示前缀
|
|||
|
|
user_id=user_id,
|
|||
|
|
description=description,
|
|||
|
|
expires_at=datetime.now() + timedelta(days=self.KEY_EXPIRY_DAYS),
|
|||
|
|
created_at=datetime.now(),
|
|||
|
|
status='active',
|
|||
|
|
version=1
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 4. 保存到数据库
|
|||
|
|
self.save(api_key)
|
|||
|
|
|
|||
|
|
return api_key
|
|||
|
|
|
|||
|
|
def is_key_valid(self, key: APIKey) -> ValidationResult:
|
|||
|
|
# 1. 检查状态
|
|||
|
|
if key.status == 'disabled':
|
|||
|
|
return ValidationResult(False, 'Key is disabled')
|
|||
|
|
|
|||
|
|
if key.status == 'expired':
|
|||
|
|
return ValidationResult(False, 'Key is expired')
|
|||
|
|
|
|||
|
|
# 2. 检查是否过期
|
|||
|
|
if key.expires_at and key.expires_at < datetime.now():
|
|||
|
|
# 检查是否在宽限期
|
|||
|
|
if key.expires_at > datetime.now() - timedelta(days=self.GRACE_PERIOD_DAYS):
|
|||
|
|
# 在宽限期,提醒但不拒绝
|
|||
|
|
return ValidationResult(True, 'Key expiring soon', warning=True)
|
|||
|
|
return ValidationResult(False, 'Key expired')
|
|||
|
|
|
|||
|
|
# 3. 检查是否需要轮换提醒
|
|||
|
|
days_until_expiry = (key.expires_at - datetime.now()).days
|
|||
|
|
if days_until_expiry <= self.WARNING_DAYS:
|
|||
|
|
# 异步通知用户
|
|||
|
|
self.notify_key_expiring(key, days_until_expiry)
|
|||
|
|
|
|||
|
|
return ValidationResult(True, 'Valid')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.2 密钥泄露应急处理
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class KeyCompromiseHandler:
|
|||
|
|
def report_compromised(self, key_id, reporter_id):
|
|||
|
|
"""报告Key泄露"""
|
|||
|
|
# 1. 立即禁用Key
|
|||
|
|
key = self.get_key(key_id)
|
|||
|
|
key.status = 'compromised'
|
|||
|
|
key.disabled_at = datetime.now()
|
|||
|
|
key.disabled_by = reporter_id
|
|||
|
|
self.save(key)
|
|||
|
|
|
|||
|
|
# 2. 通知用户
|
|||
|
|
user = self.get_user(key.user_id)
|
|||
|
|
self.send_notification(user, 'key_compromised', {
|
|||
|
|
'key_id': key_id,
|
|||
|
|
'reported_at': datetime.now()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 3. 记录审计日志
|
|||
|
|
self.audit_log('key_compromised', {
|
|||
|
|
'key_id': key_id,
|
|||
|
|
'reported_by': reporter_id,
|
|||
|
|
'action': 'disabled'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 4. 自动创建新Key(可选)
|
|||
|
|
new_key = self.generate_key(key.user_id, 'Auto-generated replacement')
|
|||
|
|
return new_key
|
|||
|
|
|
|||
|
|
def rotate_key(self, key_id):
|
|||
|
|
"""主动轮换Key"""
|
|||
|
|
old_key = self.get_key(key_id)
|
|||
|
|
|
|||
|
|
# 1. 创建新Key
|
|||
|
|
new_key = self.generate_key(
|
|||
|
|
old_key.user_id,
|
|||
|
|
f"Rotation of {old_key.description}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 2. 标记旧Key为轮换
|
|||
|
|
old_key.status = 'rotated'
|
|||
|
|
old_key.rotated_at = datetime.now()
|
|||
|
|
old_key.replaced_by = new_key.id
|
|||
|
|
self.save(old_key)
|
|||
|
|
|
|||
|
|
return new_key
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 激活码安全强化
|
|||
|
|
|
|||
|
|
### 4.1 当前问题
|
|||
|
|
|
|||
|
|
- 6位随机数entropy不足
|
|||
|
|
- MD5校验和可碰撞
|
|||
|
|
|
|||
|
|
### 4.2 解决方案
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import secrets
|
|||
|
|
import hashlib
|
|||
|
|
import hmac
|
|||
|
|
|
|||
|
|
class SecureActivationCode:
|
|||
|
|
def generate(self, user_id: int, expiry_days: int) -> str:
|
|||
|
|
# 1. 使用 crypto.random 替代 random
|
|||
|
|
# 16字节 = 128位 entropy
|
|||
|
|
random_bytes = secrets.token_bytes(16)
|
|||
|
|
random_hex = random_bytes.hex()
|
|||
|
|
|
|||
|
|
# 2. 使用 HMAC-SHA256 替代 MD5
|
|||
|
|
expiry = datetime.now() + timedelta(days=expiry_days)
|
|||
|
|
expiry_str = expiry.strftime("%Y%m%d")
|
|||
|
|
|
|||
|
|
# 3. 构建原始字符串
|
|||
|
|
raw = f"lgw-act-{user_id}-{expiry_str}-{random_hex}"
|
|||
|
|
|
|||
|
|
# 4. HMAC 签名(使用应用密钥)
|
|||
|
|
signature = hmac.new(
|
|||
|
|
self.secret_key.encode(),
|
|||
|
|
raw.encode(),
|
|||
|
|
hashlib.sha256
|
|||
|
|
).hexdigest()[:16]
|
|||
|
|
|
|||
|
|
return f"{raw}-{signature}"
|
|||
|
|
|
|||
|
|
def verify(self, code: str) -> VerificationResult:
|
|||
|
|
parts = code.split('-')
|
|||
|
|
if len(parts) != 6:
|
|||
|
|
return VerificationResult(False, 'Invalid format')
|
|||
|
|
|
|||
|
|
# 1. 解析各部分
|
|||
|
|
_, _, user_id, expiry_str, random_hex, signature = parts
|
|||
|
|
|
|||
|
|
# 2. 验证签名
|
|||
|
|
raw = f"lgw-act-{user_id}-{expiry_str}-{random_hex}"
|
|||
|
|
expected_signature = hmac.new(
|
|||
|
|
self.secret_key.encode(),
|
|||
|
|
raw.encode(),
|
|||
|
|
hashlib.sha256
|
|||
|
|
).hexdigest()[:16]
|
|||
|
|
|
|||
|
|
if not hmac.compare_digest(signature, expected_signature):
|
|||
|
|
return VerificationResult(False, 'Invalid signature')
|
|||
|
|
|
|||
|
|
# 3. 验证过期
|
|||
|
|
expiry = datetime.strptime(expiry_str, "%Y%m%d")
|
|||
|
|
if expiry < datetime.now():
|
|||
|
|
return VerificationResult(False, 'Expired')
|
|||
|
|
|
|||
|
|
return VerificationResult(True, 'Valid', user_id=int(user_id))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. DDoS防护机制
|
|||
|
|
|
|||
|
|
### 4.1 防护层级
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class DDoSProtection:
|
|||
|
|
"""DDoS防护 - 修复S-D-01"""
|
|||
|
|
|
|||
|
|
# 三层防护
|
|||
|
|
TIERS = [
|
|||
|
|
{'name': 'L4', 'layer': 'tcp', 'method': 'syn_cookie'},
|
|||
|
|
{'name': 'L7', 'layer': 'http', 'method': 'rate_limit'},
|
|||
|
|
{'name': 'APP', 'layer': 'application', 'method': 'challenge'}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 限流配置
|
|||
|
|
RATE_LIMITS = {
|
|||
|
|
'global': {'requests': 100000, 'window': 60},
|
|||
|
|
'per_ip': {'requests': 1000, 'window': 60},
|
|||
|
|
'per_token': {'requests': 100, 'window': 60},
|
|||
|
|
'burst': {'requests': 50, 'window': 1}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# IP黑名单
|
|||
|
|
def check_ip_blacklist(self, ip: str) -> bool:
|
|||
|
|
"""检查IP是否在黑名单"""
|
|||
|
|
return self.redis.sismember('ddos:blacklist', ip)
|
|||
|
|
|
|||
|
|
def add_to_blacklist(self, ip: str, reason: str, duration: int = 3600):
|
|||
|
|
"""加入黑名单"""
|
|||
|
|
self.redis.sadd('ddos:blacklist', ip)
|
|||
|
|
self.redis.expire('ddos:blacklist', duration)
|
|||
|
|
# 记录原因
|
|||
|
|
self.redis.hset('ddos:blacklist:reasons', ip, json.dumps({
|
|||
|
|
'reason': reason,
|
|||
|
|
'added_at': datetime.now().isoformat()
|
|||
|
|
}))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 攻击检测
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class AttackDetector:
|
|||
|
|
"""攻击检测"""
|
|||
|
|
|
|||
|
|
# 检测规则
|
|||
|
|
RULES = {
|
|||
|
|
'syn_flood': {'threshold': 1000, 'window': 10, 'action': 'block'},
|
|||
|
|
'http_flood': {'threshold': 500, 'window': 60, 'action': 'rate_limit'},
|
|||
|
|
'slowloris': {'threshold': 50, 'window': 60, 'action': 'block'},
|
|||
|
|
'credential_stuffing': {'threshold': 100, 'window': 60, 'action': 'challenge'}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def detect(self, metrics: AttackMetrics) -> DetectionResult:
|
|||
|
|
"""检测攻击"""
|
|||
|
|
for rule_name, rule in self.RULES.items():
|
|||
|
|
if metrics.exceeds_threshold(rule):
|
|||
|
|
return DetectionResult(
|
|||
|
|
attack=True,
|
|||
|
|
rule=rule_name,
|
|||
|
|
action=rule['action'],
|
|||
|
|
severity='HIGH' if rule['action'] == 'block' else 'MEDIUM'
|
|||
|
|
)
|
|||
|
|
return DetectionResult(attack=False)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 日志脱敏规则
|
|||
|
|
|
|||
|
|
### 5.1 脱敏字段定义
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class LogDesensitization:
|
|||
|
|
"""日志脱敏 - 修复S-D-02"""
|
|||
|
|
|
|||
|
|
# 脱敏规则
|
|||
|
|
RULES = {
|
|||
|
|
'api_key': {
|
|||
|
|
'pattern': r'(sk-[a-zA-Z0-9]{20,})',
|
|||
|
|
'replacement': r'sk-***',
|
|||
|
|
'level': 'SENSITIVE'
|
|||
|
|
},
|
|||
|
|
'password': {
|
|||
|
|
'pattern': r'(password["\']?\s*[:=]\s*["\']?)([^"\']+)',
|
|||
|
|
'replacement': r'\1***',
|
|||
|
|
'level': 'SENSITIVE'
|
|||
|
|
},
|
|||
|
|
'email': {
|
|||
|
|
'pattern': r'([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
|
|||
|
|
'replacement': r'\1***@\2',
|
|||
|
|
'level': 'PII'
|
|||
|
|
},
|
|||
|
|
'phone': {
|
|||
|
|
'pattern': r'(1[3-9]\d)(\d{4})(\d{4})',
|
|||
|
|
'replacement': r'\1****\3',
|
|||
|
|
'level': 'PII'
|
|||
|
|
},
|
|||
|
|
'ip_address': {
|
|||
|
|
'pattern': r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',
|
|||
|
|
'replacement': r'\1 (masked)',
|
|||
|
|
'level': 'NETWORK'
|
|||
|
|
},
|
|||
|
|
'credit_card': {
|
|||
|
|
'pattern': r'(\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4})',
|
|||
|
|
'replacement': r'****-****-****-\4',
|
|||
|
|
'level': 'SENSITIVE'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def desensitize(self, log: dict) -> dict:
|
|||
|
|
"""脱敏处理"""
|
|||
|
|
import re
|
|||
|
|
result = {}
|
|||
|
|
for key, value in log.items():
|
|||
|
|
if isinstance(value, str):
|
|||
|
|
result[key] = self._desensitize_value(value)
|
|||
|
|
else:
|
|||
|
|
result[key] = value
|
|||
|
|
return result
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 日志级别
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class LogLevel:
|
|||
|
|
"""日志级别"""
|
|||
|
|
|
|||
|
|
LEVELS = {
|
|||
|
|
'DEBUG': {'mask': False, 'retention_days': 7},
|
|||
|
|
'INFO': {'mask': False, 'retention_days': 30},
|
|||
|
|
'WARNING': {'mask': False, 'retention_days': 90},
|
|||
|
|
'ERROR': {'mask': False, 'retention_days': 365},
|
|||
|
|
'SENSITIVE': {'mask': True, 'retention_days': 365} # 敏感日志必须脱敏
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def should_mask(self, level: str) -> bool:
|
|||
|
|
"""是否需要脱敏"""
|
|||
|
|
return self.LEVELS.get(level, {}).get('mask', False)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 密钥定期轮换
|
|||
|
|
|
|||
|
|
### 6.1 定期轮换策略
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class KeyRotationScheduler:
|
|||
|
|
"""密钥定期轮换 - 修复S-D-03"""
|
|||
|
|
|
|||
|
|
# 轮换配置
|
|||
|
|
ROTATION_CONFIG = {
|
|||
|
|
'api_key': {'days': 90, 'warning_days': 14},
|
|||
|
|
'internal_key': {'days': 30, 'warning_days': 7},
|
|||
|
|
'provider_key': {'days': 60, 'warning_days': 10}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def schedule_rotation(self):
|
|||
|
|
"""调度轮换"""
|
|||
|
|
while True:
|
|||
|
|
# 1. 查找需要轮换的Key
|
|||
|
|
keys_due = await self.find_keys_due_for_rotation()
|
|||
|
|
|
|||
|
|
# 2. 发送提醒
|
|||
|
|
for key in keys_due:
|
|||
|
|
await self.send_rotation_warning(key)
|
|||
|
|
|
|||
|
|
# 3. 自动轮换(超过宽限期)
|
|||
|
|
keys_expired = await self.find_expired_keys()
|
|||
|
|
for key in keys_expired:
|
|||
|
|
await self.auto_rotate(key)
|
|||
|
|
|
|||
|
|
await asyncio.sleep(3600) # 每小时检查
|
|||
|
|
|
|||
|
|
async def auto_rotate(self, key: APIKey):
|
|||
|
|
"""自动轮换"""
|
|||
|
|
# 1. 创建新Key
|
|||
|
|
new_key = await self.generate_key(key.user_id, key.description)
|
|||
|
|
|
|||
|
|
# 2. 标记旧Key
|
|||
|
|
key.status = 'rotating'
|
|||
|
|
key.rotated_at = datetime.now()
|
|||
|
|
key.replaced_by = new_key.id
|
|||
|
|
|
|||
|
|
# 3. 通知用户
|
|||
|
|
await self.notify_user(key.user_id, {
|
|||
|
|
'type': 'key_rotated',
|
|||
|
|
'old_key_id': key.id,
|
|||
|
|
'new_key': new_key.key_prefix + '***'
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 实施计划
|
|||
|
|
|
|||
|
|
### 7.1 优先级
|
|||
|
|
|
|||
|
|
| 任务 | 负责人 | 截止 | 依赖 |
|
|||
|
|
|------|--------|------|------|
|
|||
|
|
| 计费防篡改机制 | 后端 | S1前 | - |
|
|||
|
|
| 跨租户隔离强化 | 架构 | S1前 | - |
|
|||
|
|
| 密钥轮换机制 | 后端 | S0-M1 | - |
|
|||
|
|
| 激活码安全强化 | 后端 | S0-M1 | - |
|
|||
|
|
| DDoS防护机制 | 安全 | S0-M2 | - |
|
|||
|
|
| 日志脱敏规则 | 后端 | S0-M1 | - |
|
|||
|
|
| 密钥定期轮换 | 后端 | S0-M2 | - |
|
|||
|
|
|
|||
|
|
### 7.2 验证标准
|
|||
|
|
|
|||
|
|
- 所有计费操作都有审计日志
|
|||
|
|
- 跨租户访问被强制拦截
|
|||
|
|
- Key可以正常轮换和失效
|
|||
|
|
- 激活码无法伪造
|
|||
|
|
- DDoS攻击可被检测和阻断
|
|||
|
|
- 敏感日志自动脱敏
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档状态**:安全解决方案(修复版)
|
|||
|
|
**关联文档**:
|
|||
|
|
- `security_api_key_vulnerability_analysis_v1_2026-03-18.md`
|
|||
|
|
- `supply_detailed_design_v1_2026-03-18.md`
|