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