From 5410a9f9a0f18a666db8eb5b41421ed52136fc8c Mon Sep 17 00:00:00 2001 From: Sergio Matias Urquin Date: Sun, 1 Jun 2025 21:03:03 -0600 Subject: [PATCH] Implement hashi corp vault --- .../Configuration/RegisterBlueprint.cs | 29 ++- .../Configuration/VaultOptions.cs | 16 ++ .../Core.Blueprint.KeyVault.csproj | 2 + .../Provider/KeyVaultProvider.cs | 215 +++++++++++------- 4 files changed, 170 insertions(+), 92 deletions(-) create mode 100644 Core.Blueprint.KeyVault/Configuration/VaultOptions.cs diff --git a/Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs b/Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs index ea094e9..0b2ce9a 100644 --- a/Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs +++ b/Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs @@ -16,17 +16,34 @@ namespace Core.Blueprint.KeyVault.Configuration { public static IServiceCollection AddKeyVault(this IServiceCollection services, IConfiguration configuration) { - var keyVaultUriString = configuration["ConnectionStrings:KeyVaultDAL"]; - if (string.IsNullOrEmpty(keyVaultUriString)) + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty; + + if(environment == "Local") { - throw new ArgumentNullException("ConnectionStrings:KeyVault", "KeyVault URI is missing in the configuration."); + var vaultSettings = configuration.GetSection("Vault").Get(); + + if (string.IsNullOrEmpty(vaultSettings?.Address) || string.IsNullOrEmpty(vaultSettings.Token) || + string.IsNullOrEmpty(vaultSettings?.SecretPath) || string.IsNullOrEmpty(vaultSettings.SecretMount)) + { + throw new ArgumentNullException("Vault options are not configured correctly."); + } + + services.AddSingleton(vaultSettings); } + else + { + var keyVaultUriString = configuration["ConnectionStrings:KeyVaultDAL"]; - var keyVaultUri = new Uri(keyVaultUriString); + if (string.IsNullOrEmpty(keyVaultUriString)) + { + throw new ArgumentNullException("ConnectionStrings:KeyVault", "KeyVault URI is missing in the configuration."); + } - // Register SecretClient as a singleton - services.AddSingleton(_ => new SecretClient(keyVaultUri, new DefaultAzureCredential())); + var keyVaultUri = new Uri(keyVaultUriString); + + services.AddSingleton(_ => new SecretClient(keyVaultUri, new DefaultAzureCredential())); + } services.AddSingleton(); return services; diff --git a/Core.Blueprint.KeyVault/Configuration/VaultOptions.cs b/Core.Blueprint.KeyVault/Configuration/VaultOptions.cs new file mode 100644 index 0000000..48aa990 --- /dev/null +++ b/Core.Blueprint.KeyVault/Configuration/VaultOptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Blueprint.KeyVault.Configuration +{ + public class VaultOptions + { + public string Address { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; + public string SecretMount { get; set; } = string.Empty; + public string SecretPath { get; set; } = string.Empty; + } +} diff --git a/Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj b/Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj index 84d4a87..7e8dd63 100644 --- a/Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj +++ b/Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj @@ -10,7 +10,9 @@ + + diff --git a/Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs b/Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs index 707fb5c..74d495e 100644 --- a/Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs +++ b/Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs @@ -1,93 +1,136 @@ -using Azure; -using Azure.Security.KeyVault.Secrets; +using Azure.Security.KeyVault.Secrets; +using VaultSharp; +using VaultSharp.V1.AuthMethods.Token; +using Core.Blueprint.KeyVault.Configuration; +using Microsoft.Extensions.Configuration; -namespace Core.Blueprint.KeyVault +namespace Core.Blueprint.KeyVault; + +/// +/// Provides operations for managing secrets in Azure Key Vault or HashiCorp Vault transparently based on the environment. +/// +public sealed class KeyVaultProvider : IKeyVaultProvider { - /// - /// Provides operations for managing secrets in Azure Key Vault. - /// - public sealed class KeyVaultProvider(SecretClient keyVaultProvider): IKeyVaultProvider + private readonly string environment; + private readonly SecretClient? azureClient; + private readonly IVaultClient? hashiClient; + private readonly VaultOptions? hashiOptions; + + public KeyVaultProvider(IConfiguration configuration) { - /// - /// Creates a new secret in Azure Key Vault. - /// - /// The request containing the name and value of the secret. - /// The cancellation token to cancel the operation. - /// A containing the details of the created secret. - public async ValueTask CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken) + environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; + + if (environment == "Local") { - KeyVaultResponse _response = new(); - KeyVaultSecret azureResponse = await keyVaultProvider.SetSecretAsync(new KeyVaultSecret(keyVaultRequest.Name, keyVaultRequest.Value), cancellationToken); - - _response.Value = azureResponse.Value; - _response.Name = azureResponse.Name; - - return _response; - } - - /// - /// Deletes a secret from Azure Key Vault if it exists. - /// - /// The name of the secret to delete. - /// The cancellation token to cancel the operation. - /// - /// A containing a status message and a boolean indicating whether the secret was successfully deleted. - /// - public async ValueTask> DeleteSecretAsync(string secretName, CancellationToken cancellationToken) - { - var existingSecret = await this.GetSecretAsync(secretName, cancellationToken); - if (existingSecret != null) - { - await keyVaultProvider.StartDeleteSecretAsync(secretName, cancellationToken); - return new("Key Deleted", true); - } - - return new("Key Not Found", false); - } - - /// - /// Retrieves a secret from Azure Key Vault. - /// - /// The name of the secret to retrieve. - /// The cancellation token to cancel the operation. - /// - /// A containing the with secret details - /// and an optional error message if the secret was not found. - /// - public async ValueTask> GetSecretAsync(string secretName, CancellationToken cancellationToken) - { - KeyVaultSecret azureResponse = await keyVaultProvider.GetSecretAsync(secretName, cancellationToken: cancellationToken); - - if (azureResponse == null) - { - return new(new KeyVaultResponse(), "Key Not Found"); - } - - return new(new KeyVaultResponse { Name = secretName, Value = azureResponse.Value }, string.Empty); - } - - /// - /// Updates an existing secret in Azure Key Vault. If the secret does not exist, an error is returned. - /// - /// The updated secret information. - /// The cancellation token to cancel the operation. - /// - /// A containing the updated and an optional error message if the secret was not found. - /// - public async ValueTask> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken) - { - KeyVaultResponse _response = new(); - var existingSecret = await this.GetSecretAsync(newSecret.Name, cancellationToken); - if (existingSecret == null) - { - return new(new KeyVaultResponse(), "Key Not Found"); - } - KeyVaultSecret azureResponse = await keyVaultProvider.SetSecretAsync(new KeyVaultSecret(newSecret.Name, newSecret.Value), cancellationToken); - - _response.Value = azureResponse.Value; - _response.Name = azureResponse.Name; - - return new(new KeyVaultResponse { Name = newSecret.Name, Value = azureResponse.Value }, string.Empty); + hashiOptions = configuration.GetSection("Vault").Get(); + hashiClient = new VaultClient(new VaultClientSettings( + hashiOptions?.Address, + new TokenAuthMethodInfo(hashiOptions?.Token) + )); } } + + /// + /// Creates a new secret in Azure Key Vault or HashiCorp Vault. + /// + /// The request containing the name and value of the secret. + /// The cancellation token to cancel the operation. + /// A containing the details of the created secret. + public async ValueTask CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken) + { + if (environment == "Local") + { + await hashiClient!.V1.Secrets.KeyValue.V2.WriteSecretAsync( + path: hashiOptions!.SecretPath, + data: new Dictionary { { keyVaultRequest.Name, keyVaultRequest.Value } }, + mountPoint: hashiOptions.SecretMount + ); + return new KeyVaultResponse { Name = keyVaultRequest.Name, Value = keyVaultRequest.Value }; + } + + KeyVaultSecret azureResponse = await azureClient!.SetSecretAsync( + new KeyVaultSecret(keyVaultRequest.Name, keyVaultRequest.Value), cancellationToken + ); + + return new KeyVaultResponse { Name = azureResponse.Name, Value = azureResponse.Value }; + } + + /// + /// Deletes a secret from Azure Key Vault or HashiCorp Vault if it exists. + /// + /// The name of the secret to delete. + /// The cancellation token to cancel the operation. + /// + /// A containing a status message and a boolean indicating whether the secret was successfully deleted. + /// + public async ValueTask> DeleteSecretAsync(string secretName, CancellationToken cancellationToken) + { + if (environment == "Local") + { + await hashiClient!.V1.Secrets.KeyValue.V2.DeleteSecretAsync( + path: hashiOptions!.SecretPath, + mountPoint: hashiOptions.SecretMount + ); + + return new("Key Deleted", true); + } + + var existingSecret = await this.GetSecretAsync(secretName, cancellationToken); + if (existingSecret != null) + { + await azureClient!.StartDeleteSecretAsync(secretName, cancellationToken); + return new("Key Deleted", true); + } + + return new("Key Not Found", false); + } + + /// + /// Retrieves a secret from Azure Key Vault or HashiCorp Vault. + /// + /// The name of the secret to retrieve. + /// The cancellation token to cancel the operation. + /// + /// A containing the with secret details + /// and an optional error message if the secret was not found. + /// + public async ValueTask> GetSecretAsync(string secretName, CancellationToken cancellationToken) + { + if (environment == "Local") + { + var secret = await hashiClient!.V1.Secrets.KeyValue.V2.ReadSecretAsync( + path: hashiOptions!.SecretPath, + mountPoint: hashiOptions.SecretMount + ); + + if (secret.Data.Data.TryGetValue(secretName, out var value)) + { + return new(new KeyVaultResponse { Name = secretName, Value = value?.ToString() ?? "" }, string.Empty); + } + + return new(new KeyVaultResponse(), "Key Not Found"); + } + + KeyVaultSecret azureResponse = await azureClient!.GetSecretAsync(secretName, cancellationToken: cancellationToken); + return new(new KeyVaultResponse { Name = secretName, Value = azureResponse.Value }, string.Empty); + } + + /// + /// Updates an existing secret in Azure Key Vault or HashiCorp Vault. If the secret does not exist, an error is returned. + /// + /// The updated secret information. + /// The cancellation token to cancel the operation. + /// + /// A containing the updated and an optional error message if the secret was not found. + /// + public async ValueTask> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken) + { + var existingSecret = await this.GetSecretAsync(newSecret.Name, cancellationToken); + if (existingSecret == null) + { + return new(new KeyVaultResponse(), "Key Not Found"); + } + + return new(await CreateSecretAsync(newSecret, cancellationToken), string.Empty); + } }