从动态屏蔽到静态清洗
前言
各国法律法规比如 GDPR(欧盟)、CCPA(美国)、网络安全法/个人信息保护法(中国) 都要求企业妥善保护用户隐私,而在企业数据库里往往存放着敏感数据 (如身份证号、手机号、银行卡号、医疗记录等)。如果直接将这些数据暴露给开发人员、测试人员,就可能造成隐私泄露、合规风险,即使在公司内部,也不能所有人都访问完整的真实数据,因此,这就涉及到了数据脱敏。在 PostgreSQL 生态中不乏有大名鼎鼎的 PostgreSQL Anonymizer,也有着小巧精悍的命令行工具 pganonymize,还有 pganonymizer 等等,那么这么多轮子,如何选型?各自都适用于什么场景?
pganonymize (rheinwerk-verlag)
首先是 pganonymize[1],基于 python 的一款 CLI 工具 + YAML 配置,可用于批量清洗数据库,官方文档:https://pganonymize.readthedocs.io/en/latest/schema.html
A commandline tool to anonymize PostgreSQL databases for DSGVO/GDPR purposes.
举个栗子,假设有如下几张表,内容如下:
anon_test=# select * from orders;id | customer_id | card_no | billing_addr | amount----+-------------+------------------+--------------------------+--------1 | 1 | 4111111111111111 | 1 Hacker Way, Menlo Park | 123.452 | 2 | 5555555555554444 | 1600 Amphitheatre Pkwy | 678.90(2 rows)anon_test=# select * from profiles;id | user_id | data----+---------+--------------------------------------------------------------------1 | 1 | {"age": 29, "vip": true, "tags": ["red", "v1"], "nickname": "Ali"}2 | 2 | {"age": 35, "vip": false, "tags": ["blue"], "nickname": "Bobby"}(2 rows)
我们需要在 YMAL 文件中定义脱敏规则,以及相应的 provider:
tables:- customers:primary_key: idsearch: "cust_type = 'enduser'"fields:- name:provider: { name: fake.name } # 用 Faker 生成姓名- email:provider: { name: md5 } # 对原邮箱做 MD5append: "@example.test" # 统一换域,便于离线联调- phone:provider: { name: partial_mask, sign: "X", unmasked_left: 0, unmasked_right: 4 }# 跳过公司域邮箱(整行跳过),注意 YAML 里的反斜杠转义excludes:- email:- "\\S+@corp\\.example\\.com"- orders:primary_key: idfields:- card_no:provider: { name: partial_mask, sign: "X", unmasked_left: 0, unmasked_right: 4 }- billing_addr:provider: { name: set, value: "REDACTED" }# amount 不配置 => 保持原值- profiles:primary_key: idfields:- data:provider:name: update_json# 根据 JSON 值类型分别处理update_values_type:str: { provider: { name: set, value: "***" } } # 字符串统一置 ***int: { provider: { name: set, value: 0 } } # 数字置 0float: { provider: { name: set, value: 0 } }bool: { provider: { name: set, value: false } }# 对数组内元素同样套用上述规则update_array_values: true# 直接清空的表(演示 TRUNCATE)truncate:- audit_log
执行之后,数据便会被相应地脱敏:
anon_test=# select * from profiles;id | user_id | data----+---------+--------------------------------------------------------------------1 | 1 | {"age": 0, "vip": false, "tags": ["red", "v1"], "nickname": "***"}2 | 2 | {"age": 0, "vip": false, "tags": ["blue"], "nickname": "***"}(2 rows)anon_test=# select * from orders;id | customer_id | card_no | billing_addr | amount----+-------------+------------------+--------------+--------1 | 1 | 4XXXXXXXXXXX1111 | REDACTED | 123.452 | 2 | 5XXXXXXXXXXX4444 | REDACTED | 678.90(2 rows)
可以看到,pganonymize 是命令行工具,基于配置文件定义规则,然后执行 TRUNCATE/UPDATE 来脱敏数据。
pg-anonymizer (rap2hpoutre)
另一款工具是 pg-anonymizer[5]
Export your PostgreSQL database anonymized. Replace all sensitive data thanks to faker. Output to a file that you can easily import with psql.
与 pganonymize 类似,基于配置文件,可以列级脱敏,内置规则较多,比如:faker(名字、地址)、哈希、截断,专注于 GDPR 合规场景。笔者没有具体使用,光看其文档:
使用方法和 pganonymize 类似,不过是脱敏导出为 SQL,再导回数据库中的方式,这种方式的优点不言而喻,不具备破坏性,不会破坏原有数据。有趣的是,在 README 中,作者写明了为何他要去开发这一款工具
There are a bunch of competitors, still I failed to use them:
•postgresql_anonymizer[6] may be hard to setup[7] and may be cumbersome for simple usage. Still, I guess it's the best solution.•pganonymize[8] fails when it does not use public schema or columns have uppercase characters•pganonymizer[9] also fails with simple cases. Errors are not explicit and silent.
此处提及了 pganonymize 不支持 public 以外的 schema,笔者也进行了验证,确实如此,虽然可以通过一些 workaround 解决,比如库级、用户级设置 search_path,但是始终很繁琐,最好能够在 YAML 文件中支持指定模式名。
psycopg2.errors.UndefinedTable: relation "myschema.customers" does not existLINE 1: SELECT COUNT(*) FROM "myschema.customers"
pgantomizer
这一款工具笔者不做过多介绍了,2 年前的版本了,pg-anonymizer[10]
PostgreSQL Anonymizer
最后介绍的便是大名鼎鼎的 PostgreSQL Anonymizer,出自 dalibo 实验室,最新的 2.0 基于 RUST + PGRX 重构,在安全性和稳健性上面有优势,使用 SECURITY LABEL 实现,并且其支持的特性和功能是最多的,官方文档:https://postgresql-anonymizer.readthedocs.io/en/stable/:
•Anonymous Dumps (导出脱敏 SQL)•Static Masking (永久脱敏)•Dynamic Masking (根据用户角色动态脱敏)•Masking Views (为不同角色提供脱敏视图)•Masking Data Wrappers (对外部数据源也能应用脱敏逻辑)
并且提提供丰富的脱敏函数,如替换、随机化、模拟(faking)、部分混淆、噪音添加、模糊泛化,甚至可自定义函数。在 1.x 的版本中,其实现还比较简陋,基于视图:
它禁止了被 mask 的用户读取原来的 schema,而允许它读取插件创建的两个 schema。并且它还设置了 search_path 这个参数,使得在 mask 这个 schema 下创建的 view 可以被优先读到。
实现原理类似如下:
并且还有一个很大的限制,仅支持一个 schema 进行脱敏,2.0 版本用 Rust + PGRX 全面重写,带来内存安全、性能与可维护性的提升;动态脱敏等策略因此在执行层面更“紧凑/高效”,并且正式支持了多个 SCEHMA,也不再是视图的方式,我个人也很喜欢其 Conditional Masking 的功能,在函数体内可以实现类似 CASE WHEN 的效果,与 PostgreSQL RLS 结合,可以实现基于用户角色实现差异化显示。但是 2.0 也有其限制,比如对于 Masked 用户,无法使用 Explain,但是这通常并不是太大的问题,性能上也会有些许损耗,具体取决于有多少个 Policy。
比对
小结
要动态脱敏(生产库角色区分显示) → postgresql_anonymizer(扩展方式,最强大)
要一次性轻量脱敏导出 → pg-anonymizer(Node.js 工具,简单直接)
要批量清洗并生成逼真测试数据 → pganonymize / pganonymizer(Python + Faker,YAML 配置灵活)
参考
https://github.com/rap2hpoutre/pg-anonymizer
https://github.com/asgeirrr/pgantomizer
https://github.com/rheinwerk-verlag/pganonymize
https://gitlab.com/dalibo/postgresql_anonymizer
https://zhuanlan.zhihu.com/p/597950184
References
[1] pganonymize:https://github.com/rheinwerk-verlag/pganonymize[2][email protected]:mailto:[email protected][3][email protected]:mailto:[email protected][4][email protected]:mailto:[email protected][5]pg-anonymizer:https://github.com/rap2hpoutre/pg-anonymizer[6]postgresql_anonymizer:https://postgresql-anonymizer.readthedocs.io/en/stable/[7]hard to setup:https://postgresql-anonymizer.readthedocs.io/en/stable/INSTALL/#install-on-macos[8]pganonymize:https://pypi.org/project/pganonymize/[9]pganonymizer:https://github.com/asgeirrr/pgantomizer[10]pg-anonymizer: https://github.com/rap2hpoutre/pg-anonymizer