Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
acfbbbe
Adding token
nchapagain001 Dec 17, 2025
ffd7423
Adding UT for AccessToken.
nchapagain001 Dec 18, 2025
cc08bf0
Clean up
nchapagain001 Dec 18, 2025
63af6a1
Idea for endpoint parsing.
nchapagain001 Dec 19, 2025
910b9c9
saving existing work
Jan 10, 2026
f113108
get-token works with tenantId praameter and tid from key vault uri
Jan 10, 2026
c4c452c
Foo
Jan 10, 2026
4121ce8
Working version
Jan 15, 2026
14c5f57
foo
Jan 15, 2026
f56c485
minor fix
Jan 31, 2026
c73e3cb
added get-token and install-cert as two seperate commands.
Feb 5, 2026
98b6a45
Adding UT
Feb 12, 2026
abfb780
Merge branch 'main' into users/nchapagain/Feature_Enable_KV_AccessToken
nchapagain001 Feb 12, 2026
e31317b
FixUT1
Feb 12, 2026
3390b14
minor changes
Feb 12, 2026
a64b83a
Including everything in bootstrap
Feb 13, 2026
e668337
combined with bootstrap
Feb 13, 2026
c940871
Adding doc and UTs.
Feb 13, 2026
5a77651
Fixing doc
Feb 13, 2026
7483aeb
bumping version
Feb 13, 2026
9095fcc
Update src/VirtualClient/VirtualClient.Dependencies/CertificateInstal…
nchapagain001 Feb 13, 2026
de5a99f
Update src/VirtualClient/VirtualClient.Main/InstallCertCommand.cs
nchapagain001 Feb 13, 2026
1bd7cc0
Update src/VirtualClient/VirtualClient.Main/BootstrapPackageCommand.cs
nchapagain001 Feb 13, 2026
7bb5ccb
Update src/VirtualClient/VirtualClient.Core/Identity/AccessTokenCrede…
nchapagain001 Feb 13, 2026
358d1c2
[WIP] Update changes from feedback on Key Vault integration (#638)
Copilot Feb 13, 2026
18fd6cd
[WIP] Address feedback on enabling get-token and bootstrap certificat…
Copilot Feb 13, 2026
e54ecff
Clarify tenant ID requirement in get-token documentation (#640)
Copilot Feb 13, 2026
8c78582
copilot suggestions
Feb 13, 2026
0793d4d
copilot suggestion
Feb 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.55
2.1.56
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace VirtualClient.Actions
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using VirtualClient.Common;
using VirtualClient.Contracts;

[TestFixture]
[Category("Functional")]
public class GetAccessTokenProfileTests
{
private DependencyFixture dependencyFixture;

[OneTimeSetUp]
public void SetupFixture()
{
this.dependencyFixture = new DependencyFixture();
ComponentTypeCache.Instance.LoadComponentTypes(TestDependencies.TestDirectory);
}

[Test]
[TestCase("GET-ACCESS-TOKEN.json", PlatformID.Unix)]
[TestCase("GET-ACCESS-TOKEN.json", PlatformID.Win32NT)]
public void GetAccessTokenProfileParametersAreInlinedCorrectly(string profile, PlatformID platform)
{
this.dependencyFixture.Setup(platform);
using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.dependencyFixture.Dependencies))
{
WorkloadAssert.ParameterReferencesInlined(executor.Profile);
}
}

[Test]
[TestCase("GET-ACCESS-TOKEN.json", PlatformID.Unix)]
[TestCase("GET-ACCESS-TOKEN.json", PlatformID.Win32NT)]
public void GetAccessTokenProfileParametersAreAvailable(string profile, PlatformID platform)
{
this.dependencyFixture.Setup(platform);

var mandatoryParameters = new List<string> { "KeyVaultUri", "TenantId" };
using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.dependencyFixture.Dependencies))
{
Assert.IsEmpty(executor.Profile.Actions);
Assert.AreEqual(1, executor.Profile.Dependencies.Count);

var dependencyBlock = executor.Profile.Dependencies.FirstOrDefault();

foreach (var parameters in mandatoryParameters)
{
Assert.IsTrue(dependencyBlock.Parameters.ContainsKey(parameters));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ public DependencyKeyVaultStore(string storeName, Uri endpointUri, TokenCredentia
/// </summary>
public TokenCredential Credentials { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public void EndpointUtilityThrowsWhenCreatingBlobStoreReferenceForCDNUriIfUriIsV
"https://anystorage.blob.core.windows.net/")]
//
[TestCase(
"https://anystorage.blob.core.windows.net?sv=2022-11-02&ss=b&srt=co&sp=rtf&se=2024-07-02T05:15:29Z&st=2024-07-01T21:15:29Z&spr=https",
"https://anystorage.blob.core.windows.net?sv=2022-11-02&ss=b&srt=co&sp=rtf&se=2024-07-02T05:15:29Z&st=2024-07-01T21:15:29Z&spr=https",
"https://anystorage.blob.core.windows.net/?sv=2022-11-02&ss=b&srt=co&sp=rtf&se=2024-07-02T05:15:29Z&st=2024-07-01T21:15:29Z&spr=https")]
//
[TestCase(
Expand Down Expand Up @@ -230,7 +230,7 @@ public void EndpointUtilityCreatesTheExpectedBlobStoreReferenceForConnectionStri

[Test]
[TestCase("https://any.service.azure.com?miid=307591a4-abb2-4559-af59-b47177d140cf", "https://any.service.azure.com")]
[TestCase("https://any.service.azure.com/?miid=307591a4-abb2-4559-af59-b47177d140cf","https://any.service.azure.com/")]
[TestCase("https://any.service.azure.com/?miid=307591a4-abb2-4559-af59-b47177d140cf", "https://any.service.azure.com/")]
public void EndpointUtilityCreatesTheExpectedBlobStoreReferenceForUrisReferencingManagedIdentities(string uri, string expectedUri)
{
DependencyBlobStore store = EndpointUtility.CreateBlobStoreReference(
Expand Down Expand Up @@ -338,7 +338,7 @@ public void EndpointUtilityCreatesTheExpectedBlobStoreReferenceForConnectionStri
Assert.IsNotNull(store.Credentials);
Assert.IsInstanceOf<ClientCertificateCredential>(store.Credentials);
}

[Test]
[TestCase("https://any.service.azure.com/?cid=307591a4-abb2-4559-af59-b47177d140cf&tid=985bbc17-e3a5-4fec-b0cb-40dbb8bc5959&crti=ABC&crts=any.domain.com", "https://any.service.azure.com/")]
[TestCase("https://any.service.azure.com/?cid=307591a4-abb2-4559-af59-b47177d140cf&tid=985bbc17-e3a5-4fec-b0cb-40dbb8bc5959&crti=ABC CA 01&crts=any.domain.com", "https://any.service.azure.com/")]
Expand Down Expand Up @@ -854,5 +854,37 @@ public void CreateKeyVaultStoreReference_ConnectionString_ThrowsOnInvalid()
"InvalidConnectionString",
this.mockFixture.CertificateManager.Object));
}

[Test]
[TestCase("https://anyvault.vault.azure.net/?cid=123456&tid=654321")]
[TestCase("https://anycontentstorage.blob.core.windows.net?cid=123456&tid=654321")]
[TestCase("https://anypackagestorage.blob.core.windows.net?tid=654321")]
[TestCase("https://anynamespace.servicebus.windows.net?cid=123456&tid=654321")]
[TestCase("https://my-keyvault.vault.azure.net/?;tid=654321")]
public void TryParseMicrosoftEntraTenantIdReference_Uri_WorksAsExpected(string input)
{
// Arrange
Uri uri = new Uri(input);
bool result = EndpointUtility.TryParseMicrosoftEntraTenantIdReference(uri, out string actualTenantId);

// Assert
Assert.True(result);
Assert.AreEqual("654321", actualTenantId);
}

[Test]
[TestCase("https://anycontentstorage.blob.core.windows.net?cid=123456&tenantId=654321")]
[TestCase("https://anypackagestorage.blob.core.windows.net?miid=654321")]
[TestCase("https://my-keyvault.vault.azure.net/;cid=654321")]
public void TryParseMicrosoftEntraTenantIdReference_Uri_ReturnFalseWhenInvalid(string input)
{
// Arrange
Uri uri = new Uri(input);
bool result = EndpointUtility.TryParseMicrosoftEntraTenantIdReference(uri, out string actualTenantId);

// Assert
Assert.IsFalse(result);
Assert.IsNull(actualTenantId);
}
}
}
38 changes: 38 additions & 0 deletions src/VirtualClient/VirtualClient.Core/EndpointUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,26 @@ public static bool TryParseCertificateReference(Uri uri, out string issuer, out
return TryGetCertificateReferenceForUri(queryParameters, out issuer, out subject);
}

/// <summary>
/// Tries to parse the Microsoft Entra reference information from the provided uri. If the uri does not contain the correctly formatted client ID
/// and tenant ID information the method will return false, and keep the two out parameters as null.
/// Ex. https://anystore.blob.core.windows.net?cid={clientId};tid={tenantId}
/// </summary>
/// <param name="uri">The uri to attempt to parse the values from.</param>
/// <param name="tenantId">The tenant ID from the Microsoft Entra reference.</param>
/// <returns>True/False if the method was able to successfully parse both the client ID and the tenant ID from the Microsoft Entra reference.</returns>
public static bool TryParseMicrosoftEntraTenantIdReference(Uri uri, out string tenantId)
{
string queryString = Uri.UnescapeDataString(uri.Query).Trim('?').Replace("&", ",,,");

IDictionary<string, string> queryParameters = TextParsingExtensions.ParseDelimitedValues(queryString)?.ToDictionary(
entry => entry.Key,
entry => entry.Value?.ToString(),
StringComparer.OrdinalIgnoreCase);

return TryGetMicrosoftEntraTenantId(queryParameters, out tenantId);
}

/// <summary>
/// Returns the endpoint by verifying package uri checks.
/// if the endpoint is a package uri without http or https protocols then append the protocol else return the endpoint value.
Expand Down Expand Up @@ -1292,5 +1312,23 @@ private static bool TryGetMicrosoftEntraReferenceForUri(IDictionary<string, stri

return parametersDefined;
}

private static bool TryGetMicrosoftEntraTenantId(IDictionary<string, string> uriParameters, out string tenantId)
{
bool parametersDefined = false;
tenantId = null;

if (uriParameters?.Any() == true)
{
if (uriParameters.TryGetValue(UriParameter.TenantId, out string microsoftEntraTenantId)
&& !string.IsNullOrWhiteSpace(microsoftEntraTenantId))
{
tenantId = microsoftEntraTenantId;
parametersDefined = true;
}
}

return parametersDefined;
}
}
}
1 change: 1 addition & 0 deletions src/VirtualClient/VirtualClient.Core/IKeyVaultManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace VirtualClient
{
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace VirtualClient.Identity
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using VirtualClient.Common.Extensions;

/// <summary>
/// A <see cref="TokenCredential"/> implementation that uses a pre-acquired
/// access token.
/// </summary>
public class AccessTokenCredential : TokenCredential
{
/// <summary>
/// Creates a new instance of the <see cref="AccessTokenCredential"/> class.
/// </summary>
/// <param name="token">
/// The access token string to use for authentication.
/// </param>
public AccessTokenCredential(string token)
{
token.ThrowIfNull(nameof(token));
this.AccessToken = new AccessToken(token, DateTimeOffset.UtcNow.AddHours(1));
}

/// <summary>
/// The access token to use for authentication.
/// </summary>
public AccessToken AccessToken { get; }

/// <summary>
/// Gets an access token using the underlying credentials.
/// </summary>
/// <param name="requestContext">Context information used when getting the access token.</param>
/// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
/// <returns>
/// An access token that can be used to authenticate with Azure resources.
/// </returns>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return this.AccessToken;
}

/// <summary>
/// Gets an access token using the underlying credentials.
/// </summary>
/// <param name="requestContext">Context information used when getting the access token.</param>
/// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
/// <returns>
/// An access token that can be used to authenticate with Azure resources.
/// </returns>
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new ValueTask<AccessToken>(this.AccessToken);
}
}
}
1 change: 0 additions & 1 deletion src/VirtualClient/VirtualClient.Core/KeyVaultManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace VirtualClient
using Azure.Security.KeyVault.Secrets;
using Polly;
using VirtualClient.Common.Extensions;
using VirtualClient.Contracts;

/// <summary>
/// Provides methods for retrieving secrets, keys, and certificates from an Azure Key Vault.
Expand Down
Loading
Loading