以前曾經(jīng)介紹過關(guān)于KMS的用法商佛,其中,提到了它的優(yōu)點(diǎn)和用處姆打,我們使用的場景有如下幾點(diǎn):
- 我們產(chǎn)品的環(huán)境的所有的配置都保存在git上(Config As Code?)良姆,所以相關(guān)的密碼、private key等需要加密
- 對AWS上應(yīng)用/服務(wù)涉及的敏感數(shù)據(jù)進(jìn)行加密
- AWS上傳輸?shù)臄?shù)據(jù)進(jìn)行加密幔戏,比如SQS
如果脫離AWS玛追,選擇好像還真是不太多,Harshicorp的Vault是我僅知道的一個(gè)闲延,RatticDB算半個(gè)吧痊剖。
什么是Vault
Vault提供了對token,密碼垒玲,證書陆馁,API key等的安全存儲(chǔ)(key/value)和控制,合愈。它能處理key的續(xù)租叮贩、撤銷、審計(jì)等功能想暗。通過API訪問可以獲取到加密保存的密碼妇汗、ssh key、X.509的certs等说莫。它的特性包括:
- 加密存儲(chǔ). 沒有做到KMS對存儲(chǔ)的HMS(硬件加密)杨箭,但是它傳輸后端提供與KMS類似的功能,允許存儲(chǔ)加密密鑰并執(zhí)行加密操作储狭。 它可以存儲(chǔ)已存在的credentials互婿,也可以為你的基礎(chǔ)設(shè)施動(dòng)態(tài)生成新的credential來限制第三方的訪問,這些credentials到期會(huì)被撤銷辽狈,也可以續(xù)租慈参。同時(shí)還有訪問控制策略來進(jìn)行訪問的權(quán)限管理。
- rotate key刮萌。如果把Vault當(dāng)做加密服務(wù)來使用的話驮配,可以設(shè)置rotate的時(shí)間來生成一個(gè)新的key。
- 審計(jì)的日志。所有對API的調(diào)用都會(huì)記錄在一個(gè)審計(jì)日志上壮锻,
因?yàn)槭褂肰ault的目的是為了
- 持續(xù)集成服務(wù)器上運(yùn)行測試或者部署需要的密碼琐旁、API key、以及private key等需要加密
- 服務(wù)部署是將加密后的應(yīng)用需要的配置解密
同時(shí)我不希望在服務(wù)器上安裝vault的命令行工具猜绣,所以在下面的使用中我都用Restful API的方式灰殴。
啟動(dòng)Vault服務(wù)
Vault存儲(chǔ)[backend(https://www.vaultproject.io/docs/secrets/index.html)]可以是
- 內(nèi)存(開發(fā)模式)
- 磁盤/數(shù)據(jù)庫
- Consoul
- AWS
- ...
我在用Docker啟動(dòng)Vault服務(wù)的時(shí)候使用的Production模式,為了簡單使用了磁盤作為存儲(chǔ)掰邢,但是為了persist牺陶,所以將valut的文件存在了docker volume上面。
docker-compose
文件如下:
version: "2"
services:
vault:
image: vault:0.6.4
volumes:
- vault-volume:/vault/file
environment:
VAULT_LOCAL_CONFIG: '{"backend": {"file": {"path": "/vault/file"}}, "default_lease_ttl": "168h", "max_lease_ttl": "720h", "listener": {"tcp": {"address": "0.0.0.0:8200", "tls_disable": "1"}}, "disable_mlock": true}'
command: "server"
cap_add:
- IPC_LOCK #--cap-add: Add Linux capabilities, in order for Vault to lock memory
ports:
- 8200:8200
volumes:
vault-volume:
external: true
創(chuàng)建volume辣之,啟動(dòng)vault服務(wù)器掰伸,配置通過環(huán)境變量VAULT_LOCAL_CONFIG
傳入。
docker volume create --name vault-volume
docker-compose up -d
從本地的8200
端口應(yīng)該就可以訪問到了:
telnet 0 8200
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
^]
初始化
下面的API請求可以對Vault進(jìn)行初始化召烂,兩個(gè)參數(shù)的意思將master key分成幾份以及還原碱工,這里就用1吧。
curl -X PUT -d "{\"secret_shares\":1, \"secret_threshold\":1}" http://127.0.0.1:8200/v1/sys/init | jq
返回結(jié)果:
{
"keys": [
"36d8ae19eb3e9d48965011e49af99865ca2bc6c78f4e900b7e14482d048d5ea2"
],
"keys_base64": [
"NtiuGes+nUiWUBHkmvmYZcorxsePTpALfhRILQSNXqI="
],
"root_token": "e4347e60-0e72-fa8f-05e8-94c7388bb12c"
}
第一個(gè)是master key的public key奏夫,第二個(gè)是unseal key,最后一個(gè)是root token怕篷。unseal vault之后才能驗(yàn)證進(jìn)行具體的操作。
curl -X PUT -d '{"key": "NtiuGes+nUiWUBHkmvmYZcorxsePTpALfhRILQSNXqI="}' http://127.0.0.1:8200/v1/sys/unseal | jq
結(jié)果:
{
"sealed": false,
"t": 1,
"n": 1,
"progress": 0,
"version": "0.6.4",
"cluster_name": "vault-cluster-603ef85a",
"cluster_id": "ac454859-dc57-ee1d-38c1-71a0226a8cf3"
}
創(chuàng)建新token
為了安全起見酗昼,我們可以用root token創(chuàng)建出有限權(quán)限的新token廊谓,來繼續(xù)后面的操作。假設(shè)這個(gè)token可以讀寫的路徑為secret/*
麻削。在這個(gè)之前我們先創(chuàng)建訪問這個(gè)路徑的policy:
curl -v -X POST -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" -d '{"rules":"path \"secret/*\" {\n policy = \"write\"\n}"}' http://127.0.0.1:8200/v1/sys/policy/admin-policy
curl -X GET -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" http://127.0.0.1:8200/v1/sys/policy/admin-policy | jq
{
"rules": "path \"secret/*\" {\n policy = \"write\"\n}",
"name": "admin-policy",
"request_id": "c7e5a70d-bca8-54b9-eb1d-793f3b027e1f",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"name": "admin-policy",
"rules": "path \"secret/*\" {\n policy = \"write\"\n}"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
對應(yīng)的創(chuàng)建user-policy
:
curl -v -X POST -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" -d '{"rules":"path \"secret/*\" {\n policy = \"read\"\n}"}' http://127.0.0.1:8200/v1/sys/policy/user-policy
curl -X GET -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" http://127.0.0.1:8200/v1/sys/policy/user-policy | jq
{
"name": "user-policy",
"rules": "path \"secret/*\" {\n policy = \"read\"\n}",
"request_id": "104fd2f3-f0ae-86f6-c1f7-ed3df01be962",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"name": "user-policy",
"rules": "path \"secret/*\" {\n policy = \"read\"\n}"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
然后我們分別創(chuàng)建兩個(gè)token蒸痹,admin token和 user token:
curl -X POST -d '{"policies": ["admin-policy"]}' -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" http://127.0.0.1:8200/v1/auth/token/create | jq
{
"request_id": "a58efd8f-4585-d736-4d69-e8ee1d29f19a",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": null,
"wrap_info": null,
"warnings": null,
"auth": {
"client_token": "a1ab3379-665f-15bf-0035-32e059e5d055",
"accessor": "a1e6d92c-9788-a980-04a6-bfa45b0b7c26",
"policies": [
"admin-policy",
"default"
],
"metadata": null,
"lease_duration": 604800,
"renewable": true
}
}
curl -X POST -d '{"policies": ["user-policy"]}' -H "X-Vault-Token:e4347e60-0e72-fa8f-05e8-94c7388bb12c" http://127.0.0.1:8200/v1/auth/token/create | jq
{
"request_id": "c32dc4a6-e432-48db-c59d-5a32c5d780cb",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": null,
"wrap_info": null,
"warnings": null,
"auth": {
"client_token": "5d9e8e7f-e133-4cfa-6f67-880f2799235b",
"accessor": "ed1c10ec-ca1b-7d1f-3a0a-ca4763ea3cec",
"policies": [
"default",
"user-policy"
],
"metadata": null,
"lease_duration": 604800,
"renewable": true
}
}
這樣就有了對于secrets/*
路徑下進(jìn)行讀寫的兩個(gè) token,可以嘗試去admin的token去添加新的鍵值對:
export ADMIN_TOKEN="a1ab3379-665f-15bf-0035-32e059e5d055"
curl -X POST -H "X-Vault-Token:$ADMIN_TOKEN" -d '{"token":"c192d0211cb81fbfeee53fb16e2a7465"}' http://127.0.0.1:8200/v1/secret/api/search
export USER_TOKEN="5d9e8e7f-e133-4cfa-6f67-880f2799235b"
curl -X GET -H "X-Vault-Token:$USER_TOKEN" http://127.0.0.1:8200/v1/secret/api/search | jq
{
"request_id": "dd2fcbae-b74f-6fc8-b895-a2c78667b1f0",
"lease_id": "",
"renewable": false,
"lease_duration": 604800,
"data": {
"token": "c192d0211cb81fbfeee53fb16e2a7465"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
curl -X POST -H "X-Vault-Token:$USER_TOKEN" -d '{"token":"286755fad04869ca523320acce0dc6a4"}' http://127.0.0.1:8200/v1/secret/api/search | jq
{
"errors": [
"permission denied"
]
}
這樣就有了基本的權(quán)限管理呛哟。假設(shè)vault服務(wù)器和應(yīng)用服務(wù)器或者CI的服務(wù)器部署在同一私有網(wǎng)絡(luò)中叠荠,應(yīng)用服務(wù)器和CI slave是不可以ssh的,那么通過應(yīng)用服務(wù)器或者CI在啟動(dòng)的時(shí)候通過HTTP請求扫责,利用讀權(quán)限的user-token
就可以拿到API-TOKEN榛鼎,同時(shí)沒有暴露給外部。
使用AppRoles的驗(yàn)證方式
AWS的EC2 instance上可以綁定instanceProfile
, instanceProfile對應(yīng)的是IAM的role鳖孤,這個(gè)role可以設(shè)置對AWS資源的訪問權(quán)限者娱,比如對S3某個(gè)bucket的寫權(quán)限,或者對Dynamodb的寫權(quán)限等苏揣。Vault提供的AppRoles的功能比instanceProfile
要差很多黄鳍,不過確實(shí)可以將機(jī)器和一定權(quán)限的Role綁定起來,控制訪問的范圍平匈。
允許使用approle的驗(yàn)證方式同時(shí)創(chuàng)建一個(gè)給CI slave使用的role:
export VAULT_TOKEN="e4347e60-0e72-fa8f-05e8-94c7388bb12c"
curl -X POST -H "X-Vault-Token:$VAULT_TOKEN" -d '{"type":"approle"}' http://127.0.0.1:8200/v1/sys/auth/approle
curl -X POST -H "X-Vault-Token:$VAULT_TOKEN" -d '{"policies":"user-policy"}' http://127.0.0.1:8200/v1/auth/approle/role/deploy-role #這里請求的參數(shù)可以指定請求來源的CIDR block '{"policies":"user-policy", "bound_cidr_list":"172.20.32.0/28"}'框沟,只有在這個(gè)ip range里面的服務(wù)器才能使用這個(gè)role從vault拿到指定數(shù)據(jù)
下面的API請求可以拿到role-id
藏古,以及根據(jù)role-id
生成secret-id
,利用它們可以登錄獲得從vault讀取數(shù)據(jù)的權(quán)限:
curl -X GET -H "X-Vault-Token:$VAULT_TOKEN" http://127.0.0.1:8200/v1/auth/approle/role/deploy-role/role-id | jq .
{
"request_id": "553d9fa9-50a2-274e-77f9-675c200d8cd1",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"role_id": "9d830303-2e06-b432-9023-677eb886041c"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
curl -X POST -H "X-Vault-Token:$VAULT_TOKEN" http://127.0.0.1:8200/v1/auth/approle/role/deploy-role/secret-id | jq .
{
"request_id": "ab5edb82-d1a9-b751-47c6-e2e0fe7ca333",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"secret_id": "6788593e-2c9b-0bae-0b0d-4b2c8d84e81d",
"secret_id_accessor": "f88c33fd-431d-35aa-6295-6d7ecd6b34d1"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
使用secret_id
和 role_id
聯(lián)合登錄忍燥,拿到新的token:
curl -X POST -d '{"role_id":"9d830303-2e06-b432-9023-677eb886041c","secret_id":"6788593e-2c9b-0bae-0b0d-4b2c8d84e81d"}' http://127.0.0.1:8200/v1/auth/approle/login | jq '{client_token: .auth.client_token}'
{
"client_token": "e0fe3cfb-1323-4ec6-9490-d6ac06dc3c69"
}
然后利用client_token
去讀取前面寫入到vault中的search api token:
~> curl -X GET -H "X-Vault-Token:e0fe3cfb-1323-4ec6-9490-d6ac06dc3c69" http://127.0.0.1:8200/v1/secret/api/search | jq '{"api-token": .data.token}'
{
"api-token": "c192d0211cb81fbfeee53fb16e2a7465"
}
秘鑰管理
AWS的EC2 instance的服務(wù)器校翔,在啟動(dòng)時(shí),可以綁定一對ssh keypair灾前,以方便用戶使用缺省的ec2-user
ssh到服務(wù)器上。從最佳實(shí)踐的角度來說孟辑,應(yīng)該把服務(wù)器當(dāng)做immutable的設(shè)施哎甲,不允許ssh。但是現(xiàn)實(shí)比較嘲諷饲嗽,這樣的需求仍然存在炭玫,那么我們可以換種方式,盡量做好ssh key的管理貌虾。
比如吞加,對于每個(gè)新啟動(dòng)的服務(wù)器,動(dòng)態(tài)的生成一對ssh keypair尽狠,只應(yīng)用在這臺(tái)服務(wù)器上衔憨,服務(wù)器銷毀后,吊銷key pair袄膏。
Vault提供了ssh keypair的管理功能践图,利用這個(gè)功能我們可以對key的生命周期的管理。它支持兩種方式:
- One-Time-Password (OTP) Type
- Dynamic Key Type
個(gè)人傾向于使用動(dòng)態(tài)key沉馆,但是官方的文檔推薦OTP類型码党,原因是無法對動(dòng)態(tài)的key的使用進(jìn)行audit,還有一個(gè)就是生成動(dòng)態(tài)的key會(huì)消耗資源導(dǎo)致vault服務(wù)的停頓(好失望:()斥黑。Anyway揖盘,不管它。
要讓vault發(fā)放keypair, 需要先注冊一個(gè)private key锌奴,這個(gè)key必須有服務(wù)器的管理權(quán)限.之后兽狭,你需要?jiǎng)?chuàng)建一個(gè)role,包括一些限定條件缨叫,如admin用戶的名字椭符,缺省用戶,目標(biāo)服務(wù)器的IP地址應(yīng)該匹配的CIDR地址耻姥,具體過程如下:
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=e4347e60-0e72-fa8f-05e8-94c7388bb12c
vault write ssh/keys/deploy-role key=@shared_deploy_key.pem
vault write ssh/roles/deploy-role \
key_type=dynamic \
key=shared_deploy_key \
admin_user=root \
default_user=ec2-user \
cidr_list=172.23.0.0/16 #目標(biāo)服務(wù)器的IP地址需要在CIDR的范圍內(nèi)
vault write ssh/creds/deploy-role ip=172.23.0.6
整個(gè)的過程大概是這樣销钝,vault利用注冊的private key登陸到目標(biāo)服務(wù)器上,然后將新生成的key pair中的public key寫入到目標(biāo)服務(wù)器的authorized_keys
文件中琐簇。在你想登陸到服務(wù)器上的時(shí)候蒸健,用對應(yīng)的client token驗(yàn)證座享,獲取private key,ssh登陸似忧。key有過期時(shí)間渣叛,過期之后就被revoke了。
PKI 管理
我覺得全站https困難之一就在于PKI的管理盯捌,今年出現(xiàn)過幾次certficate過期導(dǎo)致的產(chǎn)品環(huán)境的問題淳衙,還好及時(shí)的切換到了AWS Certificate Manager,免費(fèi)而且到期自動(dòng)續(xù)租饺著,基本上不用擔(dān)心管理的問題了箫攀。而我們原有的內(nèi)部PKI管理要稍微麻煩些,需要用自己業(yè)務(wù)線的LOB Intermediate CA去簽發(fā)新的certs幼衰,運(yùn)行一些自動(dòng)化的腳本靴跛,還得從RatticDB里面找到對應(yīng)的private key。
但是如果基礎(chǔ)設(shè)施不是基于AWS渡嚣,好像就沒有很合適的工具了梢睛,只能自己維護(hù)PKI,Vault也提供了PKI的管理识椰,可以在這方面提供幫助绝葡。考慮環(huán)境的一致性裤唠,開發(fā)挤牛、測試以及staging也需要certificate,我覺得這個(gè)功能是有意義的种蘸。
vault mount -path=example -description="example Root CA" -max-lease-ttl=87600h pki
vault write example/root/generate/internal common_name=example.com ttl=87600h key_bits=4096
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIUCugj4117yjDXigWcflp7nA6lAp0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYxMjIzMDU0MjQ3WhcNMjYx
MjIxMDU0MzE3WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
....
-----END CERTIFICATE-----
serial_number 0a:e8:23:e3:5d:7b:ca:30:d7:8a:05:9c:7e:5a:7b:9c:0e:a5:02:9d
Vault會(huì)安全的保存Root CA 的private key墓赴。 可以通過http請求拿到ca的pem文件。
curl -s http://127.0.0.1:8200/v1/example/ca/pem | openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
0a:e8:23:e3:5d:7b:ca:30:d7:8a:05:9c:7e:5a:7b:9c:0e:a5:02:9d
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=example.com
Validity
Not Before: Dec 23 05:42:47 2016 GMT
Not After : Dec 21 05:43:17 2026 GMT
Subject: CN=example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
....
需要給Vault配置訪問CA和CRL(certificate revocation list)的地址航瞭。
vault write example/config/urls issuing_certificates="http://127.0.0.1:8200/v1/example"
Success! Data written to: example/config/urls
創(chuàng)建intermediate CA.
vault mount -path=example_lob -description="Example LOB Intermediate CA" -max-lease-ttl=26280h pki
Successfully mounted 'pki' at 'example_lob'!
vault write example_lob/intermediate/generate/internal common_name="Example LOB Intermediate CA" ttl=26280h key_bits=4096 exclude_cn_from_sans=true
Key Value
--- -----
csr -----BEGIN CERTIFICATE REQUEST-----
...
-----END CERTIFICATE REQUEST-----
把這個(gè)csr內(nèi)容存在example_lob.csr
文件中诫硕,請求root ca簽發(fā)這個(gè)intermediate ca。
vault write example/root/sign-intermediate csr=@example_lob.csr common_name="Example LOB Intermediate CA" ttl=8760h
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIElTCCA32gAwIBAgIUH1UgMxdv8fGTMAfTFc86JHVnfB4wDQYJKoZIhvcNAQEL
....
-----END CERTIFICATE-----
expiration 1514009491
issuing_ca -----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIUCugj4117yjDXigWcflp7nA6lAp0wDQYJKoZIhvcNAQEL
....
-----END CERTIFICATE-----
serial_number 1f:55:20:33:17:6f:f1:f1:93:30:07:d3:15:cf:3a:24:75:67:7c:1e
得到Root CA的簽發(fā)過的intermediate CA certs后刊侯,保存為文件章办,導(dǎo)入两入。最后就是要設(shè)置CA/CRL便瑟,和上面相同裳瘪。
vault write example_lob/intermediate/set-signed certificate=@example_lob.crt
Success! Data written to: example_lob/intermediate/set-signed
curl -s http://localhost:8200/v1/example_lob/ca/pem | openssl x509 -text | head -15
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
23:0e:28:69:08:d1:5e:c9:10:8d:61:fe:46:4b:c6:09:ef:44:73:db
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=example.com
Validity
Not Before: Dec 23 06:23:55 2016 GMT
Not After : Dec 23 06:24:25 2017 GMT
Subject: CN=Example LOB Intermediate CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (4096 bit)
Modulus (4096 bit):
vault write example_lob/config/urls issuing_certificates="http://127.0.0.1:8200/v1/example_lob/ca"
Success! Data written to: example_lob/config/urls
在開始給server簽發(fā)certs前阱佛,需要?jiǎng)?chuàng)建role,之后就可以簽發(fā)了目木。
vault write example_lob/roles/web_server key_bits=2048 max_ttl=8760h allow_any_name=true
Success! Data written to: example_lob/roles/web_server
vault write example_lob/issue/web_server common_name="auth.lob.example.com" ip_sans="172.23.0.2" ttl=720h format=pem
Key Value
--- -----
lease_id example_lob/issue/web_server/a5c7ba76-34b5-b2a8-5262-3cd3fbc1630e
lease_duration 719h59m59s
lease_renewable false
ca_chain [-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----]
certificate -----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
issuing_ca -----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
....
-----END RSA PRIVATE KEY-----
private_key_type rsa
serial_number 5a:11:20:aa:35:ad:3a:dd:22:a8:8c:4b:26:04:a8:e5:ce:fb:96:74
拿到private key和crt就可以放在服務(wù)器上測試了芭挽,感覺用這個(gè)grunt-connect-proxy測試起來可能會(huì)快點(diǎn)暗挑。
其他
Vault還有一個(gè)我覺得很好的特性是可以將LDAP作為auth的backend辜羊,感覺維護(hù)的壓力又小了很多:) 剩下的特性大家可以自己嘗試踏兜,我們也正在考慮把替換RatticDB保存一些private key之類的credential词顾,下次的security meetup上面我可以展示下spike的成果……。