一、为什么要给数据包加密?先搞懂我们要解决的痛点

做开发的朋友肯定都遇到过这类糟心事:比如你要给服务器存个数据库密码、SSL证书,直接放明文的配置文件里?那一旦代码被泄露,所有敏感数据直接裸奔。用普通的加密包?要么是版本不兼容,比如老环境读不了新包,要么是密钥管理乱成一团——比如把加密密钥直接写在脚本里,或者换个开发就找不到密钥在哪。

Chef Encrypted Data Bag(以下简称CEB)就是专门解决这些问题的工具,它是Chef生态里用来存敏感数据的加密数据包,核心就是把密码、证书这类“不能公开的东西”,用加密的方式打包存起来,只有有权限的人才能解密拿到内容。

先给大家说清楚最常用的两个应用场景:第一个是运维部署场景,比如用Chef批量给上百台服务器部署服务,每个服务的数据库密码、SSL证书不一样,直接明文写进Chef的Cookbook(就是Chef的脚本包)里太危险,用CEB存,部署的时候服务器自己解密拿就行;第二个是开发测试场景,比如团队共享测试环境的敏感配置,不用每个人单独存,用CEB加密后统一管理,既安全又方便。

二、提前准备:先把需要的工具搭好

在动手之前,得先把基础环境搭起来,所有示例统一用Chef 17.x版本的技术栈,这个版本兼容性最好,也是现在大部分企业在用的版本。

首先要装Chef Client,不管是Windows还是Mac还是Linux,都可以通过官方脚本装,这里给大家通用的安装命令:

# 安装Chef Client 17.x(所有系统通用的安装脚本)
curl -L https://www.chef.io/chef/install.sh | bash -s -- -v 17

装完之后要验证安装成功,输入下面的命令,能看到版本号就行:

# 验证Chef安装,输出类似Chef Client 17.10.12(版本号可能略有差异)
chef-client -v

另外,我们后面要用到Chef的knife命令,这是Chef用来管理各种资源的工具,装完Chef Client之后会自带,不用额外装。

三、手把手实战:创建加密数据包

接下来就到最核心的实战环节了,我们分步骤来,每一步都有详细的示例,大家跟着敲就行。

3.1 生成加密用的密钥

CEB加密数据包,必须要有一个加密密钥,这个密钥就像你家的钥匙,只有拿着这把钥匙的人才能打开加密包。密钥可以用Chef自带的命令生成,也可以自己写一个符合要求的字符串,这里推荐用Chef自带的命令生成,安全系数更高。

生成密钥的命令如下,执行之后会在当前目录生成一个叫chef-encryption-key.pem的文件,这个就是我们的加密密钥,一定要存好,丢了就再也解不开加密包了:

# 生成Chef加密密钥,输出到当前目录的chef-encryption-key.pem文件
openssl genrsa -out chef-encryption-key.pem 2048

生成之后可以看一下密钥的内容,是一串加密的字符串,不要随便改:

# 查看密钥内容(确认生成成功)
cat chef-encryption-key.pem

3.2 创建加密数据包的内容文件

加密数据包的内容是JSON格式的,里面可以存任何你想加密的敏感数据,比如密码、证书、API密钥等等。我们来创建一个示例的内容文件,叫db-credentials.json,里面存一个测试用的数据库用户名、密码和端口:

{
  "id": "db-credentials", // 加密包的唯一标识,必须和文件名前缀一致
  "username": "test_db_user", // 数据库用户名
  "password": "TestPass1234!", // 数据库密码(敏感数据)
  "port": 3306, // 数据库端口
  "ssl_cert": "-----BEGIN CERTIFICATE-----\nMIIC..." // 示例SSL证书内容(真实环境要放完整证书)
}

这里要注意,JSON文件里的id字段必须和加密包的名字一致,比如我们后面要创建的加密包叫db-credentials,那id就必须是db-credentials,不然会报错。

3.3 用密钥加密数据包

有了内容文件和密钥,接下来就是加密了,用Chef的knife命令来加密,命令格式是knife data bag create <包名> --secret-file <密钥文件路径> --json <内容文件路径>,我们来执行示例命令:

# 加密db-credentials数据包,用刚才生成的密钥
knife data bag create db-credentials --secret-file ./chef-encryption-key.pem --json ./db-credentials.json

执行成功之后,会输出类似“Created data bag item db-credentials”的提示,说明加密包已经创建好了。如果是第一次用Chef,可能会提示你配置Chef Server的地址,跟着提示填就行,或者提前在~/.chef/config.rb里配置好。

四、解密调用加密数据包:拿到敏感数据

加密完之后,最关键的就是怎么解密拿到里面的内容,这也是大家最关心的部分,我们分两种场景来说:一种是在本地测试解密,一种是在Chef Cookbook里调用解密。

4.1 本地测试解密

本地测试解密的命令很简单,用knife data bag show <包名> --secret-file <密钥文件路径>就行,示例命令:

# 解密查看db-credentials加密包的内容
knife data bag show db-credentials --secret-file ./chef-encryption-key.pem

执行之后就能看到我们刚才存的所有内容,包括密码和证书,比如会输出:

id:         db-credentials
username:   test_db_user
password:   TestPass1234!
port:       3306
ssl_cert:   -----BEGIN CERTIFICATE-----
            MIIC...

这里要注意,如果你用的密钥不对,或者加密包的id和文件名不一致,会提示解密失败,或者拿到乱码,一定要仔细检查。

4.2 在Chef Cookbook里调用解密

在实际的部署场景中,我们需要在Chef的Cookbook里调用加密数据包,拿到里面的内容,然后配置到服务器上。Cookbook是Chef的核心组件,相当于用来部署服务的脚本包,我们来写一个简单的Cookbook示例,用来获取加密数据包里的数据库密码,然后配置到Nginx的配置文件里。

首先,我们创建一个Cookbook,叫test-cookbook,然后在里面的recipes目录下创建一个叫default.rb的脚本,内容如下:

# 引入Chef的Encrypted Data Bag库(必须引入,不然会报错)
require 'chef/encrypted_data_bag_item'

# 1. 定义加密包的名字和密钥文件路径
data_bag_name = 'db-credentials' # 加密包的名字,和之前创建的一致
secret_file_path = '/root/chef-encryption-key.pem' # 密钥文件在服务器上的路径

# 2. 读取密钥文件的内容
secret = Chef::EncryptedDataBagItem.load_secret(secret_file_path)

# 3. 解密加密数据包,拿到里面的内容
db_credentials = Chef::EncryptedDataBagItem.load(data_bag_name, 'db-credentials', secret)

# 4. 把解密后的内容配置到Nginx的配置文件里(示例配置)
template '/etc/nginx/conf.d/db.conf' do
  source 'db.conf.erb' # 模板文件
  variables(
    db_username: db_credentials['username'], # 从加密包拿到的用户名
    db_password: db_credentials['password'], # 从加密包拿到的密码
    db_port: db_credentials['port'] # 从加密包拿到的端口
  )
  owner 'root'
  group 'root'
  mode '0644'
end

然后在Cookbook的templates/default目录下创建一个db.conf.erb的模板文件,内容如下:

# 从加密数据包获取的数据库配置
server {
    listen 80;
    server_name test.example.com;

    location /db {
        proxy_pass http://localhost:<%= @db_port %>;
        proxy_set_header X-DB-User <%= @db_username %>;
        proxy_set_header X-DB-Pass <%= @db_password %>;
    }
}

这个Cookbook的作用就是,在部署的时候,自动解密加密数据包,拿到里面的数据库配置,然后生成Nginx的配置文件,整个过程中,密码等敏感数据不会以明文的形式出现在Cookbook或者配置文件里,非常安全。

五、解决常见痛点:版本兼容性与密钥管理

CEB虽然好用,但很多人在使用的时候会遇到版本兼容性和密钥管理的问题,我们来一一解决。

5.1 版本兼容性问题:老环境读不了新包

这个问题主要是因为Chef不同版本的加密算法不一样,比如Chef 12.x及以下版本用的是AES-256-CBC加密,而Chef 13.x及以上版本默认用的是AES-256-GCM加密,老版本的Chef不支持GCM加密,所以读不了新包。

解决方法有两个:第一个是升级所有环境的Chef版本到13.x及以上,这个是最推荐的,因为GCM加密比CBC更安全;第二个是如果必须用老版本的Chef,那在创建加密包的时候,指定用CBC加密,命令如下:

# 创建加密包的时候指定用CBC加密,兼容老版本Chef
knife data bag create db-credentials --secret-file ./chef-encryption-key.pem --json ./db-credentials.json --encrypt-with aes-256-cbc

这样创建的加密包,老版本的Chef就能读取了。

5.2 密钥管理痛点:密钥乱存找不到

很多人用CEB的时候,把密钥直接写在Cookbook里,或者随便丢在某个服务器上,导致密钥泄露或者找不到,正确的密钥管理方法有三个:

第一个是把密钥存在Chef Server的Secret Store里,Chef Server自带的Secret Store可以安全存储密钥,不用存在本地,命令如下:

# 把密钥存在Chef Server的Secret Store里,名字叫db-secret
knife data bag create secrets db-secret --secret-file ./chef-encryption-key.pem --json '{"id":"db-secret","secret":"'$(cat chef-encryption-key.pem)'"}'

然后在Cookbook里调用的时候,直接从Secret Store里拿密钥就行,不用存在本地。

第二个是用第三方密钥管理工具,比如HashiCorp Vault,把CEB的密钥存在Vault里,只有有权限的人才能拿到密钥,安全性更高。

第三个是定期轮换密钥,比如每3个月换一次加密密钥,换密钥的方法也很简单,就是重新生成一个密钥,然后用新密钥重新加密所有的加密包就行,命令如下:

# 重新生成新密钥
openssl genrsa -out new-chef-encryption-key.pem 2048
# 用新密钥重新加密db-credentials包
knife data bag create db-credentials --secret-file ./new-chef-encryption-key.pem --json ./db-credentials.json

六、CEB的优缺点与注意事项

6.1 技术优缺点

CEB的优点很明显:第一是安全,用AES加密,只有有权限的人才能解密;第二是方便,支持批量管理敏感数据,适合团队协作;第三是兼容性好,支持不同版本的Chef,只要配置对加密算法就行;第四是轻量,不用额外装复杂的工具,只要有Chef就能用。

缺点也有:第一是依赖Chef生态,如果不用Chef的话,用起来比较麻烦;第二是密钥管理比较麻烦,需要专门的密钥管理机制,不然容易出问题;第三是没有可视化的管理界面,所有操作都是命令行,对新手不太友好。

6.2 注意事项

第一是密钥一定要存好,丢了密钥就再也解不开加密包了,一定要备份;第二是加密包的id一定要和文件名前缀一致,不然会报错;第三是不要把密钥直接写在Cookbook里,一定要用专门的密钥管理工具;第四是定期轮换密钥,提高安全性;第五是在生产环境中,一定要用强加密算法,比如AES-256-GCM,不要用弱加密算法。

七、总结

Chef Encrypted Data Bag是一个非常实用的敏感数据存储工具,解决了很多开发者在存储密码、证书等敏感数据时遇到的安全问题和兼容性问题。通过本文的实战示例,大家应该已经掌握了CEB的创建、加密、解密和调用的方法,也学会了如何解决版本兼容性和密钥管理的痛点。

在实际使用中,一定要注意密钥的管理,定期轮换密钥,不要把密钥泄露出去,这样才能真正保证敏感数据的安全。CEB适合用在Chef生态的部署场景和团队协作的开发测试场景,如果你是用Chef的开发者,一定要试试这个工具,能帮你省很多事。