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

Blob: Add TokenCredential auth #98

Merged
merged 1 commit into from
Oct 29, 2024
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,15 @@ These settings are available in addition to the [Common Settings](#common-settin

| MSBuild Property Name | Setting Type | Default value | Description |
| ------------- | ------------ | ------------- | ----------- |
| `$(MSBuildCacheCredentialsType)` | `string` | "Interactive" | Indicates the credential type to use for authentication. Valid values are "Interactive", "ConnectionString", "ManagedIdentity" |
| `$(MSBuildCacheCredentialsType)` | `string` | "Interactive" | Indicates the credential type to use for authentication. Valid values are "Interactive", "ConnectionString", "ManagedIdentity", "TokenCredential" |
| `$(MSBuildCacheBlobUri)` | `Uri` | | Specifies the uri of the Azure Storage Blob. |
| `$(MSBuildCacheManagedIdentityClientId)` | `string` | | Specifies the managed identity client id when using the "ManagedIdentity" credential type |
| `$(MSBuildCacheInteractiveAuthTokenDirectory)` | `string` | "%LOCALAPPDATA%\MSBuildCache\AuthTokenCache" | Specifies a token cache directory when using the "Interactive" credential type |

When using the "ConnectionString" credential type, the connection string to the blob storage account must be provided in the `MSBCACHE_CONNECTIONSTRING` environment variable. This connection string needs both read and write access to the resource.

When using the "TokenCredential" credential type, an access token must be provided in the `MSBCACHE_ACCESSTOKEN` environment variable. Alternately, if using the programmatic project cache API, a [`TokenCredential`](https://learn.microsoft.com/en-us/dotnet/api/azure.core.tokencredential?view=azure-dotnet) may be provided in the plugin's constructor.

## Other Packages

### Microsoft.MSBuildCache.SharedCompilation
Expand Down
9 changes: 9 additions & 0 deletions src/AzureBlobStorage/AzureStorageCredentialsType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ public enum AzureStorageCredentialsType
/// Use a managed identity to authenticate.
/// </summary>
ManagedIdentity,

/// <summary>
/// Use a token credential to authenticate.
/// </summary>
/// <remarks>
/// The "MSBCACHE_ACCESSTOKEN" environment variable must contain the access token to use. Alternately if using the programmatic
/// project cache API, a TokenCredential may be provided in the plugin's constructor.
/// </remarks>
TokenCredential,
}
43 changes: 41 additions & 2 deletions src/AzureBlobStorage/MSBuildCacheAzureBlobStoragePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using BuildXL.Cache.ContentStore.Distributed.Blob;
using BuildXL.Cache.ContentStore.Distributed.NuCache;
using BuildXL.Cache.ContentStore.Hashing;
Expand All @@ -27,12 +28,31 @@ namespace Microsoft.MSBuildCache.AzureBlobStorage;

public sealed class MSBuildCacheAzureBlobStoragePlugin : MSBuildCachePluginBase<AzureBlobStoragePluginSettings>
{
// Note: This is not in PluginSettings as that's configured through item metadata and thus makes it into MSBuild logs. This is a secret so that's not desirable.
// Note: These are not in PluginSettings as that's configured through item metadata and thus makes it into MSBuild logs. This is a secret so that's not desirable.
// Environment variables are also prone to leaking, so other authentication types are preferred when possible.
private const string AzureBlobConnectionStringEnvVar = "MSBCACHE_CONNECTIONSTRING";
private const string AzureBlobAccessTokenEnvVar = "MSBCACHE_ACCESSTOKEN";

private readonly TokenCredential? _tokenCredential;

// Although Azure Blob Storage is unrelated to Azure DevOps, Vso0 hashing is much faster than SHA256.
protected override HashType HashType => HashType.Vso0;

// Constructor used when MSBuild creates the plugin
public MSBuildCacheAzureBlobStoragePlugin()
{
}

public MSBuildCacheAzureBlobStoragePlugin(TokenCredential tokenCredential)
{
_tokenCredential = tokenCredential;
}

public MSBuildCacheAzureBlobStoragePlugin(string accessToken)
: this(new StaticTokenCredential(accessToken))
{
}

protected override async Task<ICacheClient> CreateCacheClientAsync(PluginLoggerBase logger, CancellationToken cancellationToken)
{
if (Settings == null
Expand Down Expand Up @@ -99,7 +119,7 @@ protected override async Task<ICacheClient> CreateCacheClientAsync(PluginLoggerB
Settings.AsyncCacheMaterialization);
}

private static IAzureStorageCredentials CreateAzureStorageCredentials(AzureBlobStoragePluginSettings settings, CancellationToken cancellationToken)
private IAzureStorageCredentials CreateAzureStorageCredentials(AzureBlobStoragePluginSettings settings, CancellationToken cancellationToken)
{
switch (settings.CredentialsType)
{
Expand Down Expand Up @@ -140,6 +160,25 @@ private static IAzureStorageCredentials CreateAzureStorageCredentials(AzureBlobS

return new ManagedIdentityAzureStorageCredentials(settings.ManagedIdentityClientId!, settings.BlobUri);
}
case AzureStorageCredentialsType.TokenCredential:
{
if (settings.BlobUri is null)
{
throw new InvalidOperationException($"{nameof(AzureBlobStoragePluginSettings.BlobUri)} is required when using {nameof(AzureBlobStoragePluginSettings.CredentialsType)}={settings.CredentialsType}");
}

// Allow the environment variable to supersede the constuctor-provided value.
string? accessToken = Environment.GetEnvironmentVariable(AzureBlobAccessTokenEnvVar);
TokenCredential? tokenCredential = !string.IsNullOrEmpty(accessToken)
? new StaticTokenCredential(accessToken)
: _tokenCredential;
if (tokenCredential is null)
{
throw new InvalidOperationException($"Required environment variable '{AzureBlobAccessTokenEnvVar}' not set");
}

return new TokenCredentialAzureStorageCredentials(settings.BlobUri, tokenCredential);
}
default:
{
throw new InvalidOperationException($"Unknown {nameof(AzureBlobStoragePluginSettings.CredentialsType)}: {settings.CredentialsType}");
Expand Down
26 changes: 26 additions & 0 deletions src/AzureBlobStorage/StaticTokenCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Azure.Core;
using System.Threading.Tasks;
using System.Threading;
using System;

namespace Microsoft.MSBuildCache.AzureBlobStorage;

internal sealed class StaticTokenCredential : TokenCredential
{
private readonly string _accessToken;

public StaticTokenCredential(string accessToken)
{
_accessToken = accessToken;
}

public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
// The token is static and we don't know the expiry, so just say it's a day from now.
=> new AccessToken(_accessToken, DateTimeOffset.Now.AddDays(1));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems sketchy, but I don't think there's a great alternative.


public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
=> new ValueTask<AccessToken>(GetToken(requestContext, cancellationToken));
}
19 changes: 19 additions & 0 deletions src/AzureBlobStorage/TokenCredentialAzureStorageCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Azure.Core;
using BuildXL.Cache.ContentStore.Interfaces.Auth;

namespace Microsoft.MSBuildCache.AzureBlobStorage;

internal sealed class TokenCredentialAzureStorageCredentials : AzureStorageCredentialsBase
{
public TokenCredentialAzureStorageCredentials(Uri blobUri, TokenCredential tokenCredential)
: base(blobUri)
{
Credentials = tokenCredential;
}

protected override TokenCredential Credentials { get; }
}