使用 Vault Dynamic Credentials 訪問 PostgreSQL

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 還沒有太多的資源。

範例位置

sample.dotblog/Secrets Manager/Lab.HashiCorpVault/Lab.HashiCorpVault.Test/DynamicCredentialTest.cs at 02ef0ff66e2fe1b6d4c16b6869491b3c2cc9b6f0 · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo