Skip to content

Commit

Permalink
Blob: Add TokenCredential auth (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfederm authored Oct 29, 2024
1 parent adfe128 commit 9ed3ab4
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 3 deletions.
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));

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; }
}

0 comments on commit 9ed3ab4

Please sign in to comment.