開發者不應該把機敏性資料,用明碼的方式存放在電腦,HashiCorp Vault 是一套基於身份的私鑰和加密管理系統,Hashicorp Vault 是一個安全管理工具,專門用於管理機敏性資料,例如:密碼、API 密鑰和其他機密訊息,下圖出自 Introduction | Vault | HashiCorp Developer

原本我本來想要自幹一套 Secret Manager / Config Manage,因為有 Vault 我省去了自幹的時間,這裡,我將嘗試學習使用 C# VaultSharp 訪問 Hashicorp Vault
開發環境
- Windows 11 Home
- Powershell
- vault 1.17.6
- Rider 2024.2
安裝
下列方法擇一
scoop install vault
choco install vault
本機環境啟動開發用的 Vault Server
vault server -dev
Vault Server 已經被建立起來,Console 介面可以看到 Unseal Key、Root Token,這兩個是很重要的東西,不應該洩漏或是用明碼的方式儲存在電腦裡面
...
2024-10-05T21:45:22.824+0800 [INFO] core: successful mount: namespace="" path=secret/ type=kv version="v0.19.0+builtin"
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variables:
PowerShell:
$env:VAULT_ADDR="http://127.0.0.1:8200"
cmd.exe:
set VAULT_ADDR=http://127.0.0.1:8200
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: wjSRAfJc5udiO8NGStdvDTcN1hpxNFqQUWylq9otabc=
Root Token: hvs.WqTOKcy2PUR6TNGsJoDssxN4
Development mode should NOT be used in production installations!
由於使用 -dev,他會把 root token 放在本機電腦,可以用指令查看內容
Get-Content $env:USERPROFILE\.vault-token
設定服務位置的環境變數
預設服務位置為 'http://127.0.0.1:8200' ,因為還沒有憑證,所以先額外設定 http,環境變數為 VAULT_ADDR
$Env:VAULT_ADDR = "http://127.0.0.1:8200"
在瀏覽器訪問 http://127.0.0.1:8200
- Method=Token
- Token=hvs.WqTOKcy2PUR6TNGsJoDssxN4

Vault CLI
或是尻尻看 health check
PS C:\Users\yao> curl http://127.0.0.1:8200/v1/sys/health | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 391 100 391 0 0 6470 0 --:--:-- --:--:-- --:--:-- 6516
{
"initialized": true,
"sealed": false,
"standby": false,
"performance_standby": false,
"replication_performance_mode": "disabled",
"replication_dr_mode": "disabled",
"server_time_utc": 1728137740,
"version": "1.17.6",
"enterprise": false,
"cluster_name": "vault-cluster-015c7103",
"cluster_id": "040bcba4-dba1-772b-c208-924850df5f56",
"echo_duration_ms": 0,
"clock_skew_ms": 0,
"replication_primary_canary_age_ms": 0
}
啟用一個 kv-v2 secret agent
PS C:\Users\yao> vault secrets enable --version=2 --path=job/dream-team kv
Success! Enabled the kv secrets engine at: job/dream-team/
停用一個 secret agent
PS C:\Users\yao> vault secrets disable job/dream-team
Success! Disabled the secrets engine (if it existed) at: job/dream-team/
確認有哪一些 secrets
PS C:\Users\yao> vault secrets list --detailed
Path Plugin Accessor Default TTL Max TTL Force No Cache Replication Seal Wrap External Entropy Access Options Description UUID Version Running Version Running SHA256 Deprecation Status
---- ------ -------- ----------- ------- -------------- ----------- --------- ----------------------- ------- ----------- ---- ------- --------------- -------------- ------------------
cubbyhole/ cubbyhole cubbyhole_750a37f1 n/a n/a false local false false map[] per-token private secret storage f61f51eb-8e7d-b4a1-0b6d-05f69cafeb9a n/a v1.17.6+builtin.vault n/a n/a
identity/ identity identity_c91022cc system system false replicated false false map[] identity store b4e37eb6-e0fd-f404-e43a-c773e3d395c0 n/a v1.17.6+builtin.vault n/a n/a
job/dream-team/ kv kv_af041c1c system system false replicated false false map[version:2] n/a 9fc90ba1-7ade-487f-5e7a-cf84e0c34c8c n/a v0.19.0+builtin n/a supported
kv/ kv kv_b99cedb5 system system false replicated false false map[] n/a be1a0a6f-db6d-57d8-b4e2-60397c384240 n/a v0.19.0+builtin n/a supported
secret/ kv kv_a5a2f1da system system false replicated false false map[version:2] key/value secret storage 4f147440-052d-a6ad-fde0-b082d8dfe3f2 n/a v0.19.0+builtin n/a supported
sys/ system system_0a34ed05 n/a n/a false replicated true false map[] system endpoints used for control, policy and debugging dd8d92a1-439d-1e06-19db-09b0dbf2b803 n/a v1.17.6+builtin.vault n/a n/a
寫入資料到 kv-v2
- -mount=job/dream-team:-mount 是一個標誌,指定 KV 引擎的掛載路徑。job/dream-team 是實際的掛載路徑,表示這個 KV 引擎位於 "job/dream-team" 路徑下。
- my-secret:這是要創建或更新的密鑰的名稱。
- UserName=admin Password=123456:存放的 Key/Value。在這個例子中,有兩個 Key/Value:
UserName = admin
Password = 123456
PS C:\Users\yao> vault kv put -mount=job/dream-team my-secret UserName=admin Password=123456
======== Secret Path ========
job/dream-team/data/my-secret
======= Metadata =======
Key Value
--- -----
created_time 2024-10-05T16:59:02.6649909Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
從 kv-v2 讀取資料
PS C:\Users\yao> vault kv get -mount=job/dream-team my-secret
======== Secret Path ========
job/dream-team/data/my-secret
======= Metadata =======
Key Value
--- -----
created_time 2024-10-05T17:11:05.9499243Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
Password 123456
UserName admin
C# VaultSharp
適用於 HashiCorp Vault 的最全面的跨平台 .NET 套件。
dotnet add package VaultSharp --version 1.17.5.1
接下來使用 C# 的操作 Vault,必須要傳入 Authentication,這一篇我們用的是 Token,它在一開始建立的 Vault Server 的 Console 對話視窗中

建立 Vault Client 的範例如下
// 初始化 Vault Client
var authMethod = new TokenAuthMethodInfo(this.VaultToken);
var vaultClientSettings = new VaultClientSettings(vaultServer, authMethod);
var vaultClient = new VaultClient(vaultClientSettings);
啟用一個 kv-v2 secret agent
vaultClient.V1.System.MountSecretBackendAsync():建立 Secret Agent,範例如下:
[TestMethod]
public async Task 建立KV_V2()
{
// 設定 Vault Server 和 Token
var vaultServer = "http://127.0.0.1:8200";
// 初始化 Vault Client
var authMethod = new TokenAuthMethodInfo(this.VaultToken);
var vaultClientSettings = new VaultClientSettings(vaultServer, authMethod);
var vaultClient = new VaultClient(vaultClientSettings);
// 啟用 KV V2 存儲引擎
var mountPath = "job/dream-team"; // 要啟用的路徑
await CreateKvV2Path(vaultClient, mountPath);
Console.WriteLine($"KV V2 secrets engine enabled at path: {mountPath}");
}
private static async Task CreateKvV2Path(IVaultClient vaultClient, string path)
{
try
{
await vaultClient.V1.System.MountSecretBackendAsync(new SecretsEngine
{
Path = path,
Type = new SecretsEngineType("kv-v2")
});
Console.WriteLine($"Successfully created kv-v2 path: {path}");
}
catch (VaultApiException ex)
{
if (ex.Message.Contains("path is already in use"))
{
Console.WriteLine($"The path '{path}' is already in use. It may already be mounted.");
}
else
{
Console.WriteLine($"Error creating kv-v2 path: {ex.Message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
}
停用一個 secret agent
vaultClient.V1.System.UnmountSecretBackendAsync():停用 Secrect Agent,範例如下:
[TestMethod]
public async Task 停用KV_V2()
{
// 設定 Vault Server 和 Token
var vaultServer = "http://127.0.0.1:8200";
// 初始化 Vault Client
var authMethod = new TokenAuthMethodInfo(this.VaultToken);
var vaultClientSettings = new VaultClientSettings(vaultServer, authMethod);
var vaultClient = new VaultClient(vaultClientSettings);
var secretPath = "my-secret";
var mountPath = "job/dream-team";
await vaultClient.V1.System.UnmountSecretBackendAsync(mountPath);
try
{
var secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(secretPath, mountPoint: mountPath);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
kv-v2 讀寫資料
- vaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync():寫入 Key/Value
- vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync():讀取 Key/Value
[TestMethod]
public async Task 讀寫KV_V2()
{
// 設定 Vault Server 和 Token
var vaultServer = "http://127.0.0.1:8200";
// 初始化 Vault Client
var authMethod = new TokenAuthMethodInfo(this.VaultToken);
var vaultClientSettings = new VaultClientSettings(vaultServer, authMethod);
var vaultClient = new VaultClient(vaultClientSettings);
// 寫入 Secrets (等同於 vault kv put)
var secretData = new Dictionary<string, object>
{
{ "username", "admin" },
{ "password", "123456" }
};
var secretPath = "my-secret";
var mountPath = "job/dream-team";
await vaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync(secretPath, secretData, mountPoint: mountPath);
Console.WriteLine("Secrets 已成功寫入!");
Console.WriteLine("讀取KV-V2:");
var secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(secretPath, mountPoint: mountPath);
Console.WriteLine($"username={secret.Data.Data["username"]}");
Console.WriteLine($"password={secret.Data.Data["password"]}");
}
心得
完成了一個很簡單的 CRUD 操作,在實作的過程中,想要嘗試著使用 SecureString 加強安全性,避免應用程式在做 Memmory Dump 的時候洩漏了敏感性資料,但我弄不出來,在操作的過程中一定會使用到 string,忍痛宣告放棄,有興趣的可以參考一下這裡
範例位置
參考
- Vault | HashiCorp Developer
- rajanadar/VaultSharp: A comprehensive cross-platform .NET Library for HashiCorp's Vault, a secret management tool (github.com)
- 從零開始的 30 天 Hashicorp Vault Workshop :: 2023 iThome 鐵人賽
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET