12.让Ansible更安全:使用Vault进行加密
回到:Ansible系列文章
各位读者,请您:由于Ansible使用Jinja2模板,它的模板语法 {{}} 和 {%%} 和我博客系统hexo的模板使用的符号一样,在渲染时会产生冲突,尽管我尽我努力地花了大量时间做了调整,但无法保证已经全部都调整。因此,如果各位阅读时发现一些明显的诡异的错误(比如像这样的空的
行内代码),请一定要回复我修正这些渲染错误。
12.让Ansible更安全:使用Vault进行加密
管理目标节点时,有些操作需要使用密码才允许访问,但Ansible是一个自动化配置管理工具,在自动化操作的阶段中要求交互式输入密码的行为应该是一件让人败兴的事。
通常,实现非交互式的方案有:
- (1).将敏感数据写入文件(比如写入变量文件),然后读取,这种方案不安全;
- (2).定义敏感数据对应的环境变量,缺点是有些客户端工具不一定支持这种方式,且这种方案不够方便;
- (3).使用命令行选项,但缺点是不安全,且Ansible不支持这种功能;
- (4).expect类工具,缺点是不方便。
Ansible自身提供了更方便更安全的方案:Vault加密,它使用AES256加密算法为Ansible保驾护航。
在Ansible 2.4版本以前,其Vault加密功能并不友好,功能限制较大,比如所有任务涉及到的加密必须共享使用同一个密码,比如更早的版本不能加密部分字符串。现在的Ansible版本的Vault加密功能则比较友好,本文会详细介绍友好版本的Vault加密功能。
12.1 一个入门示例:创建一个加密文件
Ansible使用ansible-valut命令完成Vault加解密相关操作,它有很多子命令,比如创建加密文件的create子命令、查看加密文件的view子命令,等等,这些子命令的选项大多类似。
1 | ansible-vault --help |
例如,使用create子命令创建一个加密文件passwd_prompt.yml
,并在此文件中写入一个密码变量。
1 | ansible-vault create --vault-id @prompt passwd_prompt.yml |
上面使用了--vault-id @prompt
作为ansible-vault的参数,当执行ansible-vault create
时,这会以交互式的方式提示用户输入一个密码,这个密码是加密这个加密文件passwd_prompt.yml
所需的密码。简单地说,这个文件里保存了加密的密码数据,但要访问这个文件,也需要密码。为了区分,**本文之后将称该密码为”凭据密码”**。
在上面的示例中,我设置了一个变量mypasswd
,且其值为123456
。当退出编辑器后,会自动加密该文件。例如:
1 | 为排版需求,我截断了一部分字符 |
如果想要查看该密文,可使用ansible-vault的view子命令,同样需要提供凭据密码。
1 | ansible-vault view --vault-id @prompt passwd_prompt.yml |
另外,请暂时记住加密文件中的第一行$ANSIBLE_VAULT;1.1;AES256
,稍后会解释该行各字段的含义。
12.2 Vault ID和凭据密码提供方式
在上面的示例中,使用了选项--vault-id @prompt
,它的行为是以交互式的方式提示用户输入凭据密码。
实际上,--valut-id
选项的完整用法是这样的:
1 | --vault-id label@prompt |
在解释这些用法之前,介绍一点Ansible Vault的历史:在以前的Ansible版本中,每次执行ansbile或ansible-playbook命令时都只能提供一个凭据密码,这使得本次执行的所有任务中所涉及到的所有加密都只能共同使用这一个凭据密码。
1 | --vault-id dev_mysql@xxx |
也可以省略Vault ID,这时将统一使用默认的Vault ID。
--vault-id label@xxx
中的xxx是什么呢?它是凭据密码的来源。凭据密码的来源有三种:
- (1).prompt:prompt是固定的值,表示以交互式的方式提示用户提供凭据密码
- (2).普通文件的文件名:比如”a.txt”,表示从该文件中读取凭据密码
- (3).脚本文件名:表示从该脚本执行结果中获取凭据密码
例如有普通文件a.txt:
1 | echo '123456' >a.txt |
有脚本文件a.sh内容如下(py脚本、perl脚本甚至是程序等都可以,只要密码会输出到标准输出并且有可执行权限即可):
1 | !/bin/bash |
那么下面用三种不同的凭据密码源来创建三个加密文件:
1 | ansible-vault create --vault-id id1@prompt first_encrypted.yml |
如果省略Vault ID,则:
1 | ansible-vault create --vault-id @prompt first_encrypted1.yml |
当需要访问加密数据时,比如ansible命令、ansible-playbook命令、ansible-vault命令等,需指定与加密时相同的Vault ID,且可以指定多个。
例如:
1 | 以文件的方式获取凭据密码 |
12.3 加密已存在的文件
如果想要加密一个已经存在的文件,使用ansible-vault encrypt
,用法和create子命令几乎一样。
例如,plain.yml文件目前是明文的变量文件:
1 | cat plain.yml |
加密之:
1 | ansible-vault encrypt --vault-id [email protected] plain.yml |
可以一次性加载多份数据,但是它们使用相同的Vault ID和密码。
1 | ansible-vault encrypt --vault-id [email protected] foo.yml bar.yml baz.yml |
12.4 加密协议头的含义
在每个已加密的文件中,都包含了一行头部数据。可使用cat命令查看,例如:
1 | cat plain.yml |
有两种协议头:
1 | $ANSIBLE_VAULT;1.1;AES256 |
其中第一个字段目前只能是$ANSIBLE_VAULT
。
第二个字段1.1、1.2表示的是Vault格式的版本号,如果给定了Vault ID,则版本号为1.2,如果使用默认的Vault ID,则版本号为1.1。老版本还有1.0版本的,但现在不会设置成该版本号,1.0版本号的加密数据只能被读。
第三个字段目前只支持AES256加密算法。
第四个字段,如果加密时指定了Vault ID,则第四个字段保存的是Vault ID,否则不设置第四个字段。
当忘记了某个加密文件对应的Vault ID后,可以直接cat命令查看该加密文件来获取ID,也可以直接提取出来:
1 | awk -F';' 'NR==1{print $4}' encrypted.yml |
如果Ansible需要访问多个Vault加密的文件,将会自动根据Vault ID去寻找用户提供的凭据密码。
例如,对于如下命令来说,main.yml中引用了两个加密的变量文件,那么自然需要两个Vault ID和两个密码。当任务执行过程中需要解密第一个文件时,Ansible将自动获取其Vault ID并从命令行中寻找对应的Vault ID及其凭据密码来源。
1 | ansible-playbook --vault-id [email protected] --vault-id [email protected] main.yml |
12.5 解密已加密的文件
对于一个已Vault加密的文件,可使用ansible-vault decrypt
解密文件。
例如,解密上面示例中已加密的plain.yml:
1 | ansible-vault decrypt --vault-id [email protected] plain.yml |
解密多个文件:
1 | ansible-vault decrypt --vault-id [email protected] foo.yml bar.yml baz.yml |
12.6 修改Vault ID和凭据密码
对于一个已Vault加密的文件,可使用ansible-vault rekey
修改其Vault ID或其凭据密码。
例如,first_passwd.yml
是一个已加密的文件:
1 | cat first_encrypted.yml |
修改它的Vault ID和凭据密码:
1 | ansible-vault rekey --vault-id [email protected] \ |
再看加密后的内容,无论是Vault ID还是密文都已经改变了:
1 | cat first_encrypted.yml |
当然,也可以只修改Vault ID或只修改凭据密码,只需保持label@xxx
中的其中一项不变即可。例如:
1 | Vault ID不变,只修改凭据密码 |
12.7 编辑已加密文件的内容
如果想要编辑一个已Vault加密的文件内容,使用ansible-vault edit
命令。
1 | ansible-vault edit --vault-id [email protected] first_encrypted.yml |
它将自动打开默认的编辑器(如vim)让用户进行编辑。内部过程是先创建一个带有原始明文数据的临时文件(在打开vim时,vim底部状态栏看到临时文件名),用户修改的操作都在该临时文件中进行,当保存时,将自动加密并覆盖原文件。
也可以采用先解密、再修改、再重新加密的方式,而且这种方式可以以非交互式的方式进行。
1 | ansible-vault decrypt xxx |
12.8 ansible-playbook任务中使用Vault加密文件
对数据进行加密的原始目标是在playbook文件、task文件或变量文件中隐藏敏感数据。那么使用加密的文件呢?
例如,名为test.yml的playbook文件内容如下:
1 |
|
此playbook中引入了两个变量文件first_passwd.yml
和second_passwd.yml
,然后分别用两个任务去访问这两个变量文件中定义的两个变量passwd1和passwd2。
这两个变量文件的内容如下:
1 | # first_passwd.yml内容: |
使用不同的Vault ID和凭据密码加密这两个文件:
1 | echo 'abcdef' >a.txt |
然后执行该playbook,因为涉及到了多个加密文件的访问,且Vault ID不同,凭据密码也不同,所以需要指定多个--vault-id
选项。如下:
1 | ansible-playbook --vault-id [email protected] --vault-id [email protected] test.yml |
12.9 加密字符串并嵌入YAML文件
使用ansible-vault encrypt_string
可以加密一段字符串。
例如:
1 | ansible-vault encrypt_string --vault-id [email protected] 'hello' --name 'mysql_pass' |
其中hello
是需要被加密的明文数据,--name mysql_pass
是yaml中的key,比如可以作为变量的名称。也可以省略--name
选项。
1 | ansible-vault encrypt_string --vault-id [email protected] 'hello' |
加密一段字符串之后,就可以将这段加密后的字符串原样拷贝到yaml文件中。比如下面是MySQL Role的一个变量文件:
1 |
|
此外,ansible-vault encrypt_string
也可以从标准输入中获取要加密的明文数据:
1 | ansible-vault encrypt_string --vault-id [email protected] --stdin-name 'mysql_pass' <<<"hello" |
注意从标准输入获取待加密数据时,使用的选项是--stdin-name
,而不是--stdin --name
。
12.10 加速加解密的过程
如果要加解密的文件较多,按照官方文档所说,可安装cryptography包来解决。
1 | pip install cryptography |
12.11 Vault加密最佳实践
Ansible Vault可以加密所有的yaml、json格式的文件,但是不建议直接加密一个包含很多数据的文件,因为加密后不方便查看和修改任务文件,建议加密只包含敏感数据的文件。
比如,下面这个变量文件:
1 |
|
其实该文件只有mysql_pass
是想要隐藏的敏感数据,建议的做法是在此文件中使用jinja2再引用一次以vault_
开头的变量,以vault_
开头是为了一眼就能看到这是Vault加密的变量。例如:
1 |
|
然后单独在一个文件中定义被引用的变量vault_mysql_pass
并加密该变量文件。例如,在mysql_pass.yml
文件里定义vault_mysql_pass
变量:
1 |
|
加密之:
1 | ansible-vault encrypt --vault-id [email protected] mysql_pass.yml |