Skip to content

Commit

Permalink
Client Encryption: Adds code to support PartitionKey and Id encryptio…
Browse files Browse the repository at this point in the history
…n. (Azure#3241)

* support partition key and id encryption.

* Update EncryptionContainer.cs

* support hirarchical pk paths.

* pk none and null checks.

* Update MdeEncryptionTests.cs

* Update EncryptionContainer.cs

* Update EncryptionContainer.cs

* updates

* fixes.

* fixes to handle container delete scenarios for PK mismatch.

* Update MdeEncryptionTests.cs

* Refactoring

* Updated tests.

* Update EncryptionProcessor.cs

* Update QueryDefinitionExtensions.cs

* Update EncryptionProcessor.cs

* Updated cosmos version

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* updated build prop and supported sdk version

* Update Microsoft.Azure.Cosmos.Encryption.csproj

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Fixes

* Fixed preview tests.

* Fixes as per review comments.

* Update EncryptionContainer.cs

* Update EncryptionContainer.cs

* Update EncryptionContainer.cs

* fixes issue with nested PK path encryption.

* Update changelog.md

* Fixes.

* Update EncryptionProcessor.cs

* Update EncryptionProcessor.cs

* Update MdeEncryptionTests.cs

* Add preview flag for Encryption Emulator tests.

* Update build-preview.yml

* Update MdeEncryptionTests.cs

* Update build-preview.yml

* Update EncryptionProcessor.cs

* Fixes

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Update changelog.md

* Update EncryptionContainer.cs

* Update EncryptionProcessor.cs

* fixes as per review comments. Fixed serializer used.

* Update Microsoft.Azure.Cosmos.Encryption.csproj

* Update build-preview.yml

* updated build flags and preview test.

* Fix test run check

* Update Microsoft.Azure.Cosmos.Encryption.EmulatorTests.csproj

* Fixed PK encryption in GetItemQueryStreamIterator feed range.

* dispose clients in tests.

* Update EncryptionProcessor.cs

* move async calls from getqueryiterator to ReadNextAsync

* fixes in ENCRYPTIONPREVIEW flagged code

* Update EncryptionFeedIterator.cs

* Update EncryptionFeedIterator.cs

* Update EncryptionFeedIterator.cs
  • Loading branch information
kr-santosh authored Jul 26, 2022
1 parent 08bdfce commit 78fc16c
Show file tree
Hide file tree
Showing 13 changed files with 1,098 additions and 235 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<ClientPreviewVersion>3.29.0</ClientPreviewVersion>
<ClientPreviewSuffixVersion>preview</ClientPreviewSuffixVersion>
<DirectVersion>3.29.1</DirectVersion>
<EncryptionOfficialVersion>1.0.1</EncryptionOfficialVersion>
<EncryptionPreviewVersion>1.0.1</EncryptionPreviewVersion>
<EncryptionOfficialVersion>2.0.0</EncryptionOfficialVersion>
<EncryptionPreviewVersion>2.0.0</EncryptionPreviewVersion>
<EncryptionPreviewSuffixVersion>preview</EncryptionPreviewSuffixVersion>
<CustomEncryptionVersion>1.0.0-preview03</CustomEncryptionVersion>
<HybridRowVersion>1.1.0-preview3</HybridRowVersion>
Expand Down
9 changes: 9 additions & 0 deletions Microsoft.Azure.Cosmos.Encryption/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ Preview features are treated as a separate branch and will not be included in th
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### <a name="2.0.0"/> [2.0.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/2.0.0) - 2022-06-28

#### Added
- [#3241](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/3241) Adds code to support PartitionKey and Id encryption.

### <a name="2.0.0-preview"/> [2.0.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/2.0.0-preview) - 2022-06-28

#### Added
- [#3241](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/3241) Adds code to support PartitionKey and Id encryption.

### <a name="1.0.1"/> [1.0.1](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/1.0.1) - 2022-06-01

Expand Down
3 changes: 2 additions & 1 deletion Microsoft.Azure.Cosmos.Encryption/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ internal static class Constants
public const string DiagnosticsStartTime = "Start time";
public const string DocumentsResourcePropertyName = "Documents";
public const string IncorrectContainerRidSubStatus = "1024";
public const string PartitionKeyMismatch = "1001";

// TODO: Good to have constants available in the Cosmos SDK. Tracked via https://github.com/Azure/azure-cosmos-dotnet-v3/issues/2431
public const string IntendedCollectionHeader = "x-ms-cosmos-intended-collection-rid";
public const string IsClientEncryptedHeader = "x-ms-cosmos-is-client-encrypted";
public const string AllowCachedReadsHeader = "x-ms-cosmos-allow-cachedreads";
public const string DatabaseRidHeader = "x-ms-cosmos-database-rid";
public const string SubStatusHeader = "x-ms-substatus";
public const int SupportedClientEncryptionPolicyFormatVersion = 1;
public const int SupportedClientEncryptionPolicyFormatVersion = 2;
}
}
340 changes: 267 additions & 73 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs

Large diffs are not rendered by default.

170 changes: 163 additions & 7 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,128 @@ namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

internal sealed class EncryptionFeedIterator : FeedIterator
{
private readonly FeedIterator feedIterator;
private readonly SemaphoreSlim singleIteratorInitSema = new SemaphoreSlim(1, 1);
private readonly EncryptionContainer encryptionContainer;
private readonly RequestOptions requestOptions;
private readonly QueryDefinition queryDefinition;
private readonly string queryText;
private readonly string continuationToken;
private readonly FeedRange feedRange;
private readonly QueryType queryType;

private bool isIteratorInitialized = false;

public EncryptionFeedIterator(
FeedIterator feedIterator,
EncryptionContainer encryptionContainer,
RequestOptions requestOptions)
{
this.feedIterator = feedIterator ?? throw new ArgumentNullException(nameof(feedIterator));
this.FeedIterator = feedIterator ?? throw new ArgumentNullException(nameof(feedIterator));
this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer));
this.requestOptions = requestOptions ?? throw new ArgumentNullException(nameof(requestOptions));
}

public override bool HasMoreResults => this.feedIterator.HasMoreResults;
public EncryptionFeedIterator(
EncryptionContainer encryptionContainer,
QueryDefinition queryDefinition,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer));
this.queryDefinition = queryDefinition;
this.continuationToken = continuationToken;
this.requestOptions = requestOptions;

this.FeedIterator = encryptionContainer.Container.GetItemQueryStreamIterator(
queryDefinition,
continuationToken,
requestOptions);

this.queryType = QueryType.QueryDefinitionType;
}

public EncryptionFeedIterator(
EncryptionContainer encryptionContainer,
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer));
this.queryText = queryText;
this.continuationToken = continuationToken;
this.requestOptions = requestOptions;

this.FeedIterator = encryptionContainer.Container.GetItemQueryStreamIterator(
queryText,
continuationToken,
requestOptions);

this.queryType = QueryType.QueryTextType;
}

public EncryptionFeedIterator(
EncryptionContainer encryptionContainer,
ChangeFeedStartFrom changeFeedStartFrom,
ChangeFeedMode changeFeedMode,
ChangeFeedRequestOptions changeFeedRequestOptions = null)
{
this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer));
this.requestOptions = changeFeedRequestOptions;
this.FeedIterator = encryptionContainer.Container.GetChangeFeedStreamIterator(
changeFeedStartFrom,
changeFeedMode,
changeFeedRequestOptions);
}

public EncryptionFeedIterator(
EncryptionContainer encryptionContainer,
FeedRange feedRange,
QueryDefinition queryDefinition,
string continuationToken,
QueryRequestOptions requestOptions = null)
{
this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer));
this.feedRange = feedRange;
this.queryDefinition = queryDefinition;
this.continuationToken = continuationToken;
this.requestOptions = requestOptions;

this.FeedIterator = encryptionContainer.Container.GetItemQueryStreamIterator(
feedRange,
queryDefinition,
continuationToken,
requestOptions);

this.queryType = QueryType.QueryDefinitionWithFeedRangeType;
}

private enum QueryType
{
QueryWithoutEncryptedPk = 0,
QueryTextType = 1,
QueryDefinitionType = 2,
QueryDefinitionWithFeedRangeType = 3,
}

public override bool HasMoreResults => this.FeedIterator.HasMoreResults;

private FeedIterator FeedIterator { get; set; }

public override async Task<ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default)
{
EncryptionSettings encryptionSettings = await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken);
encryptionSettings.SetRequestHeaders(this.requestOptions);
await this.GetIteratorWithEncryptionHeaderAndEncryptPartitionKeyIfRequiredAsync(encryptionSettings);

ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken);
ResponseMessage responseMessage = await this.FeedIterator.ReadNextAsync(cancellationToken);

EncryptionDiagnosticsContext encryptionDiagnosticsContext = new EncryptionDiagnosticsContext();

// check for Bad Request and Wrong RID intended and update the cached RID and Client Encryption Policy.
// check for Bad Request and Wrong intended RID and update the cached RID and Client Encryption Policy.
await this.encryptionContainer.ThrowIfRequestNeedsARetryPostPolicyRefreshAsync(responseMessage, encryptionSettings, encryptionDiagnosticsContext, cancellationToken);

if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null)
Expand All @@ -55,5 +145,71 @@ public override async Task<ResponseMessage> ReadNextAsync(CancellationToken canc

return responseMessage;
}

private async Task GetIteratorWithEncryptionHeaderAndEncryptPartitionKeyIfRequiredAsync(EncryptionSettings encryptionSettings)
{
encryptionSettings.SetRequestHeaders(this.requestOptions);

// should be fine, flag is set at the end of init
if (this.isIteratorInitialized || this.queryType == QueryType.QueryWithoutEncryptedPk)
{
return;
}

if (await this.singleIteratorInitSema.WaitAsync(-1))
{
if (!this.isIteratorInitialized)
{
try
{
if (this.requestOptions is QueryRequestOptions queryRequestOptions)
{
if (queryRequestOptions != null && queryRequestOptions.PartitionKey.HasValue)
{
(queryRequestOptions.PartitionKey, bool isPkEncrypted) = await this.encryptionContainer.CheckIfPkIsEncryptedAndGetEncryptedPkAsync(
queryRequestOptions.PartitionKey.Value,
encryptionSettings,
cancellationToken: default);

if (!isPkEncrypted)
{
this.isIteratorInitialized = true;
return;
}
}

// we rebuild iterators which take in request options with partiton key and if partition key was encrypted.
this.FeedIterator = this.queryType switch
{
QueryType.QueryTextType => this.encryptionContainer.Container.GetItemQueryStreamIterator(
this.queryText,
this.continuationToken,
queryRequestOptions),
QueryType.QueryDefinitionType => this.encryptionContainer.Container.GetItemQueryStreamIterator(
this.queryDefinition,
this.continuationToken,
queryRequestOptions),
QueryType.QueryDefinitionWithFeedRangeType => this.encryptionContainer.Container.GetItemQueryStreamIterator(
this.feedRange,
this.queryDefinition,
this.continuationToken,
queryRequestOptions),
_ => this.FeedIterator,
};
}

this.isIteratorInitialized = true;
}
finally
{
this.singleIteratorInitSema.Release(1);
}
}
else
{
this.singleIteratorInitSema.Release(1);
}
}
}
}
}
Loading

0 comments on commit 78fc16c

Please sign in to comment.