国庆前我们遇到了阿里的 AccessKey 疑似泄露,在盘点所有泄露可能性时发现我们在 Nacos 的使用上存在诸多安全漏洞。我们在第一个时刻更换了泄露的 AccessKey,并对认知范围内的 Nacos 风险进行了填补。后来在查阅资料的过程中,发现关于如何更加安全的使用 Nacos, 官方的公众号文章给出了一个比较全面的方案: Nacos 配置安全最佳实践。
本文结合官方的 Nacos 配置安全最佳实践及我们遇到的实际场景,做了摘录、补充和总结。
前言
配置管理作为软件开发中重要的一环,肩负着连接代码和环境的职责,能很好的分离开发人员和维护人员的关注点。
Nacos 的配置管理功能就很好地满足了云原生应用对于配置管理的需求:既能做到配置和代码分离,也能做到配置的动态修改。
在本文中,我们将会从全局视角入手,讨论如何才能保证 Nacos 配置的安全性(security),即如何保证配置信息不被恶意用户获取或者泄漏。
配置架构
Nacos 配置部分的整体架构如下:
对于上图中的每一条链路,都需要考虑有没有两个基本的安全动作:认证(Identification)和鉴权(Authentication)。从上图可以看到,配置信息可能的泄漏方式有:
通过 Nacos-client 获取配置。
通过控制台获取配置。
通过服务器之间的通信协议获取配置。
直接访问持久化层(比如 DB)获取配置。
泄漏点分析
Nacos 客户端场景的认证和鉴权
在 Nacos 客户端尝试从服务端获取配置时,服务端需要确认客户端的身份,并确认该身份有权限获取配置。在默认的 Nacos server 配置中,不会对客户端鉴权,即任何能访问 Nacos server 的用户,都可以直接获取 Nacos 中存储的配置。 所以需要先开启 Nacos server 的鉴权,详见官方Nacos 权限认证。
非 Docker 环境
在Nacos server上修改application.properties中的 nacos.core.auth.enabled 值为 true 即可:
nacos.core.auth.enabled=true
注意:鉴权开关是修改之后立马生效的,不需要重启服务端。
Docker 环境
如果使用官方镜像,请在启动 docker 容器时,添加如下环境变量:
NACOS_AUTH_ENABLE=true
例如,可以通过如下命令运行开启了鉴权的容器:
docker run --env PREFER_HOST_MODE=hostname --env MODE=standalone --env NACOS_AUTH_ENABLE=true -p 8848:8848 nacos/nacos-server
客户端如何进行鉴权
以 SpringCloud 项目为例,bootstrap.yml
文件 nacos 中配置中增加username
和password
。
spring:
cloud:
nacos:
config:
server-addr: http://localhost:8848
username: nacos
password: nacos
我们可以在 Nacos 控制台上创建用户、设置权限,做更精细的权限管理。Nacos 权限可分为:
只读(r)
只写(w)
读写(rw)
注意: 亲自测试发现,Nacos 即做配置中心又做注册中心时,使用只读账号项目讲无法启动。我测试的版本是 1.4.1,后面看下最新的版本是否有此问题,如果依旧存在准备去 github 上提交个 issues。
配置控制台场景的认证和鉴权
开源版本的 Nacos 控制台,在登录的时候,会通过控制台的 login 接口,获取临时的 accessToken,然后后续的操作,都是以 accessToken 来做认证鉴权。
服务器之间的认证
Nacos 服务器之间需要同步一些信息,这时也需要认证对方身份,以确认对方真的是 Nacos-server,而不是伪装的。
在 1.4.1 之前,是通过 User-Agent 这个 header 来认证的,这种原始的认证方式,很容易被伪造。在 2021 年 1 月,Nacos 出了一个安全漏洞,外部用户能够伪装为 Nacos-server 来获取/修改配置( https://github.com/alibaba/nacos/issues/4593 )
所以 1.4.1 及之后的版本,认证的 header 以及对应的值可以自己配置。在 application.properties 中,修改如下值即可:
# 不使用User-Agent来认证
nacos.core.auth.enable.userAgentAuthWhite=false
# 认证header的key
nacos.core.auth.server.identity=Authorization
# 认证header的value
nacos.core.auth.server.identity.value=secret
这样,只有发送了 header Authorization: secret 的请求,才能确认对方是服务端,才能同步集群信息;否则就拒绝同步。由于 Nacos-server 需要全部权限才能同步配置数据,所以对于 Nacos-server 之间,则不需要做鉴权。这样,就能让服务器之间的通信也能做到安全可信了。
持久化层的安全
Nacos 的配置信息,都是存储在持久化层的。比如 Nacos 默认的持久化层是 MySQL。
为了防止通过 git 或者其他方式将 MySQL 的用户名和密码泄漏出去,我们需要定时修改 MySQL 的用户名和密码。通常的做法是使用两个数据库用户,比如 UserA 和 UserB。如果要更新密码,则按照如下方式操作:
将 Nacos server 访问数据库的用户从 UserA 切换到 UserB。
更新 UserA 的密码。
将 Nacos server 访问数据库的用户从 UserB 切换回 UserA。
更新 UserB 的密码。
最佳实践(重点)
不要暴露在公网(重中之重)
Nacos 是一个内部微服务组件,需要在可信的内部网络中运行,不可暴露在公网环境,防止带来安全风险。定期修改密码
在使用 Nacos 用户名密码认证的情况下,如果恶意用户拿到了 Nacos 的用户名和密码,那么他就有可能拿到应用的配置。但如果定期修改了密码的话,就能有效限制配置泄漏的时间段,减少攻击面。轮转 Nacos 内部认证的 key
前文有提到 Nacos 服务器之间的认证是通过nacos.core.auth.server.identity
来完成的,但如果恶意用户入侵,也会导致泄漏,从而导致配置泄漏。所以对于自建 Nacos,需要定期更换nacos.core.auth.server.identity.value
,确保恶意用户无法伪装为 Nacos Server 来获取、修改配置。轮转持久化层的用户名和密码
为了防止配置从持久化层泄漏出去,所以需要定时修改持久化层的认证信息。通常 Nacos 的持久化层都是 DB,所以需要定时修改数据库的用户名和密码。设计安全预案并定时执行
有了如上重重保险,理论上万无一失,但是因为人的操作总有失误,所以还是需要指定安全预案:
_ 定时检查配置的监听列表,确认没有未授权的机器。
_ 服务器被攻破后,如何修改 nacos.core.auth.server.identity.value 的方案。