Vault
El VaultContainer
es un componente de la librería CodeDesignPlus.Net.xUnit que proporciona un contenedor Docker para ejecutar HashiCorp Vault, una herramienta para gestionar secretos y proteger información sensible. Este componente facilita la creación de pruebas unitarias que requieren una instancia de Vault, ofreciendo un entorno de pruebas aislado y reproducible.
¿Cómo Funciona?
El VaultContainer
utiliza Docker Compose
para definir y gestionar el contenedor Vault. Al inicializar una instancia de esta clase, se genera un archivo docker-compose.yml
temporal con la configuración necesaria para ejecutar el contenedor. Luego, el contenedor se inicia y se detiene automáticamente al final de la ejecución de las pruebas.
El proceso general de funcionamiento es el siguiente:
- Configuración del Contenedor: El método
Build()
define la configuración específica del contenedor Vault, incluyendo la ruta al archivodocker-compose.yml
, opciones de reinicio forzado, eliminación de contenedores huérfanos, parada al finalizar y un nombre de servicio alternativo generado dinámicamente para evitar conflictos. También se genera un identificador unico para el archivo de configuración. - Creación del Contenedor: Se crea una instancia de
DockerComposeCompositeService
usando la configuración definida. - Inicio y Detención del Contenedor: El contenedor se inicia cuando se crea una instancia de la clase
VaultContainer
y se detiene automáticamente cuando la instancia se elimina, asegurando la limpieza del entorno. - Obtención de Credenciales: Una vez el contenedor esta inicializado se procede a la lectura del archivo generado con las credenciales de Vault.
- Deserialización de Credenciales: Las credenciales son deserializadas a la clase
VaultCredentials
para su uso posterior.
Container
El VaultContainer
hereda de la clase DockerCompose
, la cual proporciona la lógica base para interactuar con Docker Compose. La clase utiliza la clase DockerComposeCompositeService
de la librería Testcontainers
para iniciar y detener contenedores docker. La configuración del contenedor se define en el método Build()
que crea un archivo docker-compose.yml
con los parámetros necesarios para la ejecución del contenedor Vault. El archivo docker-compose.yml
se encuentra dentro del directorio Containers/VaultContainer
.
namespace CodeDesignPlus.Net.xUnit.Containers.VaultContainer;
/// <summary>/// Represents a Docker container for SQL Server, managed using Docker Compose./// </summary>public class VaultContainer : DockerCompose{ public VaultCredentials Credentials { get; private set; }
private readonly string id = Guid.NewGuid().ToString("N");
public VaultContainer() : base() {
}
/// <summary> /// Builds the Docker Compose service configuration for the SQL Server container. /// </summary> /// <returns>An <see cref="ICompositeService"/> representing the Docker Compose service.</returns> protected override ICompositeService Build() { var file = Path.Combine(Directory.GetCurrentDirectory(), "Containers", "VaultContainer", "docker-compose.yml");
var dockerCompose = new DockerComposeConfig { ComposeFilePath = [file], ForceRecreate = true, RemoveOrphans = true, StopOnDispose = true, AlternativeServiceName = "vault_" + this.id, EnvironmentNameValue = new Dictionary<string, string> { { "FILE_CREDENTIAL", this.id }, } };
this.EnableGetPort = true; this.InternalPort = 8200; this.ContainerName = $"{dockerCompose.AlternativeServiceName}-vault";
var compose = new DockerComposeCompositeService(base.DockerHost, dockerCompose);
return compose; }
protected override void OnContainerInitialized() { // Wait for the vault container to be ready (RabbitMQ) Thread.Sleep(30000);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Containers", "VaultContainer", "shared", "vault-config", $"{this.id}-credentials.json");
if (!File.Exists(filePath)) throw new FileNotFoundException("The file with the credentials was not found.", filePath);
string json = File.ReadAllText(filePath); this.Credentials = JsonSerializer.Deserialize<VaultCredentials>(json); }}
public class VaultCredentials{ [Newtonsoft.Json.JsonProperty("role_id")] public string RoleId { get; set; } [Newtonsoft.Json.JsonProperty("secret_id")] public string SecretId { get; set; }}
La clase VaultCollectionFixture
se utiliza como un fixture de xUnit, que proporciona una forma de compartir el contenedor entre las pruebas de una colección.
namespace CodeDesignPlus.Net.xUnit.Containers.VaultContainer;
public sealed class VaultCollectionFixture : IDisposable{ public const string Collection = "Vault Collection";
public VaultContainer Container { get; }
public VaultCollectionFixture() { this.Container = new VaultContainer(); }
public void Dispose() { this.Container.StopInstance(); }}
Ejemplo de Uso
Este ejemplo es una prueba unitaria en C# utilizando xUnit para verificar la conexión a un servidor Vault y la correcta recuperación de secretos. La prueba configura un cliente de Vault usando AppRoleAuthMethodInfo para la autenticación, lee un secreto específico y verifica que contiene una clave y un valor esperados.
using CodeDesignPlus.Net.xUnit.Containers.VaultContainer;using VaultSharp;using VaultSharp.V1.AuthMethods;using VaultSharp.V1.AuthMethods.AppRole;
namespace CodeDesignPlus.Net.xUnit.Test;
[Collection(VaultCollectionFixture.Collection)]public class VaultContainerTest(VaultCollectionFixture fixture){
[Fact] public async Task CheckConnectionService() { var authMethod = new AppRoleAuthMethodInfo(fixture.Container.Credentials.RoleId, fixture.Container.Credentials.SecretId); var vaultClientSettings = new VaultClientSettings($"http://localhost:{fixture.Container.Port}", authMethod);
var vaultClient = new VaultClient(vaultClientSettings);
var kv1Secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync("my-app", mountPoint: "unit-test-keyvalue");
Assert.True(kv1Secret.Data.Data.ContainsKey("Security:ClientId")); Assert.Equal("a74cb192-598c-4757-95ae-b315793bbbca", kv1Secret.Data.Data["Security:ClientId"].ToString()); }}
using System;using CodeDesignPlus.Net.xUnit.Containers.VaultContainer;
namespace CodeDesignPlus.Net.xUnit.Test.Definitions;
[CollectionDefinition(VaultCollectionFixture.Collection)]public class VaultContainerDefinition : ICollectionFixture<VaultCollectionFixture>{}
Conclusiones
- El
VaultContainer
facilita la creación de pruebas unitarias que requieren una instancia de HashiCorp Vault. - Utiliza Docker Compose para la gestión del contenedor, proporcionando una forma sencilla de definir la configuración.
- Proporciona las credenciales de Vault a través de la propiedad
Credentials
. - La clase
VaultCollectionFixture
permite compartir un contenedor entre todas las pruebas de una misma colección. - El uso del
VaultContainer
mejora la reproducibilidad y el aislamiento de las pruebas unitarias. - El contenedor Vault se inicia automaticamente al comienzo de cada prueba y se detiene una vez termina, esto permite un ambiente de pruebas limpio.