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

Private Preview AAD: Adds AAD support to the SDK #1798

Merged
merged 35 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5a6bdec
Merged PR 356078: Send AAD Token In Authorization Header
junyguo Jul 21, 2020
071d368
1. Fixed all the merge conflicts from cherry pick
Aug 7, 2020
e7a989b
merge to latest
Aug 21, 2020
8401f05
InitTest
Aug 21, 2020
55bd92b
Adding test
Aug 21, 2020
f744db5
Make constructor public for flag
Aug 21, 2020
20df14d
Adds emulator flag
Aug 21, 2020
b4bd228
Merge to latest
Sep 11, 2020
2dc1023
Switch to default
Sep 11, 2020
d2932fa
Merge remote-tracking branch 'origin/master' into users/jawilley/aad/…
Sep 17, 2020
008b1dc
Fixes and background refresh improvements
Sep 17, 2020
cb34d11
Merge remote-tracking branch 'origin/master' into users/jawilley/aad/…
Sep 17, 2020
2be725d
Test updates and fixes
Sep 17, 2020
5be5f79
Make const
Sep 17, 2020
2365a3e
Fixed gateway account reader check
Sep 18, 2020
6ac58ec
Makes it part of the preview SDK. Adds additional checks
Sep 18, 2020
8a8838f
Merged to the latest
Sep 18, 2020
b225bf2
Update preview contract
Sep 19, 2020
62f8d73
Move all the Authorization files into a single folder
Sep 19, 2020
5db4d10
Refactored AAD into a single contract with different implementations …
Sep 21, 2020
9ceed9e
Updates based on comments
Sep 21, 2020
411e010
Fixed minor bug with missing date time and renamed
Sep 22, 2020
aa31ea6
Revert file changes
Sep 22, 2020
e4bcf95
Merge branch 'master' into users/jawilley/aad/AddAAD
j82w Sep 22, 2020
6cd1170
Fixed another bug with passing new type around.
Sep 22, 2020
cf3916d
Merge branch 'users/jawilley/aad/AddAAD' of https://github.com/Azure/…
Sep 22, 2020
1a767ca
Mocks pass in valid base64 string and updated tests
Sep 23, 2020
ec3b877
Fix mock tests to pass a valid base64 string.
Sep 23, 2020
ff08f04
Merge remote-tracking branch 'origin/master' into users/jawilley/aad/…
Sep 23, 2020
2a9c099
Added additional comments
Sep 23, 2020
c2ca390
Updates based on comments
Sep 24, 2020
bde0fde
Removed unused using statements
Sep 24, 2020
ef28d10
Merge branch 'master' into users/jawilley/aad/AddAAD
j82w Sep 24, 2020
8f45e18
Fixed grammar
Sep 24, 2020
6560a75
Merge branch 'users/jawilley/aad/AddAAD' of https://github.com/Azure/…
Sep 24, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public static void ParseAuthorizationToken(
}

authorizationTokenString = HttpUtility.UrlDecode(authorizationTokenString);
j82w marked this conversation as resolved.
Show resolved Hide resolved

// Format of the token being deciphered is
// type=<master/resource/system>&ver=<version>&sig=<base64encodedstring>

Expand Down Expand Up @@ -835,4 +835,4 @@ public void Dispose()
}
}
}
}
}
73 changes: 73 additions & 0 deletions Microsoft.Azure.Cosmos/src/Authorization/CosmosAuthorization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal abstract class CosmosAuthorization : ICosmosAuthorizationTokenProvider, IAuthorizationTokenProvider, IDisposable
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an Authorizer not an Authorization.

Copy link
Member

Choose a reason for hiding this comment

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

AuthTokenProvider?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about AuthorizationTokenProvider?

{
public async Task AddSystemAuthorizationHeaderAsync(
DocumentServiceRequest request,
string federationId,
string verb,
string resourceId)
{
request.Headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);

request.Headers[HttpConstants.HttpHeaders.Authorization] = (await this.GetUserAuthorizationAsync(
resourceId ?? request.ResourceAddress,
PathsHelper.GetResourcePath(request.ResourceType),
verb,
request.Headers,
request.RequestAuthorizationTokenType)).token;
}

public abstract ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType);

public abstract ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType);

public abstract ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext);

public abstract void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload);

public static CosmosAuthorization CreateWithResourceTokenOrAuthKey(string authKeyOrResourceToken)
{
if (AuthorizationHelper.IsResourceToken(authKeyOrResourceToken))
{
return new CosmosAuthorizationResourceToken(authKeyOrResourceToken);
}
else
{
return new CosmosAuthorizationComputeHash(authKeyOrResourceToken);
}
}

public abstract void Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal sealed class CosmosAuthorizationComputeHash : CosmosAuthorization
{
////The MAC signature found in the HTTP request is not the same as the computed signature.Server used following string to sign
////The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign
private const string MacSignatureString = "to sign";
private const string EnableAuthFailureTracesConfig = "enableAuthFailureTraces";
private readonly Lazy<bool> enableAuthFailureTraces;
private readonly IComputeHash authKeyHashFunction;
private bool isDisposed = false;

public CosmosAuthorizationComputeHash(IComputeHash computeHash)
{
this.authKeyHashFunction = computeHash ?? throw new ArgumentNullException(nameof(computeHash));
this.enableAuthFailureTraces = new Lazy<bool>(() =>
{
#if NETSTANDARD20
// GetEntryAssembly returns null when loaded from native netstandard2.0
if (System.Reflection.Assembly.GetEntryAssembly() == null)
{
return false;
}
#endif
string enableAuthFailureTracesString = System.Configuration.ConfigurationManager.AppSettings[EnableAuthFailureTracesConfig];
if (string.IsNullOrEmpty(enableAuthFailureTracesString) ||
!bool.TryParse(enableAuthFailureTracesString, out bool enableAuthFailureTracesFlag))
{
return false;
}

return enableAuthFailureTracesFlag;
});
}

public CosmosAuthorizationComputeHash(SecureString authKey)
: this(new SecureStringHMACSHA256Helper(authKey))
{
}

public CosmosAuthorizationComputeHash(string authKey)
: this(new StringHMACSHA256Hash(authKey))
{
}

public override ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType)
{
string authorizationToken = AuthorizationHelper.GenerateKeyAuthorizationSignature(
requestVerb,
resourceAddress,
resourceType,
headers,
this.authKeyHashFunction,
out AuthorizationHelper.ArrayOwner arrayOwner);

using (arrayOwner)
{
string payload = null;
if (arrayOwner.Buffer.Count > 0)
{
payload = Encoding.UTF8.GetString(arrayOwner.Buffer.Array, arrayOwner.Buffer.Offset, (int)arrayOwner.Buffer.Count);
}

return new ValueTask<(string token, string payload)>((authorizationToken, payload));
}
}

public override ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext)
{
// this is masterkey authZ
headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);

string authorizationToken = AuthorizationHelper.GenerateKeyAuthorizationSignature(
requestVerb,
resourceAddress,
resourceType,
headers,
this.authKeyHashFunction,
out AuthorizationHelper.ArrayOwner arrayOwner);

using (arrayOwner)
{
return new ValueTask<string>(authorizationToken);
}
}

public override ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType)
{
string dateTime = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);
headersCollection[HttpConstants.HttpHeaders.XDate] = dateTime;

string token = AuthorizationHelper.GenerateKeyAuthorizationSignature(
verb,
requestAddress,
headersCollection,
this.authKeyHashFunction);

headersCollection.Add(HttpConstants.HttpHeaders.Authorization, token);
return default;
}

public override void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload)
{
if (payload != null
&& dce.Message != null
&& dce.StatusCode.HasValue
&& dce.StatusCode.Value == HttpStatusCode.Unauthorized
&& dce.Message.Contains(CosmosAuthorizationComputeHash.MacSignatureString))
{
// The following code is added such that we get trace data on unexpected 401/HMAC errors and it is
// disabled by default. The trace will be trigger only when "enableAuthFailureTraces" named configuration
// is set to true (currently true for CTL runs).
// For production we will work directly with specific customers in order to enable this configuration.
string normalizedPayload = CosmosAuthorizationComputeHash.NormalizeAuthorizationPayload(payload);
if (this.enableAuthFailureTraces.Value)
{
string tokenFirst5 = HttpUtility.UrlDecode(authorizationToken).Split('&')[2].Split('=')[1].Substring(0, 5);
ulong authHash = 0;
if (this.authKeyHashFunction?.Key != null)
{
byte[] bytes = Encoding.UTF8.GetBytes(this.authKeyHashFunction?.Key?.ToString());
authHash = Documents.Routing.MurmurHash3.Hash64(bytes, bytes.Length);
}
DefaultTrace.TraceError("Un-expected authorization payload mis-match. Actual payload={0}, token={1}..., hash={2:X}..., error={3}",
normalizedPayload, tokenFirst5, authHash, dce.Message);
}
else
{
DefaultTrace.TraceError("Un-expected authorization payload mis-match. Actual {0} service expected {1}", normalizedPayload, dce.Message);
}
}
}

public override void Dispose()
{
if (!this.isDisposed)
{
this.authKeyHashFunction.Dispose();
this.isDisposed = true;
}
}

private static string NormalizeAuthorizationPayload(string input)
{
const int expansionBuffer = 12;
StringBuilder builder = new StringBuilder(input.Length + expansionBuffer);
for (int i = 0; i < input.Length; i++)
{
switch (input[i])
{
case '\n':
builder.Append("\\n");
break;
case '/':
builder.Append("\\/");
break;
default:
builder.Append(input[i]);
break;
}
}

return builder.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal sealed class CosmosAuthorizationResourceToken : CosmosAuthorization
{
private readonly string urlEncodedAuthKeyResourceToken;
private readonly ValueTask<string> urlEncodedAuthKeyResourceTokenValueTask;
private readonly ValueTask<(string, string)> urlEncodedAuthKeyResourceTokenValueTaskWithPayload;
private readonly ValueTask defaultValueTask;

public CosmosAuthorizationResourceToken(
string authKeyResourceToken)
{
this.urlEncodedAuthKeyResourceToken = HttpUtility.UrlEncode(authKeyResourceToken);
this.urlEncodedAuthKeyResourceTokenValueTask = new ValueTask<string>(this.urlEncodedAuthKeyResourceToken);
this.urlEncodedAuthKeyResourceTokenValueTaskWithPayload = new ValueTask<(string, string)>((this.urlEncodedAuthKeyResourceToken, default));
this.defaultValueTask = new ValueTask();
}

public override ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType)
{
// If the input auth token is a resource token, then use it as a bearer-token.
return this.urlEncodedAuthKeyResourceTokenValueTaskWithPayload;
}

public override ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext)
{
// If the input auth token is a resource token, then use it as a bearer-token.
return this.urlEncodedAuthKeyResourceTokenValueTask;
}

public override ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType)
{
headersCollection.Add(HttpConstants.HttpHeaders.Authorization, this.urlEncodedAuthKeyResourceToken);
return this.defaultValueTask;
}

public override void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload)
{
DefaultTrace.TraceError($"Un-expected authorization for resource token. {dce.Message}");
}

public override void Dispose()
{
}
}
}
Loading