审计与合规
问题
数据库操作审计如何实施?数据脱敏和合规要求有哪些?
答案
一、数据库审计
数据库审计是记录谁在什么时间对什么数据做了什么操作的过程。
MySQL 审计
my.cnf
# 开启通用查询日志(记录所有 SQL,生产环境不建议长期开启)
general_log = 1
general_log_file = /var/log/mysql/general.log
# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
-- MySQL Enterprise Audit Plugin(企业版)
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
-- 开源替代:MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
PostgreSQL 审计
postgresql.conf
# pgAudit 扩展(推荐)
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl' # 审计写操作和 DDL
pgaudit.log_catalog = off
pgaudit.log_relation = on
pgaudit.log_parameter = on # 记录参数值
-- 安装 pgAudit
CREATE EXTENSION pgaudit;
-- 对特定角色审计
ALTER ROLE app_user SET pgaudit.log = 'read, write';
应用层审计
// 审计中间件:记录所有数据变更
class AuditInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const userId = request.user?.id;
const method = request.method;
const path = request.path;
const result = await lastValueFrom(next.handle());
// 记录审计日志
await this.auditService.log({
userId,
action: `${method} ${path}`,
timestamp: new Date(),
ip: request.ip,
requestBody: this.sanitize(request.body), // 脱敏后记录
responseStatus: context.switchToHttp().getResponse().statusCode,
});
return result;
}
}
二、数据脱敏
将敏感数据进行变换,使其无法还原,但保留数据格式和统计特性。
脱敏规则
| 数据类型 | 原始数据 | 脱敏后 | 方法 |
|---|---|---|---|
| 手机号 | 13800138000 | 138****8000 | 中间掩码 |
| 身份证 | 110101199001011234 | 110***********1234 | 中间掩码 |
| 邮箱 | alice@example.com | a****@example.com | 前缀掩码 |
| 银行卡 | 6222021001234567 | ************4567 | 仅保留后4位 |
| 姓名 | 张三丰 | 张*丰 | 中间掩码 |
| 地址 | 北京市朝阳区xxx路 | 北京市朝阳区*** | 截断 |
实现方式
// 脱敏工具函数
const masks = {
phone: (v: string) => v.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
idCard: (v: string) => v.replace(/(\d{3})\d{11}(\d{4})/, '$1***********$2'),
email: (v: string) => v.replace(/(.{1}).+(@.+)/, '$1****$2'),
bankCard: (v: string) => v.replace(/.(?=.{4})/g, '*'),
name: (v: string) => {
if (v.length <= 1) return v;
if (v.length === 2) return v[0] + '*';
return v[0] + '*'.repeat(v.length - 2) + v[v.length - 1];
},
};
// 使用装饰器自动脱敏
function Mask(type: keyof typeof masks) {
return function (target: any, propertyKey: string) {
// 在序列化时自动脱敏
};
}
class UserResponse {
name: string;
@Mask('phone') phone: string;
@Mask('email') email: string;
}
数据库视图脱敏
-- 创建脱敏视图
CREATE VIEW users_masked AS
SELECT
id,
CONCAT(LEFT(name, 1), '**') AS name,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone,
CONCAT(LEFT(email, 1), '****', SUBSTRING(email, LOCATE('@', email))) AS email,
created_at
FROM users;
-- 分析师只能查询脱敏视图
GRANT SELECT ON users_masked TO 'analyst'@'%';
三、合规要求
GDPR(欧盟通用数据保护条例)
| 要求 | 数据库层面的实现 |
|---|---|
| 数据最小化 | 只收集必要字段 |
| 存储期限 | 自动清理过期数据(TTL) |
| 被遗忘权 | 支持删除用户所有数据 |
| 数据可携带权 | 支持导出用户数据 |
| 访问控制 | RBAC + 审计日志 |
| 加密保护 | TDE + 传输加密 |
| 泄露通知 | 72 小时内通知 |
-- 被遗忘权:删除用户所有数据
-- 需要处理外键级联
DELETE FROM user_logs WHERE user_id = ?;
DELETE FROM user_orders WHERE user_id = ?;
DELETE FROM user_profiles WHERE user_id = ?;
DELETE FROM users WHERE id = ?;
-- 或使用软删除 + 数据清洗
UPDATE users SET
name = 'DELETED',
email = CONCAT('deleted_', id, '@deleted.com'),
phone = NULL,
deleted_at = NOW()
WHERE id = ?;
中国个人信息保护法
| 要求 | 实现方式 |
|---|---|
| 敏感信息加密存储 | 字段加密(身份证、银行卡) |
| 跨境传输限制 | 数据本地化存储 |
| 操作可追溯 | 数据库审计日志 |
| 最小必要原则 | 按需查询字段 |
四、审计日志最佳实践
// 审计日志表设计
interface AuditLog {
id: string;
timestamp: Date;
userId: string;
action: 'CREATE' | 'READ' | 'UPDATE' | 'DELETE';
tableName: string;
recordId: string;
oldValue: object | null; // 变更前的值
newValue: object | null; // 变更后的值
ip: string;
userAgent: string;
}
| 实践 | 说明 |
|---|---|
| 不可篡改 | 审计日志表不授予 UPDATE/DELETE 权限 |
| 独立存储 | 审计日志与业务数据分库 |
| 异步写入 | 审计日志异步写入,不影响业务性能 |
| 定期归档 | 超过保留期的日志归档到冷存储 |
| 告警规则 | 异常操作(大量 DELETE、非工作时间访问)触发告警 |
常见面试问题
Q1: 静态脱敏和动态脱敏的区别?
答案:
| 对比 | 静态脱敏 | 动态脱敏 |
|---|---|---|
| 时机 | 离线处理(ETL) | 查询时实时脱敏 |
| 数据 | 生成脱敏副本 | 原始数据不变 |
| 使用场景 | 测试环境数据准备 | 生产环境查询控制 |
| 实现 | 脱敏脚本 / 工具 | 数据库视图 / 中间件 / 应用层 |
Q2: 如何设计一个可审计的删除操作?
答案:
- 软删除:不物理删除,设置
deleted_at字段 - 审计日志:记录删除操作的操作人、时间、删除的数据内容
- 回收站:将删除的数据移到回收站表,保留 30 天
- 权限控制:物理删除需要更高权限审批
Q3: 如何实现"被遗忘权"(数据删除请求)?
答案:
- 数据映射:先梳理用户数据分布在哪些表、哪些服务
- 级联删除:按依赖顺序删除所有关联数据
- 匿名化:对于需要保留的统计数据,将个人标识替换为匿名值
- 确认机制:删除完成后生成确认报告
- 日志清理:同时清理包含用户信息的日志