HashiCorp Vault 客戶端程式開發 - 使用 VaultSharp

§ 引言

此文章紀錄取用 HashiCorp Vault(簡稱 Vault)的客方端程式碼開發基本範例。 本例只有取用秘密,一般來說也只會開放讀取給應用端。管理端肯定是另一條通道。

在 client 端取得保險箱(vault)中的秘密(secrets)。 有二種方式,一、經由 Web API;二、經由專用函式庫。 本例用官方建議的函式庫之一 VaultShare 取用秘密(secrets)。

§ 參考文件

HashiCorp Vault 開發環境部署Vault,HashiCorp Vault 入門練習Vault, 保險庫


§ 開發環境

  • IDE: Visual Studio 2022

  • 平台: .NET8

  • 程式語言: C# 12

  • 框架: Blazer Web App (server mode)

§ 本例簡述

Vault 提供多種方法多種通道存取秘密。 本例只練習三種:

  1. root token。

  2. userpass 使用者帳號密碼。

  3. cert 檢驗客戶端憑證 。

§ 前端程式碼

@using System.Security.Cryptography.X509Certificates
@using VaultSharp
@using VaultSharp.V1.AuthMethods
@using VaultSharp.V1.AuthMethods.Token
@using VaultSharp.V1.Commons
@using VaultSharp.V1.SecretsEngines
@using System.Text.Json
@page "/vault-lab"

<PageTitle>VaultLab</PageTitle>

<h3>_VaultLab</h3>
<p>@message</p>
<p>connStr1: @connStr1<br/>connStr2: @connStr2</p>

<pre>
  @if (secret != null)
  {
    @JsonSerializer.Serialize(secret, new JsonSerializerOptions { WriteIndented = true })
  }
</pre>

<button class="btn btn-primary" @onclick=GetVaultSecret>Get Vault with token</button>
<button class="btn btn-primary" @onclick=GetVaultSecret2>Get Vault with user/password</button>
<button class="btn btn-primary" @onclick=GetVaultSecret3>Get Vault with cert</button>

@code {
  //## Resource
  //const string vaultAddress = "https://127.0.0.1:8200";
  const string vaultAddress = "https://localhost:8200";

  //## State
  string message = "INIT";
  Secret<SecretData>? secret = null;
  string connStr1 = string.Empty;
  string connStr2 = string.Empty;

  /// <summary>
  /// 用 root token 取得 Vault 的 secret。
  /// </summary>
  async Task GetVaultSecret()
  {
    try
    {
      message = "START TokenAuthMethodInfo";

      const string rootToken = "hvs.AYTJgwte3oScGHIQX2mzeQCI";
      IAuthMethodInfo authMethod = new TokenAuthMethodInfo(rootToken);

      var vaultClientSettings = new VaultClientSettings(vaultAddress, authMethod);
      IVaultClient vaultClient = new VaultClient(vaultClientSettings);

      const string path = "myAwesomeApp/creds";
      const string mountPoint = "secret";
      secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path, mountPoint: mountPoint);

      IDictionary<string, object> result = secret.Data.Data;
      connStr1 = Convert.ToString(result["conn_str1"]) ?? string.Empty;
      connStr2 = Convert.ToString(result["conn_str2"]) ?? string.Empty;

      message = "SUCCESS";
    }
    catch (Exception ex)
    {
      message = "出現例外!" + ex.Message;
    }
  }

  /// <summary>
  /// 用 user/password 取得 Vault 的 secret。
  /// </summary>
  async Task GetVaultSecret2()
  {
    try
    {
      message = "START UserPassAuthMethodInfo";

      const string username = "my_user";
      const string password = "my_password";
      IAuthMethodInfo authMethod = new VaultSharp.V1.AuthMethods.UserPass.UserPassAuthMethodInfo(username, password);

      var vaultClientSettings = new VaultClientSettings(vaultAddress, authMethod);
      IVaultClient vaultClient = new VaultClient(vaultClientSettings);

      const string path = "myAwesomeApp/creds";
      const string mountPoint = "secret";
      secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path, mountPoint: mountPoint);

      IDictionary<string, object> result = secret.Data.Data;
      connStr1 = Convert.ToString(result["conn_str1"]) ?? string.Empty;
      connStr2 = Convert.ToString(result["conn_str2"]) ?? string.Empty;

      message = "SUCCESS";
    }
    catch (Exception ex)
    {
      message = "出現例外!" + ex.Message;
    }
  }

  /// <summary>
  /// 用憑證取得 Vault 的 secret。
  /// </summary>
  async Task GetVaultSecret3()
  {
    try
    {
      message = "START CertAuthMethodInfo";

      using var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
      certStore.Open(OpenFlags.ReadOnly);
      var cert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "27e630b830d9af8c9b459ab84e0715b9c09b4392", false).First();
      // 該憑證必需有私鑰且可匯出。(可私錀原則上不可匯出才對吧!)

      //byte[] certBlob = System.IO.File.ReadAllBytes("D:\\Temp\\my127002.pfx");
      //X509Certificate2 cert = new X509Certificate2(certBlob, "12345678");
      IAuthMethodInfo authMethod = new VaultSharp.V1.AuthMethods.Cert.CertAuthMethodInfo(cert);

      var vaultClientSettings = new VaultClientSettings(vaultAddress, authMethod);
      IVaultClient vaultClient = new VaultClient(vaultClientSettings);

      const string path = "myAwesomeApp/creds";
      const string mountPoint = "secret";
      secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path, mountPoint: mountPoint);

      IDictionary<string, object> result = secret.Data.Data;
      connStr1 = Convert.ToString(result["conn_str1"]) ?? string.Empty;
      connStr2 = Convert.ToString(result["conn_str2"]) ?? string.Empty;

      message = "SUCCESS";
    }
    catch (Exception ex)
    {
      message = "出現例外!" + ex.Message;

      Exception? innerEx = ex.InnerException;
      while (innerEx != null)
      {
        message += innerEx.Message;
        innerEx = innerEx?.InnerException;
      }
    }
  }
}

§ 後端相應設定

1) 當然要先存入秘密。

利用Vaualt管理介面存入模擬的連線字串。名為 conn_str1, conn_str2。 存取路徑(path):secret/mySwesomeApp/creds

2) 透過 policy 授權。

依照 Vault 的規則需經由 policy 授權存取對象。我們設定了my_policy 取存 secret 底下全部資源。

3) 設定存取方法。

存取方法此例有三種: token, userpass, cert。如圖

一、token,即 root token 在服務第一次啟用時就可拿到。

二、userpass 使用者帳號密碼。 新增時指定 username, password 外,還需指定前面設定好的 my_policy 以授權存取對象。

三、cert 客戶端憑證。 經測試此功能需主機端啟動TSL才有效用。 需上傳客戶端憑證(無金錀的公開憑證)並指定授權policy即可。

上傳客戶端公開憑證
別忘了指定 policy 以授權

§ 沒圖沒真象

由程式拿回來的秘密(secret)畫面。

(EOF)

Last updated