一、前言
说到最近,组内有一批新的组件要上生产了环境了,笔者所在公司每次上线一个新组件都会需要进行一个叫做安全审核的过程,所谓安全审核就是一堆负责安全检查的同事在你的代码里面寻找可能存在安全隐患的bug,比如什么CSRF攻击、SQL注入攻击、密码与AK\SK 管理等等。
于是就有了下面这组对话:
安全人员小B:"你怎么在配置文件中写明文密码啊,这样的话不等于把钥匙放门口,公司内部谁都能用这个密码操作DB吗"
完了,安全人员又在搞事情了
于是乎我打开IDEA 瞅瞅到底哪里写了明文密码,发现原来组件使用了sharding-sphere 5.0 进行了分表,涉及到两个配置文件,application.properties (spring-boot配置文件) 与sharding-prd.yml (sharding-sphere分表配置文件)
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
spring.datasource.url=jdbc:shardingsphere:classpath:sharding-prd.yml
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://test.aliyuncs.com:3306/power_station?useUnicode=true&useSSL=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8
username: test
password: test #这里写明文密码 上线存在安全隐患
hikari:
minimum-idle: 200
maximum-pool-size: 200
idle-timeout: 600000
auto-commit: true
pool-name: playHikariPool
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: Select 1
# 省略其他分表配置……
好嘛,问题找到了就是这个yaml文件里面写着明文的密码,来吧这密码肯定是不能明文写在配置文件里面的,想办法解决吧。
二、密码脱敏
话说配置文件里面的敏感信息脱敏,这是一个共性需求,称之为秘钥托管服务
,阿里云上面有KMS,华为云上有DEW等等,开源的还有jasypt
一般情况下这些中间件会对配置文件中的密码进行加密,在配置文件中写的密码一般就是加密之后的内容,笔者所在的公司使用的是KMS,一般情况集成了加密配置的application.properties一般长下面这样
redis.host=r-xxxxxx.redis.zhangbei.rds.aliyuncs.com
redis.port=6379
# 这里不是真实密码而是加密算法处理之后的密码 手动解密方式
redis.password=AYyDmTpey29FDoaG+so=
# 另外一种写法
redis.password=ENC(encryptedPassword)
在springBoot应用启动之后,可以选择手动解密,也可以借助一些依赖的能力,直接在应用启动的时候接上秘钥管理中心直接解密。这块儿的知识点,不是本文讨论的重点,感兴趣的读者可以用大模型搜一下相关知识点
好了,逼逼了这么多 到底怎么解决这个sharding-sphere配置文件的数据源数据库密码加密问题呢?
首先对于sharding-sphere 4.x版本这压根不是个事,在4.x版本中分表配置是和springboot主配置放在一起的,这样直接集成KMS就可以了,但是现在配置文件分开了,并且在分表的配置文件中还没有办法通过${xxx}的方式去引用application.properties中的定义的配置,这可咋整
遇事不决先到官方文档里面找找答案,官方有个placeHolder机制(shardingsphere.apache.org/document/cu…
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: $${FIXTURE_DRIVER_CLASS_NAME::org.h2.Driver}
jdbcUrl: $${FIXTURE_JDBC_URL::jdbc:h2:mem:foo_ds_do_not_use}
username: $${FIXTURE_USERNAME::}
password: $${FIXTURE_PASSWORD::}
但是这里的FIXTURE_PASSWORD 需要通过设置系统环境变量的方式注入 ,显然这个功能的作用不是拿来做密钥脱敏的而是适用于容器化部署的场景,为不同的容器启动时设置不同的数据库连接;要是这样解决密码脱敏问题相当于还是得在dockerFile文件中写明文,这相当于换个地方写明文密码显然还是不符合我们的需求。
那么到底咋整呢,官方ISSUE也有很多类似讨论,比如这位兄弟就想到了直接复写数据源的 getPassword方法,但是显然不太行(要不人家也不会发在讨论区……)实测用这种方式一个空指针会直接糊你脸上。
正当我感觉这个问题无解,是不是要给安全人员点奶茶蒙混过关的时候,突然灵光乍现有没有可能我们直接复写sharding-sphere的配置文件加载逻辑呢?只要配置文件是我们加载的,这样里面的内容不是可以随便替换?
三、接管sharding-sphere的配置文件加载逻辑
这样的开源框架一般都会有拓展点可以拓展原有的实现,查阅资料得知咱们默认的配置文件加载配置是这样的
spring.datasource.url=jdbc:shardingsphere:classpath:sharding-prd.yml
这里肯定对应的有个配置文件加载器的实现,果然很快我就找到了这个配置文件加载器
public final class ClassPathURLLoader implements ShardingSphereURLLoader {
@Override
@SneakyThrows(IOException.class)
public String load(final String configurationSubject, final Properties queryProps) {
try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(configurationSubject)) {
Objects.requireNonNull(inputStream);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
}
}
@Override
public String getType() {
return "classpath:";
}
}
好嘛,这个代码可就太简单了就是读取资源文件然后返回,queryProps 则表达的是文件加载URL参数,例如我们在配置中的文件URL的后面添加参数spring.datasource.url=jdbc:shardingsphere:classpath:sharding-prd.yml?xxx=1&yyy=2
queryProps中就会承载后面的xxx和yyy以及对应的值
于是乎说干就干,想法是直接我也来继承ShardingSphereURLLoader,加载完成之后对原先的数据库密码进行替换,替换成解密之后的密码就行,大致代码如下
public class KeyCenterDecryptSphereURLLoader implements ShardingSphereURLLoader {
private static final String KEYCENTER_TYPE = "kms:";
@Override
@SneakyThrows(IOException.class)
public String load(String configurationSubject, Properties queryProps) {
// 从配置文件URL参数中拿到解密之后的真实密码
String dbPwd = queryProps.getProperty("dbPwd");
try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(configurationSubject)) {
Objects.requireNonNull(inputStream);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String config = reader.lines().collect(Collectors.joining(System.lineSeparator()));
// 替换sharding-sphere 配置文件中的占位符
config = config.replace("${DB-PASSWORD}", dbPwd);
return config;
}
}
}
@Override
public Object getType() {
return KEYCENTER_TYPE;
}
}
配置完成之后发现如果自定义拓展要起作用需要进行SPI配置,这个我熟啊和DUBBO里面的拓展点机制类似嘛,
于是在resource 目录下创建目录META-INF/service,然后创建一个org.apache.shardingsphere.infra.url.spi.ShardingSphereURLLoader
文件,文件内容写上拓展点的实现类全名
再把application.properties中加载sharding-sphere配置文件的配置改成下面这样,使用我们自己的文件加载器
db.pwd=ENC(m52EjFcT9hTCQyCMgw==)
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
spring.datasource.url=jdbc:shardingsphere:kms:sharding-prd.yml?dbPwd=${db.pwd}
启动项目后果然密码完成了替换,问题解决,但是如果格局打开一点呢?
假如后面需要将分表配置放到nacos进行集中配置管理呢?聪明的你肯定知道怎么做 ,只要再写一套ShardingSphereURLLoader的实现,在这里访问nacos获取配置文件就可以了,这部分的代码相信在座的各位都随便写的啦,就不放具体实现了。
好了终于不用给安全审核人员买奶茶了,今天又是省下20块的一天,又可以多还一点房贷真开心。