Skip to content

HashiCorp Vault

HashiCorp Vault is an identity-based secrets and encryption management system.

Add the following dependency to your project file:

NuGet
1
dotnet add package Testcontainers.Vault

You can start a Vault container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations.

1
2
3
4
5
6
7
8
[UsedImplicitly]
public sealed class VaultDefaultConfiguration : VaultContainerTest
{
    public VaultDefaultConfiguration()
        : base(new VaultBuilder(TestSession.GetImageFromDockerfile()).Build(), VaultBuilder.DefaultRootToken)
    {
    }
}

This example uses xUnit.net's IAsyncLifetime interface to manage the lifecycle of the container. The container is started in the InitializeAsync method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the DisposeAsync method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public async ValueTask InitializeAsync()
{
    await _vaultContainer.StartAsync()
        .ConfigureAwait(false);
}

public async ValueTask DisposeAsync()
{
    await DisposeAsyncCore()
        .ConfigureAwait(false);

    GC.SuppressFinalize(this);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task ReadSecretReturnsWrittenSecret()
{
    // Given
    const string helloWorld = "Hello, World!";

    var path = Guid.NewGuid().ToString("D");

    var authMethod = new TokenAuthMethodInfo(_rootToken);

    var vaultClient = new VaultClient(new VaultClientSettings(_vaultContainer.GetBaseAddress(), authMethod));

    // When
    _ = await vaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync(path, new Dictionary<string, object> { { "message", helloWorld } }, mountPoint: "secret")
        .ConfigureAwait(true);

    var secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path, mountPoint: "secret")
        .ConfigureAwait(true);

    // Then
    Assert.Equal(helloWorld, secret.Data.Data["message"].ToString());
    Assert.Equal(_vaultContainer.GetBaseAddress(), _vaultContainer.GetConnectionString());
}

The test example writes a secret to the key/value secrets engine and reads it back. Use GetBaseAddress() to configure the Vault client. The container runs Vault in dev mode, and the default configuration uses the root token root. Use WithRootToken(string) to configure a different root token.

The test example uses the following NuGet dependencies:

1
2
3
4
5
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="VaultSharp"/>

To execute the tests, use the command dotnet test from a terminal.

Tip

For the complete source code of this example and additional information, please refer to our test projects.

Init commands

Use WithInitCommand(params string[]) to run Vault CLI commands after the container has started, for example to enable a secrets engine or to write a secret. The commands run in order, without the leading vault, and use the configured root token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[UsedImplicitly]
public sealed class VaultInitCommandConfiguration : VaultContainerTest
{
    private static readonly string RootToken = Guid.NewGuid().ToString("D");

    public VaultInitCommandConfiguration()
        : base(new VaultBuilder(TestSession.GetImageFromDockerfile()).WithRootToken(RootToken).WithInitCommand("secrets enable transit", "write -f transit/keys/orders").Build(), RootToken)
    {
    }

    [Fact]
    [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
    public async Task ReadEncryptionKeyReturnsCreatedKey()
    {
        // Given
        var authMethod = new TokenAuthMethodInfo(_rootToken);

        var vaultClient = new VaultClient(new VaultClientSettings(_vaultContainer.GetBaseAddress(), authMethod));

        // When
        var encryptionKey = await vaultClient.V1.Secrets.Transit.ReadEncryptionKeyAsync("orders")
            .ConfigureAwait(true);

        // Then
        Assert.Equal("orders", encryptionKey.Data.Name);
    }
}

If a command fails, the container start throws an ExecFailedException that contains the exit code and the output of the command. In dev mode, Vault keeps its data in memory. A restarted container starts without any data, so the init commands run on every start.