1.概述
在本文中,我們將探索Hashicorp的Vault —— 一種用於在現代應用程序體系結構中安全地管理機密信息的流行工具。
我們將討論的主要議題包括:
- Vault試圖解決什麼問題
- Vault的架構和主要概念
- 設置一個簡單的測試環境
- 使用命令行工具與Vault交互
2.機密信息問題
在深入瞭解Vault之前,讓我們試着瞭解它試圖解決的問題:機密信息管理。
大多數應用程序需要訪問機密數據才能正常工作。例如,電子商務應用程序可以在某處配置用戶名/密碼以便連接到其數據庫。它還可能需要API密鑰才能與其他服務提供商集成,例如支付網關,物流和其他業務合作伙伴。
數據庫憑證和API密鑰就是我們需要以安全的方式存儲和提供給我們的應用程序的機密信息。
一個簡單的解決方案是將這些信息存儲在配置文件中,並在啓動時讀取它們。但是這種方法的問題顯而易見:有權訪問此文件的人共享我們的應用程序具有的數據庫權限 - 通常可以完全訪問所有存儲的數據。
我們可以嘗試通過加密這些文件來使事情變得更加困難。但是,這種方法在整體安全性方面不會增加太多。主要是因爲我們的應用程序必須能夠訪問主密鑰。當以這種方式使用時,加密僅是一種“錯誤”的安全感。
現代應用程序和雲環境往往會增加一些額外的複雜性:分佈式服務,多個數據庫,消息傳遞系統等等,所有敏感信息都在各處傳播,從而增加了安全漏洞的風險。
所以,我們能做些什麼?來用下Vault吧!
3.什麼是Vault?
Hashicorp Vault解決了管理敏感信息的問題 —— 在Vault的用語中使用“secret”。在這種情況下,“管理”意味着Vault控制敏感信息的所有方面:它的生成,存儲,使用以及最後它的撤銷。
Hashicorp提供兩種版本的Vault。本文中使用的開源版本可以免費使用,即使在商業環境中也是如此。同時還提供付費版本,其中包括不同SLA的技術支持和其他功能,例如HSM(硬件安全模塊)支持。
3.1 架構和主要特點
Vault的架構非常簡單。其主要組成部分是:
- 持久性後端 —— 存儲所有機密
- 一種API服務器 —— 用於處理客戶端請求並對機密執行操作
- 許多secret引擎 —— 支持不同的機密類型
通過將所有機密處理委派給Vault,我們可以緩解一些安全問題:
我們的應用程序不再需要存儲它們 ,只需在需要時詢問Vault並在使用後將其丟棄
我們可以短暫的使用機密數據,從而限制攻擊者盜取祕密的“機會之窗”
Vault會在將所有數據寫入存儲之前使用加密密鑰對所有數據進行加密。此加密密鑰由另一個密鑰加密 —— 主密鑰,主密鑰僅在啓動時使用。
Vault實現的一個關鍵點是它不會將主密鑰存儲在服務器中。 這意味着即使Vault也無法在啓動後訪問其保存的數據。 此時,Vault實例被稱爲處於“密封”狀態。
稍後,我們將完成生成主密鑰和解封Vault實例所需的步驟。
一旦啓封,Vault就可以接受API請求了。當然,這些請求需要身份驗證,這使我們控制Vault如何對客戶端進行身份驗證並決定他們可以做什麼或不做什麼。
3.2 認證
要訪問Vault中的機密,客戶端需要使用一種受支持的方法對自身進行身份驗證。最簡單的方法使用Tokens,它只是使用特殊HTTP頭在每個API請求上發送的字符串。
最初安裝時,Vault會自動生成“根令牌”。此令牌與Linux系統中的超級用戶等效,因此應將其使用限制在最低限度。作爲最佳實踐,我們應該使用此根令牌來創建具有較少權限的其他令牌,然後撤消它。但這不是問題,因爲我們以後可以使用unseal鍵生成另一個根令牌。
Vault還支持其他身份驗證機制,如LDAP,JWT,TLS證書等。所有這些機制都建立在基本令牌機制之上:一旦Vault驗證了我們的客戶端,它將提供一個令牌,然後我們可以使用它來訪問其他API。
令牌有一些與之相關的屬性。主要屬性是:
- 一組關聯的策略
- 有效期
- 是否強制更新
- 最大使用次數
除非另有說明,否則由Vault創建的令牌將形成父子關係。子令牌最多可以與父令牌具有相同級別的權限。
通常的情況是,我們可以創建具有限制性策略的子令牌。關於此關係的另一個關鍵點:當我們使令牌無效時,所有子令牌及其後代也會失效。
3.3 策略
策略確切地定義了客戶端可以訪問哪些祕密以及它們可以使用它們執行哪些操作。讓我們看看一個簡單的策略是如何定義的:
path "secret/accounting"{
capabilities = [ "read"]
}
在這裏,我們使用HCL(Hashicorp的配置語言)語法來定義我們的策略。Vault還支持JSON格式,但我們將在示例中堅持使用HCL,因爲它更易於閱讀。
Vault中的策略是“默認拒絕”。附加到此示例策略的令牌將訪問secret/accounting下存儲的祕密,而不是其他內容。在創建時,令牌可以附加到多個策略。這非常有用,因爲它允許我們創建和測試較小的策略,然後根據需要應用它們。
策略的另一個重要方面是他們使用懶惰評估。這意味着我們可以更新給定的策略,所有令牌都會立即受到影響。
到目前爲止描述的策略也稱爲訪問控制列表策略或ACL策略。Vault還支持兩種其他策略類型:EGP和RGP策略。這些僅在付費版本中可用,並使用Sentinel支持擴展基本策略語法。
擴展基本策略允許我們在策略中考慮其他屬性,例如一天中的時間,多個身份驗證因素,客戶端網絡來源等。例如,我們可以定義一個允許僅在工作時間訪問某指定機密的策略。
我們可以在Vault的文檔中找到有關策略語法的更多詳細信息 。
4.Secret類型
Vault支持一系列不同的祕密類型,可以解決不同的用例:
- Key-Value: 簡單的靜態鍵值對
- 動態生成的憑據:由Vault根據客戶端請求生成
- 加密密鑰:用於使用客戶端數據執行加密功能
每種祕密類型由以下屬性定義:
- 一個掛載點,定義了它的REST API前綴
- 通過相應的API公開的一組操作
- 一組配置參數
可以通過路徑訪問給定的祕密實例 ,非常類似於文件系統中的目錄樹。此路徑的第一個組件對應於此類型的所有機密所在的安裝點。
例如,字符串 secret / my-application 對應於我們可以在其中找到my-application的鍵值對的路徑 。
4.1 Key-Value Secrets
顧名思義,鍵值Secrets是指在給定路徑下可用的簡單鍵值對。例如,我們可以在path / secret / my-application下 存儲foo = bar對 。
稍後,我們使用相同的路徑來檢索相同的一對或多對 - 多對可以存儲在同一路徑下。
Vault支持三種Key-Value機密:
- 非版本化鍵值對,其中更新替換現有值
- 版本化鍵值對,可保持可配置數量的舊版本
- Cubbyhole,一種特殊類型的非版本化密鑰對,其值的範圍限定爲給定的訪問令牌(稍後將詳細介紹)。
密鑰值祕密本質上是靜態的,因此沒有到期的概念與它們相關聯。這種祕密的主要使用場景是存儲憑證以訪問外部系統,例如API密鑰。
在這種情況下,憑據更新是半手動過程,通常需要有人獲取新憑據並使用Vault的命令行或其UI來輸入新值。
4.2 動態生成的Secrets
當應用程序請求時,Vault會動態生成動態機密。Vault支持多種類型的動態機密,包括以下幾種:
- 數據庫憑證
- SSH密鑰對
- X.509證書
- AWS憑證
- Google Cloud服務帳戶
- Active Directory帳戶
所有這些都遵循相同的使用模式。首先,我們使用連接到關聯服務所需的詳細信息配置secret引擎。然後,我們定義一個或多個角色, 描述實際的secret創建。
我們以數據庫secret引擎爲例。首先,我們必須使用所有用戶數據庫連接詳細信息配置Vault,包括來自具有管理員權限的預先存在的用戶的憑據以創建新用戶。
然後,我們創建一個或多個角色(Vault角色,而不是數據庫角色),其中包含用於創建新用戶的實際SQL語句。這些通常不僅包括用戶創建語句,還包括訪問模式對象(表,視圖等)以及所有必需的 grant語句。
當客戶端訪問相應的API時,Vault將使用提供的語句在數據庫中創建新的臨時用戶並返回其憑據。然後,客戶端可以使用這些憑據在請求角色的生存時間屬性定義的時間段內訪問數據庫。
憑證到達其到期時間後,Vault將自動撤消與此用戶關聯的任何權限。客戶端還可以請求Vault續訂這些憑據。只有在特定數據庫驅動程序支持且相關策略允許的情況下,纔會進行續訂過程。
4.3 加密密鑰
這個類型的祕密引擎處理加密,解密,簽名等加密功能。所有這些操作都使用Vault內部生成和存儲的加密密鑰。除非明確告知,否則Vault將永遠不會公開給定的加密密鑰。
關聯的API允許客戶端發送Vault純文本數據並接收其加密版本。相反的情況也是可能的:我們可以發送加密數據並取回原始文本。
目前,只有一種這種類型的引擎:Transit引擎。此引擎支持流行的密鑰類型,如RSA和ECDSA,還支持 Convergent Encryption。使用此模式時,給定的明文值始終會產生相同的密文結果,這種屬性在某些應用程序中非常有用。
例如,我們可以使用此模式加密事務日誌表中的信用卡號。使用收斂加密,每次插入新事務時,加密的信用卡值都是相同的,因此允許使用常規SQL查詢進行報告,搜索等。
5.Vault設置
在本節中,我們將創建一個本地測試環境,以便測試Vault的功能。
Vault的部署很簡單:只需下載與我們的操作系統對應的軟件包,並將其可執行文件(Windows上的vault 或vault.exe )提取到PATH上的某個目錄。此可執行文件包含服務器,也是標準客戶端。
Vault支持一種development 模式,適用於某些快速測試並習慣其命令行工具,但對於實際使用情況來說太簡單了:重啓時所有數據都丟失,API訪問使用普通HTTP。
相反,我們將使用基於文件的持久存儲和設置HTTPS,以便我們可以探索一些可能導致問題的真實配置細節。
5.1 啓動Vault Server
Vault使用HCL或JSON格式的配置文件。以下文件定義了使用文件存儲和自簽名證書啓動服務器所需的所有配置:
// Enable UI
ui = true
// Filesystem storage
storage "file" {
path = "./vault-data"
}
// TCP Listener using a self-signed certificate
listener "tcp" {
address = "127.0.0.1:8200"
tls_cert_file = "localhost.cert"
tls_key_file = "localhost.key"
}
現在,讓我們運行Vault。打開命令shell,轉到包含我們的配置文件的目錄並運行以下命令:
$ vault server -config ./vault-test.hcl
Vault將啓動並顯示一些初始化消息。它們將包括其版本,一些配置詳細信息以及API可用的地址。就是這樣我們的Vault服務器啓動並運行。
5.2 Vault初始化
我們的Vault服務器現在正在運行,但由於這是它的第一次運行,我們需要初始化它。
讓我們打開一個新的shell並執行以下命令來實現這個目的:
$ export VAULT_ADDR=https://localhost:8200
$ export VAULT_CACERT=localhost.cert
$ vault operator init
這裏我們定義了一些環境變量,因此我們不必每次都將它們作爲參數傳遞給Vault:
- VAULT_ADDR:我們的API服務器將爲其提供請求的基URI
- VAULT_CACERT:服務器證書公鑰的路徑
在我們的例子中,我們使用VAULT_CACERT, 因此我們可以使用HTTPS訪問Vault的API。我們需要這個,因爲我們使用的是自簽名證書。對於我們通常可以訪問CA簽名證書的製作環境而言,這不是必需的。
發出上述命令後,我們應該看到如下消息:
Unseal Key 1: kHcMDI6lwIfX1xdcThrDF5jR00gDOc1a7t6kr+fCXWcN
Unseal Key 2: 6xsSLornxUqfMUSoq7XPIb9PLIFnzPPMR+UMTzWP0m0o
Unseal Key 3: pgrhB6wtrnINh3SY4mS2cWjEzmuiMRWmg+SIp50/4DQs
Unseal Key 4: BpR1Lzuogn9AJYJADxXuCit5yxGYtM2qF0BxHn1g/Zw8
Unseal Key 5: Vj7W6HYcCZvtkCnjNdR+4fuw+aCmlfQ+IZMTc70cyzIh
Initial Root Token: 2HgD0nKyaDjI5TICz9pObW2T
前5行是我們稍後用於解開Vault存儲的主密鑰。 請注意,Vault僅在初始化期間顯示解封密鑰 - 以後永遠不會出現。記下並安全地存儲它們,否則在服務器重啓時我們將無法訪問我們的機密信息!
另外,請注意根令牌,因爲我們稍後會需要它。與unseal鍵不同,root令牌可以在以後輕鬆生成,因此一旦完成所有配置任務就可以安全地銷燬它。由於我們稍後將發出需要身份驗證令牌的命令,因此我們現在將根令牌保存在環境變量中:
$ export VAULT_TOKEN= 2HgD0nKyaDjI5TICz9pObW2T
現在讓我們看看我們的服務器狀態,我們已經使用以下命令對其進行了初始化:
$ vault status
Key Value
--- -----
Seal Type shamir
Sealed true
Total Shares 5
Threshold 3
Unseal Progress 0/3
Unseal Nonce n/a
Version 0.11.3
HA Enabled false
我們可以看到Vault仍然是密封的。Unseal Progress :“0/3”意味着Vault需要三個,但到目前爲止還沒有開啓。
5.3 Vault Unseal
我們現在開啓Vault,以便我們可以開始使用其祕密服務。爲了完成開封過程,我們需要提供五個關鍵股中的任意三個:
$ vault operator unseal kHcMDI6lwIfX1xdcThrDF5jR00gDOc1a7t6kr+fCXWcN
$ vault operator unseal 6xsSLornxUqfMUSoq7XPIb9PLIFnzPPMR+UMTzWP0m0o
$ vault operator unseal pgrhB6wtrnINh3SY4mS2cWjEzmuiMRWmg+SIp50/4DQs
發出每個命令後,Vault將打印啓封進度,包括它需要多少個共享。發送最後一個密鑰共享後,我們會看到如下消息:
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 0.11.3
Cluster Name vault-cluster-6c90f68f
Cluster ID a1259a6b-aa24-b02a-1d36-b64907ea3570
HA Enabled false
在這種情況下,“密封”屬性爲“假”,這意味着Vault已準備好接受命令。
6.測試Vault
在本節中,我們將使用其支持的兩種Secret類型測試我們的Vault設置:鍵值對和數據庫。我們還將展示如何創建附加了特定策略的新令牌。
6.1 使用鍵/值Secret
首先,讓我們存儲祕密的鍵值對並將其讀回。
假設用於初始化Vault的命令shell仍處於打開狀態,我們使用以下命令將這些鍵值對存儲在secret / fakebank路徑下:
$ vault kv put secret/fakebank api_key=abc1234 api_secret=1a2b3c4d
我們現在可以使用以下命令隨時讀取這些鍵值對:
$ vault kv get secret/fakebank
======= Data =======
Key Value
--- -----
api_key abc1234
api_secret 1a2b3c4d
這個簡單的測試向我們展示了Vault正在按預期工作。我們現在可以測試一些額外的功能。
6.2 創建新令牌
到目前爲止,我們已經使用了根令牌來驗證我們的請求。由於根令牌 的方式太強大了,它被認爲是使用較少的特權和更短的時間生存令牌的最佳實踐。
讓我們創建一個新的令牌,我們可以像根令牌一樣使用它,但在一分鐘後過期:
$ vault token create -ttl 1m
Key Value
--- -----
token Luc1pGxZxKkNEwpCLD3Ea4VX
token_accessor 1Zzp7g1FJYW5nvqACZFQtRRx
token_duration 1m
token_renewable true
token_policies ["root"]
identity_policies []
policies ["root"]
讓我們測試一下這個令牌,用它來讀取我們之前創建的鍵/值對:
$ export VAULT_TOKEN=Luc1pGxZxKkNEwpCLD3Ea4VX
$ vault kv get secret/fakebank
======= Data =======
Key Value
--- -----
api_key abc1234
api_secret 1a2b3c4d
如果我們等待一分鐘並嘗試重新發出此命令,我們會收到一條錯誤消息:
$ vault kv get secret/fakebank
Error making API request.
URL: GET https://localhost:8200/v1/sys/internal/ui/mounts/secret/fakebank
Code: 403. Errors:
* permission denied
該消息表明我們的令牌不再有效,這正是我們所期望的。
6.3 測試策略
我們在上一節中創建的示例令牌雖是短暫存在,但仍然非常強大。現在讓我們使用策略來創建更多受限制的令牌。
例如,讓我們定義一個策略,該策略只允許對我們之前使用的secret / fakebank路徑進行讀訪問:
$ cat> sample-policy.hcl <
path "secret/fakebank"{
capabilities = ["read"]
}
EOF
$ export VAULT_TOKEN=2HgD0nKyaDjI5TICz9pObW2T
$ vault policy write fakebank-ro ./sample-policy.hcl
Success! Uploaded policy: fakebank-ro
現在,我們使用以下命令使用此策略創建令牌:
$ vault token create -policy=fakebank-ro
Key Value
--- -----
token <token value>
token_accessor <token accessor value>
token_duration 768h
token_renewable true
token_policies ["default""fakebank-ro"]
identity_policies []
policies ["default""fakebank-ro"]
正如我們之前所做的那樣,讓我們使用此標記讀取我們的祕密值:
$ export VAULT_TOKEN=<token value>
$ vault kv get secret/fakebank
======= Data =======
Key Value
--- -----
api_key abc1234
api_secret 1a2b3c4d
到現在爲止還挺好。我們可以按預期讀取數據。讓我們看看當我們嘗試更新這個祕密時會發生什麼:
$ vault kv put secret/fakebank api_key=foo api_secret=bar
Error writing data to secret/fakebank: Error making API request.
URL: PUT https://127.0.0.1:8200/v1/secret/fakebank
Code: 403. Errors:
* permission denied
由於我們的策略未授予允許寫入,因此Vault將返回403 - 拒絕訪問狀態代碼。
6.4 使用動態數據庫憑據
作爲本文的最後一個示例,讓我們使用Vault的數據庫祕密引擎來創建動態憑據。我們假設我們在本地有一個MySQL服務器,我們可以使用“root”權限訪問它。我們還將使用一個非常簡單的模式,該模式由一個表 - 帳戶組成。
用於創建此架構的SQL腳本和特權用戶可在此處獲得。
--
-- Sample schema for testing vault database secrets
--
create schema fakebank;
use fakebank;
create table account(
id decimal(16,0),
name varchar(30),
branch_id decimal(16,0),
customer_id decimal(16,0),
primary key (id));
--
-- MySQL user that will be used by Vault to create other users on demand
--
create user 'fakebank-admin'@'%' identified by 'Sup&rSecre7!';
grant all privileges on fakebank.* to 'fakebank-admin'@'%' with grant option;
grant create user on *.* to 'fakebank-admin' with grant option;
flush privileges;
現在,讓我們配置Vault以使用此數據庫。默認情況下未啓用數據庫密鑰引擎,因此我們必須先解決此問題,然後才能繼續:
$ vault secrets enable database
Success! Enabled the database secrets engine at: database/
我們現在創建一個數據庫配置資源:
$ vault write database/config/mysql-fakebank plugin_name=mysql-legacy-database-plugin connection_url="{{username}}:{{password}}@tcp(127.0.0.1:3306)/fakebank" allowed_roles="*" username="fakebank-admin" password="Sup&rSecre7!"
路徑前綴database / config是必須存儲所有數據庫配置的地方。我們選擇名稱 mysql-fakebank, 以便我們可以輕鬆找出此配置所指的數據庫。至於配置鍵:
- plugin_name:定義將使用哪個數據庫插件。Vault的文檔中描述了可用的插件名稱
- connection_url:這是插件在連接數據庫時使用的模板。請注意{{username}}和{{password}}模板佔位符。連接到數據庫時,Vault將按實際值替換這些佔位符
- allowed_roles:定義哪些Vault角色(上面討論過)可以使用此配置。在我們的例子中,我們使用“*”,因此它可用於所有角色
- 用戶名和密碼:這是Vault用於執行數據庫操作的帳戶,例如創建新用戶和撤消其權限
Vault數據庫角色設置
最終配置任務是創建包含創建用戶所需的SQL命令的Vault數據庫角色資源。根據我們的安全要求,我們可以根據需要創建任意數量的角色。
在這裏,我們創建一個角色,授予對fakebank模式的所有表的只讀訪問權限:
$ vault write database/roles/fakebank-accounts-ro db_name=mysql-fakebank creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON fakebank.* TO '{{name}}'@'%';"
數據庫引擎將路徑前綴數據庫/角色 定義爲存儲角色的位置。 fakebank-accounts-ro是我們稍後在創建動態憑據時使用的角色名稱。我們還提供以下屬性:
- db_name:現有數據庫配置的名稱。對應於我們在創建配置資源時使用的路徑的最後部分
- creation_statements: Vault將用於創建新用戶的SQL語句模板列表
創建動態憑據
一旦我們準備好數據庫角色及其相應的配置,我們就會使用以下命令生成新的動態憑據:
$ vault read database/creds/fakebank-accounts-ro
Key Value
--- -----
lease_id database/creds/fakebank-accounts-ro/t4RQeXdyAVq75jNmbaQfPM6n
lease_duration 768h
lease_renewable true
password A1a-24NNCeaazWCp2EE7
username v-fake-60fSchsFo
該數據庫/ creds前綴用於生成可用角色憑據。由於我們使用了 fakebank-accounts-ro 角色,因此返回的用戶名/密碼將僅限於選擇操作。有效期爲1個小時。
我們可以通過使用提供的憑據連接到數據庫然後執行一些SQL命令來驗證這一點:
$ mysql -h 127.0.0.1 -u v-fake-60fSchsFo -p fakebank
Enter password:
mysql> select * from account;
Empty set (0.00 sec)
mysql> delete from account;
ERROR 1142 (42000): DELETE command denied to user 'v-fake-60fSchsFo'@'localhost' for table 'account'
我們可以看到第一個select成功完成,但是我們無法執行delete語句。最後,如果我們等待一個小時並嘗試使用相同的憑據進行連接,我們將無法再連接到數據庫。Vault已自動撤消此用戶的所有權限
7.結論
在本文中,我們探討了Hashicorp Vault的基礎知識,包括它試圖解決的問題的一些背景,它的架構和基本用法。
在此過程中,我們創建了一個簡單但功能齊全的測試環境,我們將在後續文章中使用它。
下一篇文章將介紹Vault的一個非常具體的用例:在Spring Boot應用程序的上下文中使用它。敬請關注!