Relational Database应该是目前生产中最常见的数据库类型,而且有着几十年的历史。那也就是说这种数据库一定会支持当时最常见的身份验证方式,即用户名密码登录。如果一个程序想要访问数据库,它就要在请求中发送登陆用的用户名和密码,这里的用户名会在数据库内部被匹配到一个已经存在的ROLE,并且验证密码,成功后赋予PRIVILEGES来访问数据库中的资源。整个过程很直白,用户需要保护的信息就是用户名和密码这两个string。那么问题来了,我们该如何安全的保存它们呢?
- 把用户名和密码hardcode在用户端程序中
- 记住密码,每次程序运行时手动输入密码
- 储存在第三方管理程序中,在用户端程序需要时实时获取
第一条在生产环境中一般是不会遇到的,虽然很方面快捷,但一旦程序的代码泄露,攻击者可以直接登录数据库。但有些程序是被编译成了二进制啊,代码也不会直接暴露,那是不是就安全了呢?相对来说是的,但是,二进制虽然人类很难读懂,但却也是明文,不是绝对的安全。还要注意的是,不要commit你的用户名和密码,既使是测试用的临时用户。第二条很安全,只要不把密码写下来贴在显示器上面就完美了,但是但是但是,什么样的程序会在运行过程中一直向用户索取密码呢?这就是一个极端的例子,安全性达到了,但是实用性没了。最后一条换种说法就是自动化敏感信息的获取,不然人类用户参与其中来达到安全和实用性的一个平衡。这里说的“第三方程序”范围很广泛,包括Vault在内,他们时DevOps中很重要的一环,需要正确的配合访问权限来达到一个目的,即只让合法的程序访问它必要的机密信息。比如说,在AWS中,假设数据库是以RDS架设并且访问凭据存在AWS的SecretsManager中,那么可以通过Security Group让RDS只接受来自合法服务器的请求,并让SecretsManager只接受来自服务器绑定的role的访问。
那么Vault和其他人比起来有什么特殊的地方吗?Vault的特殊在于它可以动态的生成访问数据库的凭据,这样一来凭据内容将是不可预测的,即使被截获并破解也不能保证下次使用时还是有效的。这种方式没有什么高端的技术,只是让静态的凭据“动”了起来,举个例子,比如说凭据就是把靶场的设计目标,集中靶子就像是获取了可用的凭据,那么如何提高设计难度呢?一是可以靶子做得很小,但是时间长了或者子弹密集了也还是会击中。那怎样再增加难度呢?则是让靶子一直动,变换位置,这就是Vault相比于其他系统的特殊之处。听着高端,实际上呢就是个自动化的过程,其实不过是在Vault中植入DB的访问权限,并让Vault作为代理来生成和删除DB的ROLE,大致流程如下:
- 开启database secrets engine
- 配置开启的engine:选则db的引擎,指定Vault的role,提供db已有的一个访问凭据
- 定义db中如何生成这个临时的ROLE,以及它有哪些权限
- App向Vault请求(租赁)一个临时db访问凭据 (假设App已拥有可用的Token)
- Vault在DB中创建相应的ROLE并记录凭据
- 将新创建的凭据返回给用户,并可开始访问DB
上图为Vault文档中图片,像所有其他engine一样,我们要先开启:
vault secrets enable -path=psql databae # 我们想弄一个postgres
vault secrets list
在database这种引擎里Vault和DB之间的是假设互相相信的,也就是说DB会接受Vault的请求。然后就是要指定要与哪一个已经在运行的数据库连接,哪一种db引擎,比如说postgres,然后还要提供DB中的一个有权限对身份信息改写的权限,这里会直接使用root来举例子。还有一个option来指明哪些Vault的role可以与这个路径下的db engine进行访问,如果没有特殊要求可用"*"。下面我们假设已经有一个正在运行的postgres服务在localhost:5432,并且root的密码为”myrootpass“:
vault write psql/config/my-postgres \
plugin_name="postgresql-database-plugin" \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres" \
allowed_roles="*" \
username="root" \
password="myrootpass" \
vault read psql/config/my-postgres # check if added
此时我们就配置好了一个Vault对于这个postgres的访问所需的一切权限,接下来要定义说db会返回给Vault一个什么样的ROLE,有什么权限。这里是允许用SQL原语来自定义的,比如说我们可以先写一句SQL来创建一个全局只读的ROLE,并存入文件role.sql:
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";
其中的name和password是由Vault在请求创建ROLE时动态指明的,然后要让Vault创建一个db引擎的role(起名为readonly)来映射这个数据库中的role,并记录这句SQL:
vault write psql/roles/readonly \
db_name=my-postgres \
creation_statements=@role.sql \
default_ttl=1h max_ttl=24h # TTL不宜过长,按需求来设定
vault read psql/roles/readonly
vault list sys/leases/lookup/psql/creds/readonly # 列出lease_id
到这里Vault和DB间的映射关系就完成了,已经可以开始动态获取DB访问凭据了:
vault read psql/creds/readonly
vault read psql/creds/readonly
vault read psql/creds/readonly #每次都有不一样的凭据生成
指令返回的username和password就可以用来登录postgres并有着全局可读权限,也可以登录postgres输入“\du"来查看刚刚创建的ROLE。这里还会返回lease_id可以用来renew或者revoke对应的凭据,并更新DB。
vault lease renew psql/creds/readonly/IQKUMCTg3M5QTRZ0abmLKjTX
vault lease revoke psql/creds/readonly/IQKUMCTg3M5QTRZ0abmLKjTX
vault lease revoke -prefix psql/creds/readonly # revoke所有readonly的leases