Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remediate Unsafe PAT Usage #2052

Merged
merged 8 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion GraphWebApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
}
},
"KnownIssues": {
"Token": "ENTER_TOKEN",
"TenantId": "ENTER_TENANT_ID",
"MsiClientId": "ENTER_MSI_CLIENT_ID",
"AppClientId": "ENTER_APP_CLIENT_ID",
"Uri": "https://microsoftgraph.visualstudio.com/"
},
"ApplicationInsights": {
Expand Down
4 changes: 4 additions & 0 deletions GraphWebApi/wwwroot/OpenApi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ servers:
description: Main server
- url: https://graphexplorerapi-staging.azurewebsites.net/
description: Staging server
- url: https://devxapi-func-prod-eastus.azurewebsites.net/
description: Torus Main server
- url: https://devxapi-func-ppe-eastus.azurewebsites.net/
description: Torus Staging server
- url: https://localhost:44399/
description: Local test server
- url: https://localhost:5001/
Expand Down
11 changes: 7 additions & 4 deletions KnownIssuesService/KnownIssuesService.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.1" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.Client" Version="19.225.1" />
<PackageReference Include="Azure.Core" Version="1.44.1" />
<PackageReference Include="Azure.Identity" Version="1.13.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.239.0-preview" />
<PackageReference Include="Microsoft.VisualStudio.Services.Client" Version="19.239.0-preview" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.239.0-preview" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.12.19">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
69 changes: 69 additions & 0 deletions KnownIssuesService/Services/FederatedApplicationCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;

namespace KnownIssuesService.Services
{
/// <summary>
/// TokenCredential implementation to use a managed identity's access token
/// as a signed client assertion when acquiring tokens for a primary client
/// application. The primary application must have a federated credential
/// configured to allow the managed identity to exchange tokens.
/// </summary>
public sealed class FederatedApplicationCredential : TokenCredential
{
/// <summary>
/// Constructor implementation for the federated application credential.
/// </summary>
/// <param name="tenantId">TenantId where you want to use the credential (not necessarily the home tenant).</param>
/// <param name="msiClientId">ClientId for the managed identity.</param>
/// <param name="appClientId">ClientId for the application registration.</param>
[ExcludeFromCodeCoverage]
public FederatedApplicationCredential(string tenantId, string msiClientId, string appClientId)
{
ManagedIdentity = new ManagedIdentityCredential(msiClientId);
ClientAssertion = new ClientAssertionCredential(tenantId, appClientId, ComputeAssertionAsync);
}

[ExcludeFromCodeCoverage]
private ManagedIdentityCredential ManagedIdentity
{
get;
}

[ExcludeFromCodeCoverage]
private ClientAssertionCredential ClientAssertion
{
get;
}

/// <inheritdoc/>
[ExcludeFromCodeCoverage]
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return ClientAssertion.GetToken(requestContext, cancellationToken);
}

/// <inheritdoc/>
[ExcludeFromCodeCoverage]
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return await ClientAssertion.GetTokenAsync(requestContext, cancellationToken);
}

/// <summary>
/// Get an exchange token from our managed identity to use as an assertion.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A signed assertion to authenticate with AzureAD.</returns>
[ExcludeFromCodeCoverage]
private async Task<string> ComputeAssertionAsync(CancellationToken cancellationToken)
{
TokenRequestContext msiContext = new(["api://AzureADTokenExchange/.default"]);
AccessToken msiToken = await ManagedIdentity.GetTokenAsync(msiContext, cancellationToken);
return msiToken.Token;
}
}
}
13 changes: 8 additions & 5 deletions KnownIssuesService/Services/KnownIssuesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Collections.Generic;
Expand All @@ -31,8 +32,10 @@
private readonly TelemetryClient _telemetryClient;
private readonly Dictionary<string, string> _knownIssuesTraceProperties =
new() { { UtilityConstants.TelemetryPropertyKey_KnownIssues, nameof(KnownIssuesService) } };
private const string AccessTokenValue = "KnownIssues:Token";
private const string KnownIssuesPath = "KnownIssues:Uri";
private const string tenantId = "KnownIssues:TenantId";
private const string msiClientId = "KnownIssues:MsiClientId";
private const string appClientId = "KnownIssues:AppClientId";
private const string KnownIssuesPath = "KnownIssues:Uri";

public KnownIssuesService(IConfiguration configuration,
TelemetryClient telemetryClient = null,
Expand All @@ -49,14 +52,14 @@
/// </summary>
/// <returns>An instance of the authenticated VS Service</returns>
[ExcludeFromCodeCoverage]
private VssBasicCredential Authenticate()
private VssAzureIdentityCredential Authenticate()
{
_telemetryClient?.TrackTrace("Fetch personal access token from configuration and use it to authenticate into Visual Studio Service",
SeverityLevel.Information,
_knownIssuesTraceProperties);

var accessToken = _configuration[AccessTokenValue];
return new VssBasicCredential(string.Empty, accessToken);
FederatedApplicationCredential credentialAzure = new FederatedApplicationCredential(_configuration[tenantId], _configuration[msiClientId], _configuration[appClientId]);
return new VssAzureIdentityCredential(credentialAzure);
}

/// <summary>
Expand All @@ -76,7 +79,7 @@
var knownIssuesUri = _configuration[KnownIssuesPath];
return new WorkItemTrackingHttpClient(new Uri(knownIssuesUri), Authenticate());
}
catch

Check warning on line 82 in KnownIssuesService/Services/KnownIssuesService.cs

View workflow job for this annotation

GitHub Actions / Build

Add logic to this catch clause or eliminate it and rethrow the exception automatically. (https://rules.sonarsource.com/csharp/RSPEC-2737)

Check warning on line 82 in KnownIssuesService/Services/KnownIssuesService.cs

View workflow job for this annotation

GitHub Actions / Build

Add logic to this catch clause or eliminate it and rethrow the exception automatically. (https://rules.sonarsource.com/csharp/RSPEC-2737)
{
throw;
}
Expand Down Expand Up @@ -139,7 +142,7 @@
/// Function to Query the List of Known Issues from Azure DevOps Known Organization
/// </summary>
/// <returns>Known Issues Contract that contains json items that will be rendered on the browser</returns>
public async Task<List<KnownIssue>> QueryBugsAsync(string environment, Wiql workItemQuery = null)

Check warning on line 145 in KnownIssuesService/Services/KnownIssuesService.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 25 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 145 in KnownIssuesService/Services/KnownIssuesService.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 25 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
_telemetryClient?.TrackTrace("Fetches a WorkItemQueryResult for fetching work item Ids and urls",
SeverityLevel.Information,
Expand Down
Loading