Dynamic Credentials 每一次產生出來的憑證,都是變動且有時效性的,這跟固定的 Key/Value,有很大的不一樣,用這樣的機制就可以保護真正的資訊,更有效的隱藏機敏性資料
開發環境
- Windows 11 Home
- Windows Terminal 1.20.11781.0
- Vault 1.17.6
- Rider 2024.2
續上篇,保護你的機敏性資料,通過 VaultSharp 訪問 Hashicorp Vault - 快速入門 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
直接進入重點,建立 Vault Server 開發環境
vault server -dev
設定環境變數
$Env:VAULT_ADDR = "http://127.0.0.1:8200"
建立資料庫執行個體
這裡我用 docker
docker run --name postgres.12 -e POSTGRES_USER=user -e POSTGRES_PASSWORD=password -e POSTGRES_DB=mydatabase -p 5432:5432 -d postgres:12-alpine
Vault CLI
啟用 Database Secrets
PS C:\Users\yao> vault secrets enable database
Success! Enabled the database secrets engine at: database/
設定 Valut 訪問資料庫
vault write database/config/my-postgresql-database `
plugin_name=postgresql-database-plugin `
allowed_roles="my-db-role" `
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" `
username="user" `
password="password"
PS C:\Users\yao> vault write database/config/my-postgresql-database `
>> plugin_name=postgresql-database-plugin `
>> allowed_roles="my-db-role" `
>> connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" `
>> username="user" `
>> password="password"
Success! Data written to: database/config/my-postgresql-database
設定 Vault 角色
當配置建立憑證時,建立一個用戶
$creationStatements = @"
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";
"@
vault write database/roles/my-db-role `
db_name=my-postgresql-database `
creation_statements=$creationStatements `
default_ttl="1h" `
max_ttl="24h"
PS C:\Users\yao> vault write database/roles/my-db-role `
>> db_name=my-postgresql-database `
>> creation_statements=$creationStatements `
>> default_ttl="1h" `
>> max_ttl="24h"
Success! Data written to: database/roles/my-db-role
讀取 Role
PS C:\Users\yao> vault read database/roles/my-db-role
Key Value
--- -----
creation_statements [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";]
credential_type password
db_name my-postgresql-database
default_ttl 1h
max_ttl 24h
renew_statements []
revocation_statements []
rollback_statements []
取得動態憑證 Dynamic Credentials
每執行就會產生一個憑證 Credential
PS C:\Users\yao> vault read database/creds/my-db-role
Key Value
--- -----
lease_id database/creds/my-db-role/WJ8TsZNAG0qot301urG7xbdI
lease_duration 1h
lease_renewable true
password 7FiylxeZDVsY-M21X6FY
username v-root-my-db-ro-iYq58EshiZiF0ljRp3h3-1728312012
欄位解釋
- lease_id: 此次請求的憑證 ID,用於後續操作(如續租或撤銷)。
- lease_duration: 憑證的有效時間(例如 1 小時)。
- username: Vault 動態生成的臨時使用者名稱。
- password: 與生成的使用者對應的密碼。
- lease_renewable: 如果為 true,表示這個憑證可以續租。
續約憑證
PS C:\Users\yao> vault lease renew database/creds/my-db-role/WJ8TsZNAG0qot301urG7xbdI
Key Value
--- -----
lease_id database/creds/my-db-role/WJ8TsZNAG0qot301urG7xbdI
lease_duration 1h
lease_renewable true
撤銷憑證
PS C:\Users\yao> vault lease revoke database/creds/my-db-role/WJ8TsZNAG0qot301urG7xbdI
All revocation operations queued successfully!
列出所有的租約(leases)
PS C:\Users\yao> vault list sys/leases/lookup/database/creds/my-db-role
Keys
----
7DQgKfYbJl5XJ660yCLSl4Su
Dny8L1FByuYddxYnAWyYMdsl
IQERRbcVznmbN8xkaRch1niB
使用動態憑證登入 PostgreSQL
隨便一個租約都是可以登入大象
檢視租約
PS C:\Users\yao> vault lease lookup database/creds/my-db-role/h0N2j19tAx0Viog8aMMIIYuj
Key Value
--- -----
expire_time 2024-10-07T23:20:15.4764045+08:00
id database/creds/my-db-role/h0N2j19tAx0Viog8aMMIIYuj
issue_time 2024-10-07T22:20:15.4764045+08:00
last_renewal <nil>
renewable true
ttl 34m55s
名詞解釋
- lease_id: 該租約的唯一識別碼。
- lease_duration: 憑證的有效時間。
- lease_renewable: 是否允許續租。
- lease_ttl: 憑證的剩餘存活時間(以秒為單位)。
C# VaultSharp
啟用 Database Secrets
[TestMethod]
public async Task _01_啟用資料庫()
{
var vaultClient = this.CreateVaultClient();
var enableSecretsEngine = new SecretsEngine
{
Type = new SecretsEngineType("database"),
Path = "database",
Description = "Database Secrets Engine"
};
await vaultClient.V1.System.MountSecretBackendAsync(enableSecretsEngine);
Console.WriteLine("Secrets 已成功寫入!");
}
設定 Valut 訪問資料庫
[TestMethod]
public async Task _02_配置資料庫連線()
{
var vaultClient = this.CreateVaultClient();
// 寫入配置到 Vault
var config = new PostgreSQLConnectionConfigModel
{
PluginName = "postgresql-database-plugin",
AllowedRoles = new List<string> { "my-db-role" },
Username = "user",
Password = "password",
ConnectionUrl = "postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable",
// 正確的 username_template 使用有效的模板函數
UsernameTemplate = "{{uuid}}",
};
var connectionName = "my-postgresql-database";
await vaultClient.V1.Secrets.Database.ConfigureConnectionAsync(connectionName, config);
Console.WriteLine("已成功寫入 PostgreSQL 配置!");
}
設定 Vault 角色
[TestMethod]
public async Task _03_建立角色()
{
var vaultClient = this.CreateVaultClient();
// 定義創建語句
var creationStatements = @"
CREATE ROLE ""{{name}}"" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ""{{name}}"";
";
var connectionName = "my-postgresql-database";
var role = new Role()
{
DatabaseProviderType = new DatabaseProviderType(connectionName),
DefaultTimeToLive = "1h",
MaximumTimeToLive = "24h",
CreationStatements = [creationStatements],
RevocationStatements = null,
RollbackStatements = null,
RenewStatements = null,
};
var roleName = "my-db-role";
await vaultClient.V1.Secrets.Database.CreateRoleAsync(roleName, role);
Console.WriteLine("已成功寫入資料庫角色配置!");
}
讀取 Role
[TestMethod]
public async Task _04_取得角色資訊()
{
var vaultClient = this.CreateVaultClient();
// 讀取資料庫角色的詳細資訊
var roleName = "my-db-role";
var roleInfo = await vaultClient.V1.Secrets.Database.ReadRoleAsync(roleName);
// 輸出角色的詳細資訊
var roleJson = JsonSerializer.Serialize(roleInfo);
Console.WriteLine(roleJson);
}
取得動態憑證 Dynamic Credentials
[TestMethod]
public async Task _05_建立憑證()
{
var vaultClient = this.CreateVaultClient();
// 讀取資料庫角色的憑證
var roleName = "my-db-role";
var credentials = await vaultClient.V1.Secrets.Database.GetCredentialsAsync(roleName);
// 輸出角色的詳細資訊
var roleJson = JsonSerializer.Serialize(credentials);
Console.WriteLine(roleJson);
}
續約憑證
[TestMethod]
public async Task _06_續約憑證()
{
var vaultClient = this.CreateVaultClient();
var leaseId = "database/creds/my-db-role/RhazfRMw84PiOJfFZD5QAEez";
// 續期租約
var renewedLease = await vaultClient.V1.System.RenewLeaseAsync(leaseId, 3600);
Console.WriteLine("租約已成功續期!");
var roleJson = JsonSerializer.Serialize(renewedLease);
Console.WriteLine(roleJson);
}
撤憑證證
[TestMethod]
public async Task _07_撤銷憑證()
{
var vaultClient = this.CreateVaultClient();
var leaseId = "database/creds/my-db-role/RhazfRMw84PiOJfFZD5QAEez";
// 續期租約
await vaultClient.V1.System.RevokeLeaseAsync(leaseId);
Console.WriteLine("租約已成功撤銷!");
}
列出所有的租約 (leases)
[TestMethod]
public async Task 讀取所有租約()
{
// 初始化 Vault Client
var vaultClient = this.CreateVaultClient();
// 指定要查詢的角色名稱
var roleName = "my-db-role";
try
{
var leases = await vaultClient.V1.System.GetAllLeasesAsync("database/creds/" + roleName);
var json = JsonSerializer.Serialize(leases);
Console.WriteLine(json);
}
catch (VaultApiException e)
{
Console.WriteLine(e.ToString());
}
}
心得
透過上面的演練,真的可以隱藏真實的帳號/密碼了,提升了安全性;考慮到 Vault Server 的負荷,應用程式跟 Vault Server 中間,可以考慮再墊一層快取。
後話:本來想著已經用 Vault CLI 實作 Dyncmic Credentials,換成用 VaultSharp 實現,透過 AI 應該可以很快速的轉換吧,沒想到沒有一個 AI 吐出來的答案是對的 QQ,花了一些時間在除錯,估計 VaultSharp 還沒有太多的資源。
範例位置
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET