Implement hashicorp vault
This commit is contained in:
		| @@ -23,8 +23,8 @@ namespace Core.Blueprint.KeyVault.Configuration | |||||||
|             { |             { | ||||||
|                 var vaultSettings = configuration.GetSection("Vault").Get<VaultOptions>(); |                 var vaultSettings = configuration.GetSection("Vault").Get<VaultOptions>(); | ||||||
|  |  | ||||||
|                 if (string.IsNullOrEmpty(vaultSettings?.Address) || string.IsNullOrEmpty(vaultSettings.Token) || |                 if (string.IsNullOrEmpty(vaultSettings?.Address) || string.IsNullOrEmpty(vaultSettings.Token) | ||||||
|                     string.IsNullOrEmpty(vaultSettings?.SecretPath) || string.IsNullOrEmpty(vaultSettings.SecretMount)) |                     || string.IsNullOrEmpty(vaultSettings.SecretMount)) | ||||||
|                 { |                 { | ||||||
|                     throw new ArgumentNullException("Vault options are not configured correctly."); |                     throw new ArgumentNullException("Vault options are not configured correctly."); | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -11,6 +11,5 @@ namespace Core.Blueprint.KeyVault.Configuration | |||||||
|         public string Address { get; set; } = string.Empty; |         public string Address { get; set; } = string.Empty; | ||||||
|         public string Token { get; set; } = string.Empty; |         public string Token { get; set; } = string.Empty; | ||||||
|         public string SecretMount { get; set; } = string.Empty; |         public string SecretMount { get; set; } = string.Empty; | ||||||
|         public string SecretPath { get; set; } = string.Empty; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ using VaultSharp; | |||||||
| using VaultSharp.V1.AuthMethods.Token; | using VaultSharp.V1.AuthMethods.Token; | ||||||
| using Core.Blueprint.KeyVault.Configuration; | using Core.Blueprint.KeyVault.Configuration; | ||||||
| using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||||
|  | using System.Net.Http.Json; | ||||||
|  | using VaultSharp.Core; | ||||||
|  |  | ||||||
| namespace Core.Blueprint.KeyVault; | namespace Core.Blueprint.KeyVault; | ||||||
|  |  | ||||||
| @@ -28,22 +30,24 @@ public sealed class KeyVaultProvider : IKeyVaultProvider | |||||||
|                 new TokenAuthMethodInfo(hashiOptions?.Token) |                 new TokenAuthMethodInfo(hashiOptions?.Token) | ||||||
|             )); |             )); | ||||||
|         } |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             var keyVaultUri = new Uri(configuration["ConnectionStrings:KeyVaultDAL"]!); | ||||||
|  |             azureClient = new SecretClient(keyVaultUri, new Azure.Identity.DefaultAzureCredential()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Creates a new secret in Azure Key Vault or HashiCorp Vault. |     /// Creates a new secret in Azure Key Vault or HashiCorp Vault. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="keyVaultRequest">The request containing the name and value of the secret.</param> |  | ||||||
|     /// <param name="cancellationToken">The cancellation token to cancel the operation.</param> |  | ||||||
|     /// <returns>A <see cref="KeyVaultResponse"/> containing the details of the created secret.</returns> |  | ||||||
|     public async ValueTask<KeyVaultResponse> CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken) |     public async ValueTask<KeyVaultResponse> CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (environment == "Local") |         if (environment == "Local") | ||||||
|         { |         { | ||||||
|             await hashiClient!.V1.Secrets.KeyValue.V2.WriteSecretAsync( |             await hashiClient!.V1.Secrets.KeyValue.V2.WriteSecretAsync( | ||||||
|                 path: hashiOptions!.SecretPath, |                 path: keyVaultRequest.Name, | ||||||
|                 data: new Dictionary<string, object> { { keyVaultRequest.Name, keyVaultRequest.Value } }, |                 data: new Dictionary<string, object> { { "value", keyVaultRequest.Value } }, | ||||||
|                 mountPoint: hashiOptions.SecretMount |                 mountPoint: hashiOptions!.SecretMount | ||||||
|             ); |             ); | ||||||
|             return new KeyVaultResponse { Name = keyVaultRequest.Name, Value = keyVaultRequest.Value }; |             return new KeyVaultResponse { Name = keyVaultRequest.Name, Value = keyVaultRequest.Value }; | ||||||
|         } |         } | ||||||
| @@ -56,7 +60,7 @@ public sealed class KeyVaultProvider : IKeyVaultProvider | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Deletes a secret from Azure Key Vault or HashiCorp Vault if it exists. |     /// Permanently deletes a secret from Azure Key Vault or HashiCorp Vault (hard delete for Vault). | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="secretName">The name of the secret to delete.</param> |     /// <param name="secretName">The name of the secret to delete.</param> | ||||||
|     /// <param name="cancellationToken">The cancellation token to cancel the operation.</param> |     /// <param name="cancellationToken">The cancellation token to cancel the operation.</param> | ||||||
| @@ -67,16 +71,11 @@ public sealed class KeyVaultProvider : IKeyVaultProvider | |||||||
|     { |     { | ||||||
|         if (environment == "Local") |         if (environment == "Local") | ||||||
|         { |         { | ||||||
|             await hashiClient!.V1.Secrets.KeyValue.V2.DeleteSecretAsync( |             await DestroyAllSecretVersionsAsync(secretName, cancellationToken); | ||||||
|                 path: hashiOptions!.SecretPath, |  | ||||||
|                 mountPoint: hashiOptions.SecretMount |  | ||||||
|             ); |  | ||||||
|  |  | ||||||
|             return new("Key Deleted", true); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var existingSecret = await this.GetSecretAsync(secretName, cancellationToken); |         var existingSecret = await this.GetSecretAsync(secretName, cancellationToken); | ||||||
|         if (existingSecret != null) |         if (existingSecret.Item2 == string.Empty) | ||||||
|         { |         { | ||||||
|             await azureClient!.StartDeleteSecretAsync(secretName, cancellationToken); |             await azureClient!.StartDeleteSecretAsync(secretName, cancellationToken); | ||||||
|             return new("Key Deleted", true); |             return new("Key Deleted", true); | ||||||
| @@ -85,52 +84,105 @@ public sealed class KeyVaultProvider : IKeyVaultProvider | |||||||
|         return new("Key Not Found", false); |         return new("Key Not Found", false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Retrieves a secret from Azure Key Vault or HashiCorp Vault. |     /// Retrieves a secret from Azure Key Vault or HashiCorp Vault. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="secretName">The name of the secret to retrieve.</param> |  | ||||||
|     /// <param name="cancellationToken">The cancellation token to cancel the operation.</param> |  | ||||||
|     /// <returns> |  | ||||||
|     /// A <see cref="Tuple"/> containing the <see cref="KeyVaultResponse"/> with secret details  |  | ||||||
|     /// and an optional error message if the secret was not found. |  | ||||||
|     /// </returns> |  | ||||||
|     public async ValueTask<Tuple<KeyVaultResponse, string?>> GetSecretAsync(string secretName, CancellationToken cancellationToken) |     public async ValueTask<Tuple<KeyVaultResponse, string?>> GetSecretAsync(string secretName, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (environment == "Local") |         if (environment == "Local") | ||||||
|  |         { | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 var secret = await hashiClient!.V1.Secrets.KeyValue.V2.ReadSecretAsync( |                 var secret = await hashiClient!.V1.Secrets.KeyValue.V2.ReadSecretAsync( | ||||||
|                 path: hashiOptions!.SecretPath, |                     path: secretName, | ||||||
|                 mountPoint: hashiOptions.SecretMount |                     mountPoint: hashiOptions!.SecretMount | ||||||
|                 ); |                 ); | ||||||
|  |  | ||||||
|             if (secret.Data.Data.TryGetValue(secretName, out var value)) |                 if (secret.Data.Data.TryGetValue("value", out var value)) | ||||||
|                 { |                 { | ||||||
|                     return new(new KeyVaultResponse { Name = secretName, Value = value?.ToString() ?? "" }, string.Empty); |                     return new(new KeyVaultResponse { Name = secretName, Value = value?.ToString() ?? "" }, string.Empty); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return new(new KeyVaultResponse(), "Key Not Found"); |                 return new(new KeyVaultResponse(), "Key Not Found"); | ||||||
|             } |             } | ||||||
|  |             catch (VaultSharp.Core.VaultApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound) | ||||||
|  |             { | ||||||
|  |                 return new(new KeyVaultResponse(), "Key Not Found"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try | ||||||
|  |         { | ||||||
|             KeyVaultSecret azureResponse = await azureClient!.GetSecretAsync(secretName, cancellationToken: cancellationToken); |             KeyVaultSecret azureResponse = await azureClient!.GetSecretAsync(secretName, cancellationToken: cancellationToken); | ||||||
|             return new(new KeyVaultResponse { Name = secretName, Value = azureResponse.Value }, string.Empty); |             return new(new KeyVaultResponse { Name = secretName, Value = azureResponse.Value }, string.Empty); | ||||||
|         } |         } | ||||||
|  |         catch (Azure.RequestFailedException ex) when (ex.Status == 404) | ||||||
|  |         { | ||||||
|  |             return new(new KeyVaultResponse(), "Key Not Found"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Updates an existing secret in Azure Key Vault or HashiCorp Vault. If the secret does not exist, an error is returned. |     /// Updates an existing secret in Azure Key Vault or HashiCorp Vault. If the secret does not exist, an error is returned. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="newSecret">The updated secret information.</param> |  | ||||||
|     /// <param name="cancellationToken">The cancellation token to cancel the operation.</param> |  | ||||||
|     /// <returns> |  | ||||||
|     /// A <see cref="Tuple"/> containing the updated <see cref="KeyVaultResponse"/> and an optional error message if the secret was not found. |  | ||||||
|     /// </returns> |  | ||||||
|     public async ValueTask<Tuple<KeyVaultResponse, string>> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken) |     public async ValueTask<Tuple<KeyVaultResponse, string>> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var existingSecret = await this.GetSecretAsync(newSecret.Name, cancellationToken); |         var existingSecret = await this.GetSecretAsync(newSecret.Name, cancellationToken); | ||||||
|         if (existingSecret == null) |         if (!string.IsNullOrEmpty(existingSecret.Item2)) | ||||||
|         { |         { | ||||||
|             return new(new KeyVaultResponse(), "Key Not Found"); |             return new(new KeyVaultResponse(), "Key Not Found"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new(await CreateSecretAsync(newSecret, cancellationToken), string.Empty); |         var updated = await CreateSecretAsync(newSecret, cancellationToken); | ||||||
|  |         return new(updated, string.Empty); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Permanently deletes all versions of a given secret in HashiCorp Vault. | ||||||
|  |     /// Returns a tuple indicating the result status and a message. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="secretName">The secret name/path.</param> | ||||||
|  |     /// <param name="cancellationToken">A cancellation token.</param> | ||||||
|  |     /// <returns> | ||||||
|  |     /// A tuple: | ||||||
|  |     /// - <c>bool?</c>: <c>true</c> if deleted, <c>false</c> if no versions, <c>null</c> if not found. | ||||||
|  |     /// - <c>string</c>: message explaining the result. | ||||||
|  |     /// </returns> | ||||||
|  |     private async Task<(bool? WasDeleted, string Message)> DestroyAllSecretVersionsAsync(string secretName, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         Dictionary<string, object> versions; | ||||||
|  |  | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             var metadata = await hashiClient!.V1.Secrets.KeyValue.V2.ReadSecretMetadataAsync( | ||||||
|  |                 path: secretName, | ||||||
|  |                 mountPoint: hashiOptions!.SecretMount | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             versions = metadata.Data.Versions.Keys.ToDictionary(k => k, _ => (object)0); | ||||||
|  |             if (versions.Count == 0) | ||||||
|  |                 return (false, "Key exists but contains no versions."); | ||||||
|  |         } | ||||||
|  |         catch (VaultApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound) | ||||||
|  |         { | ||||||
|  |             return (null, "Key Not Found."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         using var httpClient = new HttpClient { BaseAddress = new Uri(hashiOptions.Address) }; | ||||||
|  |         var request = new HttpRequestMessage(HttpMethod.Post, $"/v1/{hashiOptions.SecretMount}/destroy/{secretName}") | ||||||
|  |         { | ||||||
|  |             Content = JsonContent.Create(new { versions = versions.Keys.ToArray() }) | ||||||
|  |         }; | ||||||
|  |         request.Headers.Add("X-Vault-Token", hashiOptions.Token); | ||||||
|  |         var response = await httpClient.SendAsync(request, cancellationToken); | ||||||
|  |         response.EnsureSuccessStatusCode(); | ||||||
|  |  | ||||||
|  |         await hashiClient.V1.Secrets.KeyValue.V2.DeleteMetadataAsync( | ||||||
|  |             path: secretName, | ||||||
|  |             mountPoint: hashiOptions.SecretMount | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return (true, "Key Permanently Deleted."); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user