Skip to content

Commit 5815fc9

Browse files
imadityaaAditya Abhishek
andauthored
Add support for keyVault integration in virtual client to resolve secrets, certificates and keys (#513)
* adding KeyVault Capability * resolve enum * 2nd commit * working solution * add unit tests * more conflicts * more conflicts part 2 * more conflicts part 3 * more conflicts part 4 * remove extra changes * add doc * resolve comments * nit spaces * nit * resolve doc build * resolve comments * remove keyvaultdescriptor * cleanup --------- Co-authored-by: Aditya Abhishek <[email protected]>
1 parent b2b0984 commit 5815fc9

19 files changed

+1373
-2
lines changed

src/VirtualClient/Module.props

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131

3232
<!-- Azure.Messaging.EventHubs -->
3333
<Azure_Messaging_EventHubs_PackageVersion>5.11.5</Azure_Messaging_EventHubs_PackageVersion>
34+
35+
<!-- Azure.Security.KeyVault.Certificates -->
36+
<Azure_Security_KeyVault_Certificates_PackageVersion>4.7.0</Azure_Security_KeyVault_Certificates_PackageVersion>
37+
38+
<!-- Azure.Security.KeyVault.Keys -->
39+
<Azure_Security_KeyVault_Keys_PackageVersion>4.7.0</Azure_Security_KeyVault_Keys_PackageVersion>
40+
41+
<!-- Azure.Security.KeyVault.Secrets -->
42+
<Azure_Security_KeyVault_Secrets_PackageVersion>4.7.0</Azure_Security_KeyVault_Secrets_PackageVersion>
3443

3544
<!-- Azure.Storage.Blobs -->
3645
<Azure_Storage_Blobs_PackageVersion>12.18.0</Azure_Storage_Blobs_PackageVersion>
@@ -145,7 +154,7 @@
145154

146155
<!-- System.ServiceProcess.ServiceController -->
147156
<System_ServiceProcess_ServiceController_PackageVersion>9.0.3</System_ServiceProcess_ServiceController_PackageVersion>
148-
157+
149158
<!-- YamlDotNet -->
150159
<YamlDotNet_PackageVersion>15.1.1</YamlDotNet_PackageVersion>
151160

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace VirtualClient
5+
{
6+
using System;
7+
using Azure.Core;
8+
using VirtualClient.Common.Extensions;
9+
10+
/// <summary>
11+
/// Represents an Azure Key vault Namespace store
12+
/// </summary>
13+
public class DependencyKeyVaultStore : DependencyStore
14+
{
15+
/// <summary>
16+
/// Initializes an instance of the <see cref="DependencyKeyVaultStore"/> class.
17+
/// </summary>
18+
/// <param name="storeName">The name of the KeyVault store (e.g. KeyVault).</param>
19+
/// <param name="endpointUri">The URI/SAS for the target Key Vault.</param>
20+
public DependencyKeyVaultStore(string storeName, Uri endpointUri)
21+
: base(storeName, DependencyStore.StoreTypeAzureKeyVault)
22+
{
23+
endpointUri.ThrowIfNull(nameof(endpointUri));
24+
this.EndpointUri = endpointUri;
25+
this.KeyVaultNameSpace = endpointUri.Host;
26+
}
27+
28+
/// <summary>
29+
/// Initializes an instance of the <see cref="DependencyKeyVaultStore"/> class.
30+
/// </summary>
31+
/// <param name="storeName">The name of the KeyVault store (e.g. KeyVault).</param>
32+
/// <param name="endpointUri">The URI/SAS for the target Key Vault.</param>
33+
/// <param name="credentials">An identity token credential to use for authentication against the Key Vault.</param>
34+
public DependencyKeyVaultStore(string storeName, Uri endpointUri, TokenCredential credentials)
35+
: this(storeName, endpointUri)
36+
{
37+
credentials.ThrowIfNull(nameof(credentials));
38+
this.Credentials = credentials;
39+
}
40+
41+
/// <summary>
42+
/// The URI/SAS for the target Key Vault.
43+
/// </summary>
44+
public Uri EndpointUri { get; }
45+
46+
/// <summary>
47+
/// The Key Vault namespace.
48+
/// </summary>
49+
public string KeyVaultNameSpace { get; }
50+
51+
/// <summary>
52+
/// An identity token credential to use for authentication against the Key vault.
53+
/// </summary>
54+
public TokenCredential Credentials { get; }
55+
}
56+
}

src/VirtualClient/VirtualClient.Contracts/DependencyStore.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ public class DependencyStore
1616
/// </summary>
1717
public const string Content = nameof(DependencyStore.Content);
1818

19+
/// <summary>
20+
/// KeyVault store name.
21+
/// </summary>
22+
public const string KeyVault = nameof(DependencyStore.KeyVault);
23+
1924
/// <summary>
2025
/// Packages store name.
2126
/// </summary>
@@ -41,6 +46,11 @@ public class DependencyStore
4146
/// </summary>
4247
public const string StoreTypeFileSystem = "FileSystem";
4348

49+
/// <summary>
50+
/// Store Type = AzureKeyVault
51+
/// </summary>
52+
public const string StoreTypeAzureKeyVault = "AzureKeyVault";
53+
4454
/// <summary>
4555
/// Store Type = ProxyApi
4656
/// </summary>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using NUnit.Framework;
6+
using VirtualClient.Contracts;
7+
using VirtualClient;
8+
9+
namespace VirtualClient.Core.UnitTests
10+
{
11+
[TestFixture]
12+
[Category("Unit")]
13+
public class DependencyFactoryTests
14+
{
15+
[Test]
16+
public void CreateKeyVaultManager_ThrowsIfDependencyStoreIsNotKeyVaultStore()
17+
{
18+
// Arrange: Use a base DependencyBlobStore, not a DependencyKeyVaultStore
19+
var store = new DependencyBlobStore("KeyVault", new Uri("https://myblob.azure.net/"));
20+
21+
// Act & Assert
22+
var ex = Assert.Throws<DependencyException>(() => DependencyFactory.CreateKeyVaultManager(store));
23+
StringAssert.Contains("Required Key Vault information not provided", ex.Message);
24+
}
25+
26+
[Test]
27+
public void CreateKeyVaultManager_ReturnsKeyVaultManagerForValidStore()
28+
{
29+
// Arrange: Use a valid DependencyKeyVaultStore
30+
var keyVaultStore = new DependencyKeyVaultStore("KeyVault", new Uri("https://myvault.vault.azure.net/"));
31+
32+
// Act
33+
var manager = DependencyFactory.CreateKeyVaultManager(keyVaultStore);
34+
35+
// Assert
36+
Assert.IsNotNull(manager);
37+
Assert.IsInstanceOf<IKeyVaultManager>(manager);
38+
}
39+
40+
[Test]
41+
public void CreateKeyVaultManager_ThrowsIfNullPassed()
42+
{
43+
// Act & Assert
44+
Assert.Throws<DependencyException>(() => DependencyFactory.CreateKeyVaultManager(null));
45+
}
46+
}
47+
}

src/VirtualClient/VirtualClient.Core.UnitTests/EndpointUtilityTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace VirtualClient
1212
using Moq;
1313
using NUnit.Framework;
1414
using VirtualClient.Contracts;
15+
using VirtualClient.Identity;
1516
using VirtualClient.TestExtensions;
1617

1718
[TestFixture]
@@ -795,5 +796,63 @@ public void EndpointUtilityThrowsWhenCreatingAProfileReferenceIfTheValueProvided
795796
invalidEndpoint,
796797
this.mockFixture.CertificateManager.Object));
797798
}
799+
800+
[Test]
801+
[TestCase(
802+
"https://my-keyvault.vault.azure.net/?cid=985bbc17-e3a5-4fec-b0cb-40dbb8bc5959&tid=307591a4-abb2-4559-af59-b47177d140cf&crtt=1234567",
803+
"https://my-keyvault.vault.azure.net/")]
804+
[TestCase(
805+
"Endpoint=https://my-keyvault.vault.azure.net/;CertificateThumbprint=1234567;ClientId=985bbc17;TenantId=307591a4",
806+
"https://my-keyvault.vault.azure.net/")]
807+
public void EndpointUtility_CreateKeyVaultStoreReference_Entra(string connectionString, string expectedUri)
808+
{
809+
// Setup: A matching certificate is found in the local store.
810+
this.mockFixture.CertificateManager.Setup(mgr => mgr.GetCertificateFromStoreAsync("1234567", It.IsAny<IEnumerable<StoreLocation>>(), It.IsAny<StoreName>()))
811+
.ReturnsAsync(this.mockFixture.Create<X509Certificate2>());
812+
813+
var store = EndpointUtility.CreateKeyVaultStoreReference(
814+
DependencyStore.KeyVault,
815+
connectionString,
816+
this.mockFixture.CertificateManager.Object);
817+
818+
Assert.IsNotNull(store);
819+
Assert.AreEqual(DependencyStore.KeyVault, store.StoreName);
820+
Assert.AreEqual(DependencyStore.StoreTypeAzureKeyVault, store.StoreType);
821+
Assert.AreEqual(new Uri(expectedUri).ToString(), store.EndpointUri.ToString());
822+
Assert.IsNotNull(store.Credentials);
823+
Assert.IsInstanceOf<ClientCertificateCredential>(store.Credentials);
824+
}
825+
826+
[Test]
827+
[TestCase(
828+
"Endpoint=https://my-keyvault.vault.azure.net/;ManagedIdentityId=307591a4-abb2-4559-af59-b47177d140cf",
829+
"https://my-keyvault.vault.azure.net/")]
830+
[TestCase(
831+
"https://my-keyvault.vault.azure.net/?miid=307591a4-abb2-4559-af59-b47177d140cf",
832+
"https://my-keyvault.vault.azure.net/")]
833+
public void EndpointUtility_CreateKeyVaultStoreReference_Miid(string connectionString, string expectedUri)
834+
{
835+
var store = EndpointUtility.CreateKeyVaultStoreReference(
836+
DependencyStore.KeyVault,
837+
connectionString,
838+
this.mockFixture.CertificateManager.Object);
839+
840+
Assert.IsNotNull(store);
841+
Assert.AreEqual(DependencyStore.KeyVault, store.StoreName);
842+
Assert.AreEqual(DependencyStore.StoreTypeAzureKeyVault, store.StoreType);
843+
Assert.AreEqual(new Uri(expectedUri).ToString(), store.EndpointUri.ToString());
844+
Assert.IsNotNull(store.Credentials);
845+
Assert.IsInstanceOf<ManagedIdentityCredential>(store.Credentials);
846+
}
847+
848+
[Test]
849+
public void CreateKeyVaultStoreReference_ConnectionString_ThrowsOnInvalid()
850+
{
851+
Assert.Throws<SchemaException>(() =>
852+
EndpointUtility.CreateKeyVaultStoreReference(
853+
DependencyStore.KeyVault,
854+
"InvalidConnectionString",
855+
this.mockFixture.CertificateManager.Object));
856+
}
798857
}
799858
}

0 commit comments

Comments
 (0)