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

Support AZURE_AUTH_LOCATION #9312

Merged
merged 2 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Fixes and improvements
- Fix `UsernamePasswordCredential` constructor parameter mishandling
- Add `SdkAuthFileCredential` which allows using an auth file produced by the Azure CLI to authenticate
- Add support for `AZURE_AUTH_LOCATION` to `EnvironmentCredential`, which uses the newly added `SdkAuthFileCredential`

## 1.1.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ public ManagedIdentityCredential(string clientId = null, Azure.Identity.TokenCre
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class SdkAuthFileCredential : Azure.Core.TokenCredential
ellismg marked this conversation as resolved.
Show resolved Hide resolved
{
public SdkAuthFileCredential(string filePath) { }
public SdkAuthFileCredential(string pathToFile, Azure.Identity.TokenCredentialOptions options) { }
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken) { throw null; }
}
public partial class SharedTokenCacheCredential : Azure.Core.TokenCredential
{
public SharedTokenCacheCredential() { }
Expand Down
16 changes: 16 additions & 0 deletions sdk/identity/Azure.Identity/src/CredentialPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ private CredentialPipeline(TokenCredentialOptions options)
Diagnostics = new ClientDiagnostics(options);
}

private CredentialPipeline(Uri authorityHost, HttpPipeline httpPipeline, ClientDiagnostics diagnostics)
{
AuthorityHost = authorityHost ?? throw new ArgumentNullException(nameof(authorityHost));
HttpPipeline = httpPipeline ?? throw new ArgumentNullException(nameof(httpPipeline));
Diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
}

public static CredentialPipeline GetInstance(TokenCredentialOptions options)
{
return (options is null) ? s_Singleton.Value : new CredentialPipeline(options);
Expand Down Expand Up @@ -53,5 +60,14 @@ public CredentialDiagnosticScope StartGetTokenScope(string fullyQualifiedMethod,

return scope;
}

/// <summary>
/// Creates a new CredentialPipeline from an existing pipeline while replacing the AuthorityHost with a new value.
/// </summary>
/// <returns></returns>
public CredentialPipeline WithAuthorityHost(Uri authorityHost)
{
return new CredentialPipeline(authorityHost, HttpPipeline, Diagnostics);
}
}
}
35 changes: 25 additions & 10 deletions sdk/identity/Azure.Identity/src/EnvironmentCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ namespace Azure.Identity
/// <item><term>AZURE_CLIENT_SECRET</term><description>A client secret that was generated for the App Registration.</description></item>
/// <item><term>AZURE_USERNAME</term><description>The username, also known as upn, of an Azure Active Directory user account.</description></item>
/// <item><term>AZURE_PASSWORD</term><description>The password of the Azure Active Directory user account. Note this does not support accounts with MFA enabled.</description></item>
/// <item><term>AZURE_AUTH_LOCATION</term><description>The path to an SDK Auth file which contains configuration information.</description></item>
/// </list>
/// This credential ultimately uses a <see cref="ClientSecretCredential"/> or <see cref="UsernamePasswordCredential"/> to
/// perform the authentication using these details. Please consult the
/// documentation of that class for more details.
/// This credential ultimately uses a <see cref="ClientSecretCredential"/>, <see cref="UsernamePasswordCredential"/> or <see cref="SdkAuthFileCredential"/>
/// perform the authentication using these details. Please consult the documentation of that class for more details.
/// </summary>
public class EnvironmentCredential : TokenCredential, IExtendedTokenCredential
{
Expand All @@ -32,7 +32,7 @@ public class EnvironmentCredential : TokenCredential, IExtendedTokenCredential

/// <summary>
/// Creates an instance of the EnvironmentCredential class and reads client secret details from environment variables.
/// If the expected environment variables are not found at this time, the GetToken method will return the default <see cref="AccessToken"/> when invoked.
/// If the expected environment variables are not found at this time, the GetToken method will throw <see cref="CredentialUnavailableException"/>.
/// </summary>
public EnvironmentCredential()
: this(CredentialPipeline.GetInstance(null))
Expand All @@ -41,7 +41,7 @@ public EnvironmentCredential()

/// <summary>
/// Creates an instance of the EnvironmentCredential class and reads client secret details from environment variables.
/// If the expected environment variables are not found at this time, the GetToken method will return the default <see cref="AccessToken"/> when invoked.
/// If the expected environment variables are not found at this time, the GetToken method will throw <see cref="CredentialUnavailableException"/>.
/// </summary>
/// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service.</param>
public EnvironmentCredential(TokenCredentialOptions options)
Expand All @@ -58,6 +58,7 @@ internal EnvironmentCredential(CredentialPipeline pipeline)
string clientSecret = EnvironmentVariables.ClientSecret;
string username = EnvironmentVariables.Username;
string password = EnvironmentVariables.Password;
string sdkAuthLocation = EnvironmentVariables.SdkAuthLocation;

if (tenantId != null && clientId != null)
{
Expand All @@ -71,9 +72,14 @@ internal EnvironmentCredential(CredentialPipeline pipeline)
}
}

if (_credential is null && sdkAuthLocation != null)
{
_credential = new SdkAuthFileCredential(sdkAuthLocation);
}

if (_credential is null)
{
StringBuilder builder = new StringBuilder("Environment variables not fully configured. AZURE_TENANT_ID and AZURE_CLIENT_ID must be set, along with either AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD. Currently set variables [ ");
StringBuilder builder = new StringBuilder("Environment variables not fully configured. AZURE_TENANT_ID and AZURE_CLIENT_ID must be set, along with either AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD. Alternately, AZURE_AUTH_LOCATION ca be set. Currently set variables [");

if (tenantId != null)
{
Expand All @@ -100,6 +106,11 @@ internal EnvironmentCredential(CredentialPipeline pipeline)
builder.Append(" AZURE_PASSWORD");
}

if (sdkAuthLocation != null)
{
builder.Append(" AZURE_AUTH_LOCATION");
}

_unavailbleErrorMessage = builder.Append(" ]").ToString();
}
}
Expand All @@ -113,11 +124,13 @@ internal EnvironmentCredential(CredentialPipeline pipeline, TokenCredential cred

/// <summary>
/// Obtains a token from the Azure Active Directory service, using the specified client details specified in the environment variables
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate.
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate. Alternately,
/// if AZURE_AUTH_LOCATION is set, that information is used.
/// This method is called by Azure SDK clients. It isn't intended for use in application code.
/// </summary>
/// <remarks>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are not specified, the default <see cref="AccessToken"/>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_AUTH_LOCATION are not specified,
/// this method throws <see cref="CredentialUnavailableException"/>.
/// </remarks>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
Expand All @@ -129,11 +142,13 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell

/// <summary>
/// Obtains a token from the Azure Active Directory service, using the specified client details specified in the environment variables
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate.
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate. Alternately,
/// if AZURE_AUTH_LOCATION is set, that information is used.
/// This method is called by Azure SDK clients. It isn't intended for use in application code.
/// </summary>
/// <remarks>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are not specifeid, the default <see cref="AccessToken"/>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_AUTH_LOCATION are not specified,
/// this method throws <see cref="CredentialUnavailableException"/>.
/// </remarks>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
Expand Down
1 change: 1 addition & 0 deletions sdk/identity/Azure.Identity/src/EnvironmentVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal class EnvironmentVariables
public static string TenantId => Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
public static string ClientId => Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
public static string ClientSecret => Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
public static string SdkAuthLocation => Environment.GetEnvironmentVariable("AZURE_AUTH_LOCATION");

public static string MsiEndpoint => Environment.GetEnvironmentVariable("MSI_ENDPOINT");
public static string MsiSecret => Environment.GetEnvironmentVariable("MSI_SECRET");
Expand Down
146 changes: 146 additions & 0 deletions sdk/identity/Azure.Identity/src/SdkAuthFileCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;

namespace Azure.Identity
{
/// <summary>
/// Enables authentication to Azure Active Directory using configuration information stored Azure SDK Auth File.
/// </summary>
/// <remarks>
/// An Azaure SDK Auth file may be generated by passing <code>--sdk-auth</code> when generating an service principal
/// with <code>az ad sp create-for-rbac</code>. At this time <see cref="SdkAuthFileCredential"/> only supports
/// SDK Auth Files which contain a client secret, client certificates are not supported at this time.
/// </remarks>
public class SdkAuthFileCredential : TokenCredential
{
internal string FilePath { get; }

private readonly CredentialPipeline _pipeline;

// Initialized on first use by EnsureCredential
private TokenCredential _credential;

/// <summary>
/// Creates an instance of the SdkAuthFileCredential class based on information in given SDK Auth file.
/// If the file is not found or there are errors parsing it, <see cref="GetToken(TokenRequestContext, CancellationToken)"/>
/// and <see cref="GetTokenAsync(TokenRequestContext, CancellationToken)"/> will throw a <see cref="AuthenticationFailedException"/>
/// with details on why the file could not be used.
/// </summary>
/// <param name="filePath">The path to the SDK Auth file.</param>
public SdkAuthFileCredential(string filePath):
this(filePath, CredentialPipeline.GetInstance(null))
{
}

/// <summary>
/// Creates an instance of the SdkAuthFileCredential class based on information in given SDK Auth file.
/// If the file is not found or there are errors parsing it, <see cref="GetToken(TokenRequestContext, CancellationToken)"/>
/// and <see cref="GetTokenAsync(TokenRequestContext, CancellationToken)"/> will throw a <see cref="AuthenticationFailedException"/>
/// with details on why the file could not be used.
/// </summary>
/// <param name="pathToFile">The path to the SDK Auth file.</param>
/// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service. Note that
/// <see cref="TokenCredentialOptions.AuthorityHost"/> is ignored in favor of the <code>activeDirectoryEndpointUrl</code> property
/// of the SDK Auth file.</param>
public SdkAuthFileCredential(string pathToFile, TokenCredentialOptions options)
: this(pathToFile, CredentialPipeline.GetInstance(options))
{
}

internal SdkAuthFileCredential(string pathToFile, CredentialPipeline pipeline)
{
FilePath = pathToFile ?? throw new ArgumentNullException(nameof(pathToFile));
_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline));
}

/// <summary>
/// Obtains a token from the Azure Active Directory service, using the specified client detailed specified in the SDK Auth file.
/// This method is called by Azure SDK clients. It isn't intended for use in application code.
/// </summary>
/// <remarks>
/// If the SDK Auth file is missing or invalid, this method throws a <see cref="AuthenticationFailedException"/> exception.
/// </remarks>
/// <param name="requestContext">The details of the authentication request</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
EnsureCredential(false, cancellationToken).GetAwaiter().GetResult();

return _credential.GetToken(requestContext, cancellationToken);
}

/// <summary>
/// Obtains a token from the Azure Active Directory service, using the specified client detailed specified in the SDK Auth file.
/// This method is called by Azure SDK clients. It isn't intended for use in application code.
/// </summary>
/// <remarks>
/// If the SDK Auth file is missing or invalid, this method throws a <see cref="AuthenticationFailedException"/> exception.
/// </remarks>
/// <param name="requestContext">The details of the authentication request</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns>
public async override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
await EnsureCredential(true, cancellationToken).ConfigureAwait(false);

return await _credential.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Ensures that credential information is loaded from the SDK Auth file. This method should be called to initialize
/// <code>_credential</code> before it is used. If the SDK Auth file is not found or invalid, this method will throw
/// <see cref="AuthenticationFailedException"/>.
/// </summary>
/// <param name="isAsync">When true, the task reutrned by this method may complete asynchronously.</param>
/// <param name="cancellationToken">>A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>A task that will ensure <code>_credential</code> has been initialized</returns>
internal async Task EnsureCredential(bool isAsync, CancellationToken cancellationToken)
{
if (_credential == null)
{
try
{
_credential = BuildCredentialForCredentialsFile(isAsync ? await ParseCredentialsFileAsync(FilePath, cancellationToken).ConfigureAwait(false) : ParseCredentialsFile(FilePath));
} catch (Exception e) when (!(e is OperationCanceledException))
{
throw new AuthenticationFailedException("Error parsing SDK Auth File", e);
}
}
}

private static Dictionary<string, string> ParseCredentialsFile(string filePath)
{
return JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(filePath));
}

private static async Task<Dictionary<string, string>> ParseCredentialsFileAsync(string filePath, CancellationToken cancellationToken)
{
using Stream s = File.OpenRead(filePath);
return await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(s, null, cancellationToken);
}

private TokenCredential BuildCredentialForCredentialsFile(Dictionary<string, string> authData)
{
authData.TryGetValue("clientId", out string clientId);
authData.TryGetValue("clientSecret", out string clientSecret);
authData.TryGetValue("tenantId", out string tenantId);
authData.TryGetValue("activeDirectoryEndpointUrl", out string activeDirectoryEndpointUrl);

if (clientId == null || clientSecret == null || tenantId == null || activeDirectoryEndpointUrl == null)
{
throw new Exception("Malformed Azure SDK Auth file. The file should contain 'clientId', 'clientSecret', 'tenentId' and 'activeDirectoryEndpointUrl' values.");
}

return new ClientSecretCredential(tenantId, clientId, clientSecret, _pipeline.WithAuthorityHost(new Uri(activeDirectoryEndpointUrl)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<ProjectReference Include="..\src\Azure.Identity.csproj" />
<None Update="Data\authfile.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="Data\cert.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Expand Down
12 changes: 12 additions & 0 deletions sdk/identity/Azure.Identity/tests/Data/authfile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"clientId": "mockclientid",
"clientSecret": "mockclientsecret",
"subscriptionId": "mocksubscriptionid",
"tenantId": "mocktenantid",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
Loading