-
Notifications
You must be signed in to change notification settings - Fork 528
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
Cosmos managed Identity Authentication Changes #3950
Changes from all commits
fa7c777
cf45309
73b14b3
28ebfa4
01b0896
0c6a52e
b63feac
86a9cd6
4781ad2
a3eb589
10a29b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
using System.Net.Http; | ||
using System.Net.Http.Json; | ||
using System.Security.Cryptography; | ||
using System.Security.Cryptography.Xml; | ||
using System.Text.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
@@ -18,6 +19,7 @@ | |
using Microsoft.Health.Abstractions.Exceptions; | ||
using Microsoft.Health.Extensions.DependencyInjection; | ||
using Microsoft.Health.Fhir.Core.Extensions; | ||
using Microsoft.Health.Fhir.Core.Features.Operations; | ||
using Microsoft.Health.Fhir.CosmosDb.Core.Configs; | ||
|
||
namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage | ||
|
@@ -33,19 +35,23 @@ internal class CosmosDbCollectionPhysicalPartitionInfo : IRequireInitializationO | |
private readonly IHttpClientFactory _httpClientFactory; | ||
private readonly ILogger<CosmosDbCollectionPhysicalPartitionInfo> _logger; | ||
private readonly CancellationTokenSource _backgroundLoopCancellationTokenSource = new(); | ||
private readonly IAccessTokenProvider _aadTokenProvider; | ||
private Task _backgroundLoopTask; | ||
|
||
public CosmosDbCollectionPhysicalPartitionInfo( | ||
IAccessTokenProvider aadTokenProvider, | ||
CosmosDataStoreConfiguration dataStoreConfiguration, | ||
IOptionsMonitor<CosmosCollectionConfiguration> collectionConfiguration, | ||
IHttpClientFactory httpClientFactory, | ||
ILogger<CosmosDbCollectionPhysicalPartitionInfo> logger) | ||
{ | ||
EnsureArg.IsNotNull(aadTokenProvider, nameof(aadTokenProvider)); | ||
EnsureArg.IsNotNull(dataStoreConfiguration, nameof(dataStoreConfiguration)); | ||
EnsureArg.IsNotNull(collectionConfiguration, nameof(collectionConfiguration)); | ||
EnsureArg.IsNotNull(httpClientFactory, nameof(httpClientFactory)); | ||
EnsureArg.IsNotNull(logger, nameof(logger)); | ||
|
||
_aadTokenProvider = aadTokenProvider; | ||
_dataStoreConfiguration = dataStoreConfiguration; | ||
_collectionConfiguration = collectionConfiguration.Get(Constants.CollectionConfigurationName); | ||
_httpClientFactory = httpClientFactory; | ||
|
@@ -92,46 +98,18 @@ private async Task<int> GetPhysicalPartitionCount(CancellationToken cancellation | |
using HttpClient client = _httpClientFactory.CreateClient(); | ||
|
||
string host = _dataStoreConfiguration.Host; | ||
string key = _dataStoreConfiguration.Key; | ||
|
||
if (string.IsNullOrWhiteSpace(host)) | ||
{ | ||
if (string.IsNullOrWhiteSpace(key)) | ||
{ | ||
host = CosmosDbLocalEmulator.Host; | ||
key = CosmosDbLocalEmulator.Key; | ||
} | ||
else | ||
{ | ||
Ensure.That(host, $"{nameof(CosmosDataStoreConfiguration)}.{nameof(CosmosDataStoreConfiguration.Host)}").IsNotNullOrEmpty(); | ||
} | ||
} | ||
else if (string.IsNullOrWhiteSpace(key)) | ||
{ | ||
Ensure.That(key, $"{nameof(CosmosDataStoreConfiguration)}.{nameof(CosmosDataStoreConfiguration.Key)}").IsNotNullOrEmpty(); | ||
} | ||
Comment on lines
-95
to
-112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should keep the same behavior but using the new tokenProvider. We might need to add a new variable called "token" in CosmosDBLocalEmuletor and make sure we clean up "key" in case it is not needed anymore. If "token" is not longer needed (because now you use new DefaultAzureCredential()), then can we clean up all key references? I think we can keep the same structure because Ensure.That() will throw an exception in case host or token are null or white space, as now, it will just check host and not token. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still need "key"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @abiisnn We had removed "Key" reference from everywhere. |
||
Ensure.That(host, $"{nameof(CosmosDataStoreConfiguration)}.{nameof(CosmosDataStoreConfiguration.Host)}").IsNotNullOrEmpty(); | ||
|
||
string date = DateTime.UtcNow.ToString("R"); | ||
|
||
bool isResourceToken = IsResourceToken(key); | ||
|
||
string authToken = HttpUtility.UrlEncode( | ||
isResourceToken | ||
? key | ||
: GenerateAuthToken( | ||
"get", | ||
"pkranges", | ||
$"dbs/{_dataStoreConfiguration.DatabaseId}/colls/{_collectionConfiguration.CollectionId}", | ||
date, | ||
key)); | ||
string accessToken = await GenerateAccessToken(host, cancellationToken); | ||
|
||
using var httpRequestMessage = new HttpRequestMessage( | ||
HttpMethod.Get, | ||
$"{host}/dbs/{_dataStoreConfiguration.DatabaseId}/colls/{_collectionConfiguration.CollectionId}/pkranges") | ||
{ | ||
Headers = | ||
{ | ||
{ "authorization", authToken }, | ||
{ "authorization", accessToken }, | ||
{ "x-ms-version", "2018-12-31" }, | ||
{ "x-ms-date", date }, | ||
}, | ||
|
@@ -175,15 +153,21 @@ public async ValueTask DisposeAsync() | |
} | ||
} | ||
|
||
private static string GenerateAuthToken(string verb, string resourceType, string resourceId, string date, string key) | ||
private async Task<string> GenerateAccessToken(string host, CancellationToken cancellationToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we check that host is not null, empty or white space? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @abiisnn We are already checking for "host" as not null and empty or white space in line number 101. |
||
{ | ||
string payLoad = $"{verb.ToLowerInvariant()}\n{resourceType.ToLowerInvariant()}\n{resourceId}\n{date.ToLowerInvariant()}\n\n"; | ||
|
||
using var hmacSha256 = new HMACSHA256 { Key = Convert.FromBase64String(key) }; | ||
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)); | ||
string signature = Convert.ToBase64String(hashPayLoad); | ||
string accessToken = string.Empty; | ||
var resourceURI = new Uri(host); | ||
try | ||
{ | ||
accessToken = await _aadTokenProvider.GetAccessTokenForResourceAsync(resourceURI, cancellationToken); | ||
accessToken = HttpUtility.UrlEncode($"type=aad&ver=1.0&sig={accessToken}"); | ||
} | ||
catch (AccessTokenProviderException ex) | ||
{ | ||
_logger.LogError(ex, "Failed to get access token from managed identity."); | ||
} | ||
|
||
return $"type=master&ver=1.0&sig={signature}"; | ||
return accessToken; | ||
} | ||
|
||
private record PartitionKeyRange; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What "add" in "aadTokenProvider" stands for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abiisnn "aad" stands here for Azure Active Directory.