forked from dotnet/arcade-services
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add credential support to PCS API client (dotnet#3610)
Co-authored-by: Djuradj Kurepa <[email protected]>
- Loading branch information
Showing
6 changed files
with
171 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
src/ProductConstructionService/ProductConstructionService.Client/PcsApiCredential.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Azure.Core; | ||
using Azure.Identity; | ||
|
||
namespace ProductConstructionService.Client | ||
{ | ||
/// <summary> | ||
/// A credential that first tries a user-based browser auth flow then falls back to a managed identity-based flow. | ||
/// </summary> | ||
internal class PcsApiCredential : TokenCredential | ||
{ | ||
private const string TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47"; | ||
|
||
private static readonly Dictionary<string, string> EntraAppIds = new Dictionary<string, string> | ||
{ | ||
[ProductConstructionServiceApi.StagingPcsBaseUri.TrimEnd('/')] = "baf98f1b-374e-487d-af42-aa33807f11e4", | ||
}; | ||
|
||
private readonly TokenRequestContext _requestContext; | ||
private readonly TokenCredential _tokenCredential; | ||
|
||
private PcsApiCredential(TokenCredential credential, TokenRequestContext requestContext) | ||
{ | ||
_requestContext = requestContext; | ||
_tokenCredential = credential; | ||
} | ||
|
||
public override AccessToken GetToken(TokenRequestContext _, CancellationToken cancellationToken) | ||
{ | ||
// We hardcode the request context as we know which scopes we need to invoke in each scenario (user vs daemon) | ||
return _tokenCredential.GetToken(_requestContext, cancellationToken); | ||
} | ||
|
||
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext _, CancellationToken cancellationToken) | ||
{ | ||
// We hardcode the request context as we know which scopes we need to invoke in each scenario (user vs daemon) | ||
return _tokenCredential.GetTokenAsync(_requestContext, cancellationToken); | ||
} | ||
|
||
/// <summary> | ||
/// Use this for darc invocations from services using an MI | ||
/// </summary> | ||
internal static PcsApiCredential CreateManagedIdentityCredential(string barApiBaseUri, string managedIdentityId) | ||
{ | ||
string appId = EntraAppIds[barApiBaseUri.TrimEnd('/')]; | ||
|
||
var miCredential = new ManagedIdentityCredential(managedIdentityId); | ||
|
||
var appCredential = new ClientAssertionCredential( | ||
TENANT_ID, | ||
appId, | ||
async (ct) => (await miCredential.GetTokenAsync(new TokenRequestContext(new string[] { "api://AzureADTokenExchange" }), ct)).Token); | ||
|
||
var requestContext = new TokenRequestContext(new string[] { $"api://{appId}/.default" }); | ||
return new PcsApiCredential(appCredential, requestContext); | ||
} | ||
|
||
/// <summary> | ||
/// Use this for darc invocations from pipelines without a token. | ||
/// </summary> | ||
internal static PcsApiCredential CreateNonUserCredential(string barApiBaseUri) | ||
{ | ||
var requestContext = new TokenRequestContext(new string[] { $"{EntraAppIds[barApiBaseUri.TrimEnd('/')]}/.default" }); | ||
var credential = new AzureCliCredential(); | ||
return new PcsApiCredential(credential, requestContext); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...uctConstructionService/ProductConstructionService.Client/ProductConstructionServiceApi.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Azure.Core; | ||
|
||
namespace ProductConstructionService.Client | ||
{ | ||
public partial class ProductConstructionServiceApi | ||
{ | ||
public const string StagingPcsBaseUri = "https://product-construction-int.delightfuldune-c0f01ab0.westus2.azurecontainerapps.io/"; | ||
|
||
/// <summary> | ||
/// Creates a credential based on parameters provided. | ||
/// </summary> | ||
/// <param name="barApiBaseUri">BAR API URI used to determine the right set of credentials (INT vs PROD)</param> | ||
/// <param name="barApiToken">Token to use for the call. If none supplied, will try other flows.</param> | ||
/// <param name="managedIdentityId">Managed Identity to use for the auth</param> | ||
/// <returns>Credential that can be used to call the Maestro API</returns> | ||
public static TokenCredential CreateApiCredential( | ||
string barApiBaseUri, | ||
string barApiToken = null, | ||
string managedIdentityId = null) | ||
{ | ||
// 1. BAR or Entra token that can directly be used to authenticate against Maestro | ||
if (!string.IsNullOrEmpty(barApiToken)) | ||
{ | ||
return new PcsApiTokenCredential(barApiToken!); | ||
} | ||
|
||
barApiBaseUri ??= StagingPcsBaseUri; | ||
|
||
// 2. Managed identity (for server-to-server scenarios) | ||
if (!string.IsNullOrEmpty(managedIdentityId)) | ||
{ | ||
return PcsApiCredential.CreateManagedIdentityCredential(barApiBaseUri, managedIdentityId!); | ||
} | ||
|
||
// 3. Azure CLI authentication (for CI scenarios) | ||
return PcsApiCredential.CreateNonUserCredential(barApiBaseUri); | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...tructionService/ProductConstructionService.Client/ProductConstructionServiceApiOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace ProductConstructionService.Client | ||
{ | ||
public partial class ProductConstructionServiceApiOptions | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of <see cref="ProductConstructionServiceApiOptions"/> with the provided base URI. | ||
/// </summary> | ||
/// <param name="baseUri">API base URI</param> | ||
/// <param name="accessToken">Optional BAR token. When provided, will be used as the primary auth method.</param> | ||
/// <param name="managedIdentityId">Managed Identity to use for the auth</param> | ||
public ProductConstructionServiceApiOptions(string baseUri, string accessToken, string managedIdentityId) | ||
: this( | ||
new Uri(baseUri), | ||
ProductConstructionServiceApi.CreateApiCredential(baseUri, accessToken, managedIdentityId)) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="ProductConstructionServiceApiOptions"/> with the provided base URI. | ||
/// </summary> | ||
/// <param name="baseUri">API base URI</param> | ||
/// <param name="accessToken">Optional BAR token. When provided, will be used as the primary auth method.</param> | ||
/// <param name="managedIdentityId">Managed Identity to use for the auth</param> | ||
public ProductConstructionServiceApiOptions(string accessToken, string managedIdentityId) | ||
: this( | ||
new Uri(ProductConstructionServiceApi.StagingPcsBaseUri), | ||
ProductConstructionServiceApi.CreateApiCredential(ProductConstructionServiceApi.StagingPcsBaseUri, accessToken, managedIdentityId)) | ||
{ | ||
} | ||
} | ||
} |