Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5498968
Initial commit. 2 TODOs
Robbie-Microsoft Aug 6, 2025
e04e408
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft Aug 6, 2025
4e096b7
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft Aug 6, 2025
6bc2164
Implemented CSR generator
Robbie-Microsoft Aug 6, 2025
762ccdf
first pass at improved unit tests
Robbie-Microsoft Aug 6, 2025
4ea6c09
Finished improving unit tests
Robbie-Microsoft Aug 6, 2025
009f948
Updates to CUID
Robbie-Microsoft Aug 7, 2025
21d4ef3
Unit test improvements
Robbie-Microsoft Aug 7, 2025
cd013a3
Implemented Feedback
Robbie-Microsoft Aug 7, 2025
480ae9e
renamed file
Robbie-Microsoft Aug 7, 2025
0aa8692
small improvement
Robbie-Microsoft Aug 8, 2025
621c566
added missing awaitor for async method
Robbie-Microsoft Aug 8, 2025
068461b
Fixed bugs discovered from unit testing in child branch
Robbie-Microsoft Aug 8, 2025
2034b25
undid changes to .proj
Robbie-Microsoft Aug 8, 2025
2b7486a
undid change to global.json
Robbie-Microsoft Aug 8, 2025
189ff9e
added missing sets
Robbie-Microsoft Aug 8, 2025
92b325f
Inplemented some feedback
Robbie-Microsoft Aug 11, 2025
067c83c
Implemented some feedback
Robbie-Microsoft Aug 14, 2025
f7d6f88
PKCS1 -> Pss padding
Robbie-Microsoft Aug 15, 2025
74e8e60
re-used imports
Robbie-Microsoft Aug 15, 2025
152f396
Implemented feedback
Robbie-Microsoft Aug 15, 2025
d46c853
Changes from manual testing.
Robbie-Microsoft Aug 19, 2025
3f75e3a
ImdsV2: Reworked Custom ASN1 Encoder to use System.Formats.Asn1 Nuget…
Robbie-Microsoft Aug 22, 2025
253993d
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft Aug 22, 2025
3481c39
Implemented feedback
Robbie-Microsoft Aug 25, 2025
92158bb
Small rework due to spec changes
Robbie-Microsoft Aug 25, 2025
729a56a
Additional rework due to spec changes
Robbie-Microsoft Aug 25, 2025
3027392
Implemented feedback
Robbie-Microsoft Aug 25, 2025
3c3dcdf
Removed null check on vmId. Created CuidInfo.IsNullOrEmpty
Robbie-Microsoft Aug 25, 2025
f51cdf9
Implemented feedback
Robbie-Microsoft Aug 26, 2025
5e7ab07
Updated min version of imds, spec has incorrect info
Robbie-Microsoft Aug 26, 2025
362b407
Updated a comment
Robbie-Microsoft Aug 27, 2025
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
41 changes: 41 additions & 0 deletions src/client/Microsoft.Identity.Client/ManagedIdentity/CsrRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Microsoft.Identity.Client.ManagedIdentity
{
internal class CsrRequest
{
public string Pem { get; }

public CsrRequest(string pem)
{
Pem = pem ?? throw new ArgumentNullException(nameof(pem));
}

/// <summary>
/// Generates a CSR for the given client, tenant, and CUID info.
/// </summary>
/// <param name="clientId">Managed Identity client_id.</param>
/// <param name="tenantId">AAD tenant_id.</param>
/// <param name="cuid">CuidInfo object containing VMID and VMSSID.</param>
/// <returns>CsrRequest containing the PEM CSR.</returns>
public static CsrRequest Generate(string clientId, string tenantId, CuidInfo cuid)
{
if (string.IsNullOrWhiteSpace(clientId))
throw new ArgumentException("clientId must not be null or empty.", nameof(clientId));
if (string.IsNullOrWhiteSpace(tenantId))
throw new ArgumentException("tenantId must not be null or empty.", nameof(tenantId));
if (cuid == null)
throw new ArgumentNullException(nameof(cuid));
if (string.IsNullOrWhiteSpace(cuid.Vmid))
throw new ArgumentException("cuid.Vmid must not be null or empty.", nameof(cuid.Vmid));
if (string.IsNullOrWhiteSpace(cuid.Vmssid))
throw new ArgumentException("cuid.Vmssid must not be null or empty.", nameof(cuid.Vmssid));

// TODO: Implement the actual CSR generation logic.
return new CsrRequest("pem");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#if SUPPORTS_SYSTEM_TEXT_JSON
using JsonProperty = System.Text.Json.Serialization.JsonPropertyNameAttribute;
#else
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Json;
#endif

namespace Microsoft.Identity.Client.ManagedIdentity
{
/// <summary>
/// Represents the response for a Managed Identity CSR request.
/// </summary>
internal class CsrRequestResponse
{
[JsonProperty("client_id")]
public string ClientId { get; }

[JsonProperty("tenant_id")]
public string TenantId { get; }

[JsonProperty("client_credential")]
public string ClientCredential { get; }

[JsonProperty("regional_token_url")]
public string RegionalTokenUrl { get; }

[JsonProperty("expires_in")]
public int ExpiresIn { get; }

[JsonProperty("refresh_in")]
public int RefreshIn { get; }

public CsrRequestResponse() { }

public static bool ValidateCsrRequestResponse(CsrRequestResponse csrRequestResponse)
{
if (string.IsNullOrEmpty(csrRequestResponse.ClientId) ||
string.IsNullOrEmpty(csrRequestResponse.TenantId) ||
string.IsNullOrEmpty(csrRequestResponse.ClientCredential) ||
string.IsNullOrEmpty(csrRequestResponse.RegionalTokenUrl) ||
csrRequestResponse.ExpiresIn <= 0 ||
csrRequestResponse.RefreshIn <= 0)
{
return false;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http;
Expand All @@ -16,6 +17,7 @@ namespace Microsoft.Identity.Client.ManagedIdentity
internal class ImdsV2ManagedIdentitySource : AbstractManagedIdentity
{
private const string CsrMetadataPath = "/metadata/identity/getPlatformMetadata";
private const string CsrRequestPath = "/metadata/identity/issuecredential";

public static async Task<CsrMetadata> GetCsrMetadataAsync(
RequestContext requestContext,
Expand All @@ -29,7 +31,7 @@ public static async Task<CsrMetadata> GetCsrMetadataAsync(
requestContext.Logger);
if (userAssignedIdQueryParam != null)
{
queryParams += $"{userAssignedIdQueryParam.Value.Key}={userAssignedIdQueryParam.Value.Value}";
queryParams += $"&{userAssignedIdQueryParam.Value.Key}={userAssignedIdQueryParam.Value.Value}";
}

var headers = new Dictionary<string, string>
Expand All @@ -41,7 +43,6 @@ public static async Task<CsrMetadata> GetCsrMetadataAsync(
IRetryPolicyFactory retryPolicyFactory = requestContext.ServiceBundle.Config.RetryPolicyFactory;
IRetryPolicy retryPolicy = retryPolicyFactory.GetRetryPolicy(RequestType.CsrMetadataProbe);

// CSR metadata GET request
HttpResponse response = null;

try
Expand All @@ -50,7 +51,7 @@ public static async Task<CsrMetadata> GetCsrMetadataAsync(
ImdsManagedIdentitySource.GetValidatedEndpoint(requestContext.Logger, CsrMetadataPath, queryParams),
headers,
body: null,
method: System.Net.Http.HttpMethod.Get,
method: HttpMethod.Get,
logger: requestContext.Logger,
doNotThrow: false,
mtlsCertificate: null,
Expand Down Expand Up @@ -194,8 +195,75 @@ public static AbstractManagedIdentity Create(RequestContext requestContext)
internal ImdsV2ManagedIdentitySource(RequestContext requestContext) :
base(requestContext, ManagedIdentitySource.ImdsV2) { }

private async Task<CsrRequestResponse> ExecuteCsrRequestAsync(
RequestContext requestContext,
string queryParams,
string pem)
{
var headers = new Dictionary<string, string>
{
{ "Metadata", "true" },
{ "x-ms-client-request-id", requestContext.CorrelationId.ToString() }
};

IRetryPolicyFactory retryPolicyFactory = requestContext.ServiceBundle.Config.RetryPolicyFactory;
IRetryPolicy retryPolicy = retryPolicyFactory.GetRetryPolicy(RequestType.Imds);

HttpResponse response = null;

try
{
response = await requestContext.ServiceBundle.HttpManager.SendRequestAsync(
ImdsManagedIdentitySource.GetValidatedEndpoint(requestContext.Logger, CsrRequestPath, queryParams),
headers,
body: new StringContent($"{{\"pem\":\"{pem}\"}}", System.Text.Encoding.UTF8, "application/json"),
method: HttpMethod.Post,
logger: requestContext.Logger,
doNotThrow: false,
mtlsCertificate: null,
validateServerCertificate: null,
cancellationToken: requestContext.UserCancellationToken,
retryPolicy: retryPolicy)
.ConfigureAwait(false);
}
catch (Exception ex)
{
throw MsalServiceExceptionFactory.CreateManagedIdentityException(
MsalError.ManagedIdentityRequestFailed,
$"[ImdsV2] ImdsV2ManagedIdentitySource.ExecuteCsrRequest failed.",
ex,
ManagedIdentitySource.ImdsV2,
(int)response.StatusCode);
}

var csrRequestResponse = JsonHelper.DeserializeFromJson<CsrRequestResponse>(response.Body);
if (!CsrRequestResponse.ValidateCsrRequestResponse(csrRequestResponse))
{
throw MsalServiceExceptionFactory.CreateManagedIdentityException(
MsalError.ManagedIdentityRequestFailed,
$"[ImdsV2] ImdsV2ManagedIdentitySource.GetCsrMetadataAsync failed because the CsrMetadata response is invalid. Status code: {response.StatusCode} Body: {response.Body}",
null,
ManagedIdentitySource.ImdsV2,
(int)response.StatusCode);
}

return csrRequestResponse;
}

protected override ManagedIdentityRequest CreateRequest(string resource)
{
var csrMetadata = GetCsrMetadataAsync(_requestContext, false).GetAwaiter().GetResult();
var csrRequest = CsrRequest.Generate(csrMetadata.ClientId, csrMetadata.TenantId, csrMetadata.Cuid);

var queryParams = $"cid={csrMetadata.Cuid}";
if (_requestContext.ServiceBundle.Config.ManagedIdentityId.UserAssignedId != null)
{
queryParams += $"&uaid{_requestContext.ServiceBundle.Config.ManagedIdentityId.UserAssignedId}";
}
queryParams += $"&api-version={ImdsManagedIdentitySource.ImdsApiVersion}";

var csrRequestResponse = ExecuteCsrRequestAsync(_requestContext, queryParams, csrRequest.Pem);

throw new NotImplementedException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,7 @@ public async Task GetCsrMetadataAsyncFails404WhichIsNonRetriableAndRetryPolicyIs
Assert.AreEqual(ManagedIdentitySource.DefaultToImds, miSource);
}
}

// TODO: Create CSR generation unit tests
}
}