PostgreSQL学徒

从动态屏蔽到静态清洗

前言

各国法律法规比如 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.45  2 |           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: id      search: "cust_type = 'enduser'"      fields:        - name:            provider: { name: fake.name }          # 用 Faker 生成姓名        - email:            provider: { name: md5 }                # 对原邮箱做 MD5            append: "@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: id      fields:        - 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: id      fields:        - data:            provider:              name: update_json              # 根据 JSON 值类型分别处理              update_values_type:                str:   { provider: { name: set, value: "***" } }   # 字符串统一置 ***                int:   { provider: { name: set, value: 0 } }       # 数字置 0                float: { 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.45  2 |           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 合规场景。笔者没有具体使用,光看其文档:

Image

使用方法和 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 可以被优先读到。

实现原理类似如下:

Image

并且还有一个很大的限制,仅支持一个 schema 进行脱敏,2.0 版本用 Rust + PGRX 全面重写,带来内存安全、性能与可维护性的提升;动态脱敏等策略因此在执行层面更“紧凑/高效”,并且正式支持了多个 SCEHMA,也不再是视图的方式,我个人也很喜欢其 Conditional Masking 的功能,在函数体内可以实现类似 CASE WHEN 的效果,与 PostgreSQL RLS 结合,可以实现基于用户角色实现差异化显示。但是 2.0 也有其限制,比如对于 Masked 用户,无法使用 Explain,但是这通常并不是太大的问题,性能上也会有些许损耗,具体取决于有多少个 Policy。

比对

Image

小结

要动态脱敏(生产库角色区分显示) → 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