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; /// /// Provides operations for managing secrets in Azure Key Vault or HashiCorp Vault transparently based on the environment. /// public sealed class KeyVaultProvider : IKeyVaultProvider { private readonly string environment; private readonly SecretClient? azureClient; private readonly IVaultClient? hashiClient; private readonly VaultOptions? hashiOptions; public KeyVaultProvider(IConfiguration configuration) { environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; if (environment == "Local") { 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); } }