diff --git a/src/ModularPipelines.Azure/AzureKeyVault.cs b/src/ModularPipelines.Azure/AzureKeyVault.cs index fbc975c5e0..0b71ac657d 100644 --- a/src/ModularPipelines.Azure/AzureKeyVault.cs +++ b/src/ModularPipelines.Azure/AzureKeyVault.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using Azure.Core; using Azure.Security.KeyVault.Certificates; using Azure.Security.KeyVault.Keys; @@ -5,20 +6,46 @@ namespace ModularPipelines.Azure; +/// +/// Provides access to Azure Key Vault clients with caching. +/// +/// +/// +/// Azure SDK clients (SecretClient, CertificateClient, KeyClient) are thread-safe and designed +/// to be long-lived and reused. This class caches clients by vault URI to avoid the overhead +/// of creating new clients on every call. +/// +/// +/// Important: Clients are cached by vault URI only. Within a scope, the first +/// TokenCredential used for a vault URI will be used for all subsequent requests to that vault. +/// This is intentional since AzureKeyVault is registered as Scoped and typically the same +/// credential is used throughout a pipeline execution scope. +/// +/// internal class AzureKeyVault : IAzureKeyVault { + // Cache by vault URI only. Within a scope, the same credential is typically used. + // TokenCredential lacks value equality, so including it in the key would cause cache misses + // for logically identical credentials (e.g., two DefaultAzureCredential instances). + private readonly ConcurrentDictionary _secretClients = new(); + private readonly ConcurrentDictionary _certificateClients = new(); + private readonly ConcurrentDictionary _keyClients = new(); + public SecretClient GetSecretClient(Uri vaultUri, TokenCredential tokenCredential) { - return new SecretClient(vaultUri, tokenCredential); + var key = vaultUri.ToString(); + return _secretClients.GetOrAdd(key, _ => new SecretClient(vaultUri, tokenCredential)); } public CertificateClient GetCertificateClient(Uri vaultUri, TokenCredential tokenCredential) { - return new CertificateClient(vaultUri, tokenCredential); + var key = vaultUri.ToString(); + return _certificateClients.GetOrAdd(key, _ => new CertificateClient(vaultUri, tokenCredential)); } public KeyClient GetKeyClient(Uri vaultUri, TokenCredential tokenCredential) { - return new KeyClient(vaultUri, tokenCredential); + var key = vaultUri.ToString(); + return _keyClients.GetOrAdd(key, _ => new KeyClient(vaultUri, tokenCredential)); } } \ No newline at end of file diff --git a/src/ModularPipelines/Context/Hasher.cs b/src/ModularPipelines/Context/Hasher.cs index e99d3f3c3a..325b6b7244 100644 --- a/src/ModularPipelines/Context/Hasher.cs +++ b/src/ModularPipelines/Context/Hasher.cs @@ -3,6 +3,13 @@ namespace ModularPipelines.Context; +/// +/// Provides hashing operations using various algorithms. +/// +/// +/// Uses the static HashData methods available in .NET 5+ which are thread-safe +/// and don't require disposal, avoiding resource leaks. +/// internal class Hasher : IHasher { private readonly IHex _hex; @@ -16,36 +23,31 @@ public Hasher(IHex hex, IBase64 base64) public string Sha1(string input, HashType hashType = HashType.Hex) { - return ComputeHash(SHA1.Create(), input, hashType); + var bytes = System.Security.Cryptography.SHA1.HashData(Encoding.UTF8.GetBytes(input)); + return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); } public string Sha256(string input, HashType hashType = HashType.Hex) { - return ComputeHash(SHA256.Create(), input, hashType); + var bytes = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(input)); + return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); } public string Sha384(string input, HashType hashType = HashType.Hex) { - return ComputeHash(SHA384.Create(), input, hashType); + var bytes = System.Security.Cryptography.SHA384.HashData(Encoding.UTF8.GetBytes(input)); + return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); } public string Sha512(string input, HashType hashType = HashType.Hex) { - return ComputeHash(SHA512.Create(), input, hashType); + var bytes = System.Security.Cryptography.SHA512.HashData(Encoding.UTF8.GetBytes(input)); + return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); } public string Md5(string input, HashType hashType = HashType.Hex) { - return ComputeHash(MD5.Create(), input, hashType); - } - - private string ComputeHash(HashAlgorithm hashAlgorithm, string input, HashType hashType) - { - using (hashAlgorithm) - { - var bytes = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); - - return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); - } + var bytes = System.Security.Cryptography.MD5.HashData(Encoding.UTF8.GetBytes(input)); + return hashType == HashType.Hex ? _hex.ToHex(bytes) : _base64.ToBase64String(bytes); } } \ No newline at end of file