使用 Vault 保护您的部署密钥





5.00/5 (1投票)
使用 Harshicorp Vault(Vagrant 工具的作者)以安全的方式存储部署凭据
背景
“不要将密码检入源代码控制或将其硬编码在应用程序中。如果运维人员发现您这样做,他们会用勺子挖掉您的眼睛。不要给他们这个机会。密码应始终由执行部署的用户输入。处理多层系统的身份验证有几种可接受的方式。您可以使用证书、目录服务或单点登录系统。”
此引言摘自《持续交付:通过构建、测试和部署自动化实现可靠的软件发布》(Addison-Wesley Signature Series (Fowler)) 一书的第 2 章,作者是 David Farley 和 Jez Humble。
HashiCorp 的 Vault 是一个可能为 DevOps 工程师提供企业级安全级别的工具,同样也适用于初创公司这样的小团队。
需要解决的挑战
在文章结束时,我们应该能够
- 在 Ubuntu 14.04 :TS 服务器上安装 Vault
- 初始化 Vault
- 在 Vault 中存储秘密
- 在 Vault 中访问秘密
安装
正式的安装步骤已在本篇文章中介绍: https://vaultproject.io/docs/install/。为了方便演示,我将提供一个半自动脚本,该脚本将 Vault 0.1.2 版本安装到 /opt/vault_0.1.2 文件夹中,并将其配置为监听本地主机端口 8200,同时注册为一个名为 vault-server
的服务。
#!/bin/sh
VAULT_VERSION=${VAULT_VERSION-0.1.2}
VAULT_PATH=/opt/vault_$VAULT_VERSION
UNAME=`uname -m`
if [ "$UNAME" != "x86_64" ]; then
PLATFORM=386
else
PLATFORM=amd64
fi
if [ "$(id -u)" != "0" ]; then
echo "Installation must be done under sudo"
exit 1
fi
test -x $VAULT_PATH/vault
if [ $? -eq 0 ]; then
echo vault already installed
exit 1
fi
apt-get install -y curl unzip
rm /opt/vault_${VAULT_VERSION}_linux_${PLATFORM}.zip
curl -L "https://dl.bintray.com/mitchellh/vault/vault_${VAULT_VERSION}_linux_${PLATFORM}.zip" >
/opt/vault_${VAULT_VERSION}_linux_${PLATFORM}.zip
mkdir -p $VAULT_PATH
unzip /opt/vault_${VAULT_VERSION}_linux_${PLATFORM}.zip -d $VAULT_PATH
chmod 0755 $VAULT_PATH/vault
chown root:root $VAULT_PATH/vault
echo create config
cat <$VAULT_PATH/vault-config.hcl
backend "file" {
path = "$VAULT_PATH/storage"
}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = 1
}
EOF
echo create run script
cat <$VAULT_PATH/vault-server
#!/bin/sh
if [ -z \$1 ]
then
echo syntax: vault-server /PATH/TO/VAULT/HCL/CONFIG optional_flags
exit 1
fi
BASEDIR=\$(dirname \$0)
cd \$BASEDIR
./vault server -config=\$1 \$2 \$3 \$4 \$5 \$6 \$7 \$8 \$9
EOF
chmod 0755 $VAULT_PATH/vault-server
chown root:root $VAULT_PATH/vault-server
echo create upstart script
cat </etc/init/vault-server.conf
description "Vault server"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
script
# Make sure to use all our CPUs, because Vault can block a scheduler thread
export GOMAXPROCS=`nproc`
exec $VAULT_PATH/vault-server ${VAULT_PATH}/vault-config.hcl >>/var/log/vault.log 2>&1
end script
EOF
service vault-server start
cat /var/log/vault.log
检查安装
./vault_status.sh
Error checking seal status: Error making API request.
URL: GET https://:8200/v1/sys/seal-status
Code: 400. Errors:
* server is not yet initialized
该消息表示 Vault 已正确安装和配置,但需要进行初始化。初始化发生在服务器首次启动并连接到从未与 Vault 一起使用过的新后端时。在初始化期间,会生成加密密钥、创建解封密钥,并设置初始根令牌。要初始化 Vault,请使用 vault init
。这是一个未经身份验证的请求,但它仅适用于没有任何数据的全新 Vault。
让我们进行初始化。密钥共享的数量以及用于解锁密钥的密钥共享数量对安全性有重要影响。
它是如何工作的?用于加密数据的密钥也使用 256 位 AES GCM 模式进行加密。这被称为主密钥。加密的密钥存储在后端存储中。然后使用 Shamir 秘密共享技术将主密钥分割。Shamir 秘密共享技术确保没有单个人(包括 Vault)能够解密数据。要解密数据,需要一定数量的密钥(默认是三个,但可配置)来解封 Vault。这些密钥预计会分散在三个不同的地点/个人手中。
这与安全的银行保险箱有着完全的类比,一个钥匙在银行人员手中,一个在您手中。在 Vault 的情况下,您可能拥有更高水平的安全性。
为了演示,我将只使用一个密钥。
./vault_init.sh
The number of key shares to split the master key into: 1
The number of key shares required to reconstruct the master key 1
Key 1: af29615803fc23334c3a93f8ad58353b587f50eb0399d23a6950721cbae94948
Initial Root Token: 98df443c-65ee-d843-7f4b-9af8c426128a
Vault initialized with 1 keys and a key threshold of 1!
Please securely distribute the above keys. Whenever a Vault server
is started, it must be unsealed with 1 (the threshold) of the
keys above (any of the keys, as long as the total number equals
the threshold).
Vault does not store the original master key. If you lose the keys
above such that you no longer have the minimum number (the
threshold), then your Vault will not be able to be unsealed.
初始根令牌必须立即保存在安全的位置。
使用 Vault
解封
当 Vault 服务器启动时,它处于密封状态。解封是构建读取解密密钥以解密数据的过程,因此在解封之前,Vault 几乎无法进行任何操作。
让我们解封
./vault_ unseal af29615803fc23334c3a93f8ad58353b587f50eb0399d23a6950721cbae94948
Sealed: false
Key Shares: 1
Key Threshold: 1
Unseal Progress: 0
请注意,如果您设置了更高的阈值,所有密钥持有者都需要使用他们的密钥部分执行解封操作。这为访问数据提供了额外的安全级别。
Authorization
为了继续使用 Vault,您首先需要进行身份验证。让我们使用 auth
命令,提供我们的初始 root
令牌来完成此操作。
./vault_ auth 98df443c-65ee-d843-7f4b-9af8c426128a
Successfully authenticated! The policies that are associated
with this token are listed below:
root
策略
Vault 中的访问控制策略控制着用户可以访问的内容。在初始化 Vault 时,只有“root
”策略存在。它为 Vault 中的所有内容提供了超级用户访问权限。
由于我们计划为多个项目存储秘密,因此我们应该能够清晰地区分对不同项目秘密的访问。这就是策略发挥作用的地方。
Vault 中的策略使用 HCL 格式化。HCL 是一种人类可读的配置格式,也与 JSON 兼容,因此您也可以使用 JSON。下面是一个策略示例:
path "secret/project/name" {
policy = "read"
}
它指定了路径,就像我们在某些树状结构中看到的那样,支持通配符。如果您提供了对树状结构特定部分的访问权限,则也会为所有子节点提供相同的访问权限,除非您覆盖它。
策略使用 policy-write
命令注册。
./vault_ policy-write demo demo.hcl
vault policy-write -address=https://:8200 demo demo.hcl
Policy 'demo' written.
部署令牌
现在是时候创建部署令牌了。在我们的例子中,这个令牌将允许我们从 Vault 读取部署的秘密值,除了这个之外没有任何其他特权。
为了做到这一点,我们使用 create token
和 policy
命令。
./vault_create_token_with_policy.sh demo
vault token-create -address=https://:8200 -policy=demo
4d79adad-a4ec-de8b-3f85-5467b3e8536a
存储数据
现在是时候存储一些用于部署的秘密了。为了演示,我们假设是一些 API 密钥和用于部署的私钥。
使用 write
命令来写入秘密。
./vault_write.sh secret/project/name/apikey BLABLABLA
vault write -address=https://:8200 secret/project/name/apikey value=BLABLABLA
Success! Data written to: secret/project/name/apikey
./vault_write_file.sh secret/project/name/id_rsa ./demo_rsa
Success! Data written to: secret/project/name/id_rsa
重要
目前不支持二进制文件存储,但您可以始终存储 base64 编码的文件,就像电子邮件中的 MIME 附件一样。幸运的是,对于大多数部署,我们都有文本格式的 API 密钥和私钥。
检索数据
有两种方法可以访问您的数据。第一种是使用 Vault 客户端本身。
./vault_read.sh secret/project/name/apikey
vault read -address=https://:8200 secret/project/name/apikey
Key Value
lease_id secret/project/name/apikey/a74dd189-de4b-1c98-ba24-6b29258c511b
lease_duration 2592000
lease_renewable false
value BLABLABLA
./vault_read.sh secret/project/name/id_rsa
vault read -address=https://:8200 secret/project/name/id_rsa
Key Value
lease_id secret/project/name/id_rsa/204ba657-9648-4fa5-8f82-ede992a054b4
lease_duration 2592000
lease_renewable false
value -----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEApiLCR2sgf5unedMk1a2maL22PsoPwQWpGTDFZgCvhSVWvnBs
...
第二种是使用基于 HTTP 的 API。对于这种情况,您需要使用我们之前分配的部署令牌进行授权。
./vault_curl.sh 4d79adad-a4ec-de8b-3f85-5467b3e8536a secret/project/name/apikey
curl -H X-Vault-Token: 4d79adad-a4ec-de8b-3f85-5467b3e8536a -X
GET https://:8200/v1/secret/project/name/apikey
{"lease_id":"secret/project/name/apikey/2189c6c4-1fa7-0f4d-2598-bded29a4ce6b",
"renewable":false,"lease_duration":2592000,"data":{"value":"BLABLABLA"},"auth":null}
./vault_curl.sh 4d79adad-a4ec-de8b-3f85-5467b3e8536a secret/project/name/id_rsa
curl -H X-Vault-Token: 4d79adad-a4ec-de8b-3f85-5467b3e8536a -X
GET https://:8200/v1/secret/project/name/id_rsa
{"lease_id":"secret/project/name/id_rsa/ec509e1f-09a7-6aee-54e2-f3364720c7de",
"renewable":false,"lease_duration":2592000,"data":{"value":
"-----BEGIN RSA PRIVATE KEY-----\nMIIEpgI......-----END RSA PRIVATE KEY-----"},
"auth":null}
保护 Vault HTTP API
Vault 本身支持 HTTPS,但我认为在生产部署中,最好将其隐藏在 Web 服务器作为代理的后面。
下面是一个 Nginx 配置示例。
server {
listen 443 ssl;
server_name vault.YOURDOMAIN.COM;
ssl_certificate YOUR_SSL_CERTIFICATE.crt;
ssl_certificate_key YOUR_SSL_CERTIFICATE_KEY.key;
location / {
proxy_pass http://127.0.0.1:8200;
proxy_set_header Host $host;
expires -1;
}
#ssl config per https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+
SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+
aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:
!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED";
ssl_prefer_server_ciphers on;
ssl_dhparam dhparam.pem;
#only supported since 1.3.7
ssl_stapling on;
ssl_stapling_verify on;
# Optimize SSL by caching session parameters for 10 minutes.
# This cuts down on the number of expensive SSL handshakes.
# The handshake is the most CPU-intensive operation,
# and by default it is re-negotiated on every new/parallel connection.
# By enabling a cache (of type "shared between all Nginx workers"),
# we tell the client to re-use the already negotiated state.
# Further optimization can be achieved by raising keepalive_timeout,
# but that shouldn't be done unless you serve primarily HTTPS.
ssl_session_cache shared:SSL:10m; # a 1mb cache can hold about 4000 sessions,
# so we can hold 40000 sessions
ssl_session_timeout 10m;
add_header Strict-Transport-Security max-age=63072000;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
}
代码示例
代码可以从仓库下载: https://github.com/Voronenko/hashi_vault_utils
一些文件只是为了更方便地使用现有的 Vault 功能。
- vault_status.sh - 获取 Vault 的状态
- vault_policy.sh - 列出已知的策略,或显示作为第一个参数提供的策略的详细信息。
- vault_create_token_with_policy.sh - 创建并返回具有作为第一个参数提供的策略的令牌。
- vault_read.sh - 通过键(第一个参数)读取秘密。
- vault_write.sh - 通过键(第一个参数)写入秘密,并设置其值(第二个参数)。
- vault_write_file.sh - 通过键(第一个参数)写入秘密,并存储作为第二个参数提供的文本文件的内容。
- vault_curl.sh - 可用于测试 HTTP API。第一个参数 - 访问令牌,第二个参数 - 要读取的秘密键。
关注点
这只涵盖了在您的组织中开始使用 Vault 的基本方面,但这可能是向前迈出的一个很好的第一步。