Skip to content

Commit ae7cd59

Browse files
authored
Managed Identity and Service Principal Support (#492)
# Overview - Added MSAL Managed Identity and Service Principal Token Providers to Microsoft.Artifacts.Authentication Library. - Created new endpoint `ARTIFACTS_CREDENTIALPROVIDER_FEED_ENDPOINTS` environment variable with new json schema for MI/SP required fields. - Updated VstsBuildTaskServiceEndpointCredentialProvider to call Microsoft.Artifacts.Authentication for MI/SP token providers. - Reverted #485 Changes to use system.text.json for de/serialization everywhere except for the `VSS_NUGET_EXTERNAL_FEED_ENDPOINTS` environment variable. ## Design Decisions - Intentionally not supporting SP secrets authentication to promote security best practices. - The new environment variable name and json schema were created instead of reusing or extending the existing `VSS_NUGET_EXTERNAL_FEED_ENDPOINTS` to reduce password usage and clarify the environment variable will be available to our other credproviders such as the [artifacs-keyring](https://github.com/microsoft/artifacts-keyring) not just NuGet. ## Environment Variable `ARTIFACTS_CREDENTIALPROVIDER_FEED_ENDPOINTS` ```javascript {"endpointCredentials": [{"endpoint":"http://example.index.json", "clientId":"required", "clientCertificateSubjectName":"optional", "clientCertificateFilePath":"optional"}]} ``` - `endpoint`: required. Feed url to authenticate against. - `clientId`: required for both MI/SP. For user assigned managed identities enter the Entra client id. For system assigned variables set the value to `system`. - `clientCertificateSubjectName`: Subject Name of the certificate located in the My/ CurrentUser or LocalMachine certificate store. Optional field. Only used by SP authentication. - `clientCertificateFilePath`: File path location of the certificate on the machine. Optional field. Only used by SP authentication. Will throw error if both `clientCertificateSubjectName` or `clientCertificateFilePath` are specified.
1 parent 74fe273 commit ae7cd59

26 files changed

+1269
-369
lines changed

CredentialProvider.Microsoft.Tests/CredentialProvider.Microsoft.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<PropertyGroup>
55
<LangVersion>latest</LangVersion>
66
<IsPackable>false</IsPackable>
7+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
8+
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
79
</PropertyGroup>
810

911
<ItemGroup>
@@ -18,5 +20,4 @@
1820
<ProjectReference Include="..\CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj" />
1921
<ProjectReference Include="..\src\Authentication\Microsoft.Artifacts.Authentication.csproj" />
2022
</ItemGroup>
21-
2223
</Project>

CredentialProvider.Microsoft.Tests/CredentialProviders/Vsts/AuthUtilTests.cs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,27 @@ public void TestCleanup()
4545
}
4646

4747
[TestMethod]
48-
public async Task GetAadAuthorityUri_WithoutAuthenticateHeaders_ReturnsCorrectAuthority()
48+
public async Task GetAuthorizationInfoAsync_WithoutAuthenticateHeaders_ReturnsCorrectAuthority()
4949
{
5050
var requestUri = new Uri("https://example.pkgs.visualstudio.com/_packaging/feed/nuget/v3/index.json");
5151

52-
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
52+
var authInfo = await authUtil.GetAuthorizationInfoAsync(requestUri, cancellationToken);
5353

54-
authorityUri.Should().Be(organizationsAuthority);
54+
authInfo.EntraAuthorityUri.Should().Be(organizationsAuthority);
5555
}
5656

5757
[TestMethod]
58-
public async Task GetAadAuthorityUri_WithoutAuthenticateHeadersAndPpe_ReturnsCorrectAuthority()
58+
public async Task GetAuthorizationInfoAsync_WithoutAuthenticateHeadersAndPpe_ReturnsCorrectAuthority()
5959
{
6060
var requestUri = new Uri("https://example.pkgs.vsts.me/_packaging/feed/nuget/v3/index.json");
6161

62-
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
62+
var authInfo = await authUtil.GetAuthorizationInfoAsync(requestUri, cancellationToken);
6363

64-
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
64+
authInfo.EntraAuthorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
6565
}
6666

6767
[TestMethod]
68-
public async Task GetAadAuthorityUri_WithoutAuthenticateHeadersAndPpeAndPpeOverride_ReturnsCorrectAuthority()
68+
public async Task GetAuthorizationInfoAsync_WithoutAuthenticateHeadersAndPpeAndPpeOverride_ReturnsCorrectAuthority()
6969
{
7070
var ppeUris = new[]
7171
{
@@ -79,36 +79,49 @@ public async Task GetAadAuthorityUri_WithoutAuthenticateHeadersAndPpeAndPpeOverr
7979

8080
foreach (var ppeUri in ppeUris)
8181
{
82-
var authorityUri = await authUtil.GetAadAuthorityUriAsync(ppeUri, cancellationToken);
82+
var authInfo = await authUtil.GetAuthorizationInfoAsync(ppeUri, cancellationToken);
8383

84-
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
84+
authInfo.EntraAuthorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
8585
}
8686
}
8787

8888
[TestMethod]
89-
public async Task GetAadAuthorityUri_WithAuthenticateHeaders_ReturnsCorrectAuthority()
89+
public async Task GetAuthorizationInfoAsync_WithAuthenticateHeaders_ReturnsCorrectAuthority()
9090
{
9191
var requestUri = new Uri("https://example.pkgs.visualstudio.com/_packaging/feed/nuget/v3/index.json");
9292

9393
MockAadAuthorityHeaders(testAuthority);
9494

95-
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
95+
var authInfo = await authUtil.GetAuthorizationInfoAsync(requestUri, cancellationToken);
9696

97-
authorityUri.Should().Be(testAuthority);
97+
authInfo.EntraAuthorityUri.Should().Be(testAuthority);
9898
}
9999

100100
[TestMethod]
101-
public async Task GetAadAuthorityUri_WithAuthenticateHeadersAndEnvironmentOverride_ReturnsOverrideAuthority()
101+
public async Task GetAuthorizationInfoAsync_WithAuthenticateHeadersAndEnvironmentOverride_ReturnsOverrideAuthority()
102102
{
103103
var requestUri = new Uri("https://example.pkgs.visualstudio.com/_packaging/feed/nuget/v3/index.json");
104104
var overrideAuthority = new Uri("https://override.aad.authority.com");
105105

106106
MockAadAuthorityHeaders(testAuthority);
107107

108108
Environment.SetEnvironmentVariable(EnvUtil.MsalAuthorityEnvVar, overrideAuthority.ToString());
109-
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
109+
var authInfo = await authUtil.GetAuthorizationInfoAsync(requestUri, cancellationToken);
110110

111-
authorityUri.Should().Be(overrideAuthority);
111+
authInfo.EntraAuthorityUri.Should().Be(overrideAuthority);
112+
}
113+
114+
[TestMethod]
115+
public async Task GetAuthorizationInfoAsync_WithTenantHeaders_ReturnsCorrectTenantId()
116+
{
117+
var requestUri = new Uri("https://example.pkgs.visualstudio.com/_packaging/feed/nuget/v3/index.json");
118+
119+
var testTenant = Guid.NewGuid();
120+
MockVssResourceTenantHeader(testTenant);
121+
122+
var authInfo = await authUtil.GetAuthorizationInfoAsync(requestUri, cancellationToken);
123+
124+
authInfo.EntraTenantId.Should().Be(testTenant.ToString());
112125
}
113126

114127
[TestMethod]
@@ -203,9 +216,9 @@ private void MockResponseHeaders(string key, string value)
203216
authUtil.HttpResponseHeaders.Add(key, value);
204217
}
205218

206-
private void MockVssResourceTenantHeader()
219+
private void MockVssResourceTenantHeader(Guid? guid = null)
207220
{
208-
MockResponseHeaders(AuthUtil.VssResourceTenant, Guid.NewGuid().ToString());
221+
MockResponseHeaders(AuthUtil.VssResourceTenant,(guid ?? Guid.NewGuid()).ToString());
209222
}
210223

211224
private void MockVssAuthorizationEndpointHeader()

CredentialProvider.Microsoft.Tests/CredentialProviders/Vsts/VstsCredentialProviderTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ public void TestInitialize()
5353

5454
mockAuthUtil = new Mock<IAuthUtil>();
5555
mockAuthUtil
56-
.Setup(x => x.GetAadAuthorityUriAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>()))
57-
.Returns(Task.FromResult(testAuthority));
56+
.Setup(x => x.GetAuthorizationInfoAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>()))
57+
.Returns(Task.FromResult(new AuthorizationInfo() { EntraAuthorityUri = testAuthority }));
5858

5959
vstsCredentialProvider = new VstsCredentialProvider(
6060
mockLogger.Object,

0 commit comments

Comments
 (0)