背景
日志脱敏是常见的安全需求。为了金融交易的安全性,国家强制规定对于姓名、手机号、身份证号码、住址等信息需要数据脱敏。一般我们会采取注解的形式指定字段进行脱敏处理,但这样对代码的侵入性较高。假设公司有几十个系统要改造,或者采购了一些系统没有源代码,怎么办?还有,日志脱敏后,系统用户需要通过手机号找出对应的日志,怎么匹配?
目标
实现一个零侵入性的日志插件,并能支持原始数据检索。
实现
我们调研了 Github 一些开源项目,发现 https://github.com/houbb/sensitive 能够满足这个需求,它通过 Trie 类对字符进行解析处理,具体的处理逻辑大部分封装在 com.github.houbb.chars.scan.bs.CharsScanBs 这个类,我们只需要引入 CharsScanBs.scanAndReplace(text) 就可以实现对原始日志的脱敏处理,被脱敏的内容后面自动追加原始数据库的 MD5 信息,假设用户反馈系统问题,会提供他的手机号,我们将手机号生成 MD5,去脱敏日志文件匹配,就能定位到原始数据。
Log4j2 插件提供了两个扩展方式 AbstractStringLayout 和 RewritePolicy。在这里,我们定义了 MaskingStringLayout 对 AbstractStringLayout 进行扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Plugin(name = "MaskingStringLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) public class MaskingStringLayout extends AbstractStringLayout {
private final CharsScanBs charsScanBs;
@Override public String toSerializable(LogEvent event) { if (patternFormatterList == null || patternFormatterList.isEmpty()) { return event.getMessage().getFormattedMessage(); } StringBuilder stringBuilder = new StringBuilder(); for (PatternFormatter formatter : patternFormatterList) { formatter.format(event, stringBuilder); } return charsScanBs.scanAndReplace(stringBuilder.toString()); } @PluginFactory public static MaskingStringLayout createLayout( @PluginConfiguration final Configuration config, @PluginAttribute(value = "charset", defaultString = "UTF-8") String charset, @PluginAttribute(value = "pattern") String pattern, @PluginAttribute(value = "type") String type, @PluginAttribute(value = "prefix") String prefix, @PluginAttribute(value = "scanList") String scanList, @PluginAttribute(value = "replaceList") String replaceList, @PluginAttribute(value = "defaultReplace") String defaultReplace, @PluginAttribute(value = "replaceHash") String replaceHash, @PluginAttribute(value = "whiteList") String whiteList) { MaskingConfig maskingConfig = new MaskingConfig(); if (type != null) { maskingConfig.setType(type); } MaskingStringLayout layout = new MaskingStringLayout(Charset.forName(charset), maskingConfig); layout.patternFormatterList = patternParser.parse(pattern); return layout; } }
|
对应的 MaskingConfig 配置类如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @EqualsAndHashCode @ToString @Setter @Getter public class MaskingConfig {
private String type = "chars-scan";
private final CharsScan charsScan = new CharsScan();
@EqualsAndHashCode(callSuper = false) @ToString @Setter @Getter public static class CharsScan {
private String prefix = ":‘“,| ,:\\\"'=";
private String scanList = "TELEPHONE,ID_CARD,BANK_CARD,PASSPORT,ADDRESS,EMAIL";
private String replaceList = "TELEPHONE,ID_CARD,BANK_CARD,PASSPORT,ADDRESS,EMAIL";
private String defaultReplace = "ANY_PARTIALLY_MASKED";
private String replaceHash = "md5";
private String whiteList = ""; } }
|
在 log4j2.yml 配置相关日志插件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Configuration: status: WARN monitorInterval: 30
Properties: Property: - name: MASKING_STRATEGIES value: TELEPHONE,ID_CARD,BANK_CARD,PASSPORT,ADDRESS,EMAIL - name: MASKING_REPLACEMENT value: ANY_PARTIALLY_MASKED - name: MASKING_HASH value: "md5" - name: MASKING_WHITELIST value: ""
Appenders: Console: name: STDOUT target: SYSTEM_OUT MaskingStringLayout: pattern: ${LOG_PATTERN} strategies: ${MASKING_STRATEGIES} replacement: ${MASKING_REPLACEMENT} hash: ${MASKING_HASH} whitelist: ${MASKING_WHITELIST}
|
假设我要查询 18820132137 这个手机号的日志,可以通过 MD5 在线加密工具得到 MD5 值。

搜索日志时,直接输入 MD5 值,就能定位到原始日志。

产出
可以满足金融级别的监管合规检查,对业务零侵入,研发团队只需要在 log4j2.yml 引入相关日志插件就可以完成日志脱敏。
本文涉及的代码完全开源,感兴趣的伙伴可以查阅 eden-data-masker。