Exploring HashiCorp Vault with Vault .NET Client Library
Tags: vault, dotnet, tutorial
Published: 2023-04-23 11:56:32

Hello, everyone! How are you today? I hope you are doing fine. I'm so excited because this is my first post about HashiCorp Vault. In short, Vault is a secret manager, but it's not only that! Please check [here](https://developer.hashicorp.com/vault/docs/what-is-vault) for more information about Vault.

This [page](https://developer.hashicorp.com/vault/docs/get-started/developer-qs) inspires me to create this post. If you have some questions, feel free to ask me. I expect you already understand Docker and .NET. If you are still unsure, feel free to comment.

## Prerequisites

1. [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [Docker](https://docs.docker.com/get-docker/)

## Preparing the Project

1. We will use the project generator provided by .NET 8 SDK. Please generate the solution file. `dotnet new sln -o VaultExample`.
2. Change the directory to VaultExample. `cd VaultExample`.
3. Now, we will create the Minimal Web API. `dotnet new api -o VaultExampleAPI`
4. Add the `HashiCrop.Vault` to the `VaultExampleAPI` project. `dotnet add VaultExampleAPI package Hashicorp.Vault --version "0.1.0-beta"`
5. Add the `VaultExampleAPI` to the solution. `dotnet sln add VaultExampleAPI`.
6. Now, it's ready to code!

## Writing the Code to Read and Write Secrets into Vault

1. Create `VaultData.cs` inside `VaultExampleAPI` directory.

    ```csharp
    namespace VaultExampleAPI
    {
        public class VaultData
        {
            public string Name { get; set; }
            public Dictionary<string, string> Data { get; set; }
        }
    }
    ```

2. Update `Program.cs`.

    ```csharp
    using Microsoft.AspNetCore.Mvc;
    using System.Text.Json;
    using Vault;
    using Vault.Client;
    using Vault.Model;
    using VaultExampleAPI;
    
    var builder = WebApplication.CreateSlimBuilder(args);
    builder.Logging.AddConsole();
    
    builder.Services.AddSingleton((serviceProvider) =>
    {
       string address = "http://127.0.0.1:8200";
       VaultConfiguration config = new(address);

       VaultClient vaultClient = new(config);
       vaultClient.SetToken("dev-only-token");
       return vaultClient;
    });
    
    var app = builder.Build();
   
    var sampleTodos = TodoGenerator.GenerateTodos().ToArray();
    
    var todosApi = app.MapGroup("/todos");
    todosApi.MapGet("/", () => sampleTodos);
    todosApi.MapGet("/{id}", (int id) =>
         sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
             ? Results.Ok(todo)
             : Results.NotFound());
     
     var secretsApi = app.MapGroup("/secrets");
     
     secretsApi.MapPost("/", (VaultClient vaultClient, [FromBody] VaultData secretData) =>
     {
         // Write a secret
         var kvRequestData = new KVv2WriteRequest(secretData.Data);
         vaultClient.Secrets.KVv2Write(secretData.Name, kvRequestData);
         return Results.Ok();
     });
     
     secretsApi.MapGet("/{secretKey}", (ILoggerFactory loggerFactory, VaultClient vaultClient, string secretKey) =>
     {
         var logger = loggerFactory.CreateLogger("vault");
         try
         {
             VaultResponse<object> resp = vaultClient.Secrets.KVv2Read(secretKey);
             var data = resp.Data.ToString();
             return !string.IsNullOrEmpty(data) ? Results.Ok(JsonSerializer.Deserialize<object>(data)) : Results.NotFound();
         }
         catch (VaultApiException ex)
         {
             logger.LogError(ex, "vault error");
             return Results.NotFound();
         }
     });
     
     app.Run();
     ```

### Break Down Each Added Code

* Let's break down each added code. First, we add the Dependency Injection (DI) for Vault Client as a Singleton. So, we can get the Vault Client in our Minimal Web API through DI.
    ```csharp
    builder.Services.AddSingleton((serviceProvider) =>
    {
       string address = "http://127.0.0.1:8200";
       VaultConfiguration config = new(address);

       VaultClient vaultClient = new(config);
       vaultClient.SetToken("dev-only-token");
       return vaultClient;
    });
   ```

* We create an API group. `var secretsApi = app.MapGroup("/secrets");`

* We will use the `POST` method for writing a secret.

    ```csharp
    secretsApi.MapPost("/", (VaultClient vaultClient, [FromBody] VaultData secretData) =>
     {
         // Write a secret
         var kvRequestData = new KVv2WriteRequest(secretData.Data);
         vaultClient.Secrets.KVv2Write(secretData.Name, kvRequestData);
         return Results.Ok();
     });
    ```

* We will use the `GET {secretKey}` for reading the secret.

     ```csharp
     secretsApi.MapGet("/{secretKey}", (ILoggerFactory loggerFactory, VaultClient vaultClient, string secretKey) =>
     {
         var logger = loggerFactory.CreateLogger("vault");
         try
         {
             VaultResponse<object> resp = vaultClient.Secrets.KVv2Read(secretKey);
             var data = resp.Data.ToString();
             return !string.IsNullOrEmpty(data) ? Results.Ok(JsonSerializer.Deserialize<object>(data)) : Results.NotFound();
         }
         catch (VaultApiException ex)
         {
             logger.LogError(ex, "vault error");
             return Results.NotFound();
         }
     });
     ```

## More Information About The Codes

You can the repository [here](https://github.com/bervProject/VaultExample). My Pull Request:

{% embed https://github.com/bervProject/VaultExample/pull/1 %}

## Testing our API

1. Run the Vault server. We can use `docker-compose.yml` to help us and run `docker compose up -d`.

    ```yml
    version: '3.9'
    services:
      vault:
        ports:
        - 8200:8200
        environment:
        - VAULT_DEV_ROOT_TOKEN_ID=dev-only-token
        image: vault
    ```
2. Open a browser and type `localhost:8200`. Ensure the Vault page is shown.

    ![localhost](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4myh8tu95978s7wymrgj.png)

3. Run the API. `dotnet run VaultExampleAPI`

4. Test use HTTP Client or Postman.

### Testing Write Secret

![writing secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csx5pmugcef4u22jiuu9.png)

### Testing Read Secret

![Read secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2laikb35nk457oq79l5r.png)

### Read the secret from the Browser

1. Continue to open `localhost:8200` in the browser and input the token `dev-only-token`.

2. Click on `secret`.

    ![secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5mugtfm3w65g8r53bxs.png)

3. Click on the created secret.

    ![created secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slowrvtu78kqbkl7vr9u.png)

4. You can check the entry.

    ![entry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lmqlbzloi3kp6tf42o1.png)

## Thank you for reading

Thank you for reading. :) Feel free to give feedback in the comment section.

![Awesome GIF](https://media.giphy.com/media/sJrp1Nn3ReSfGo4eKy/giphy.gif)