Skip to content

Commit

Permalink
[CosmosDB] Adds support to allow partition key and id paths to be par…
Browse files Browse the repository at this point in the history
…t of client encryption policy. (#18633)

* Support client encryption policy during container creation

* Update SqlOperationsTests.ps1

* Update New-AzCosmosDBSqlContainer.md

* Update PSClientEncryptionPolicy.cs

* Update NewAzCosmosDBSqlContainer.cs

* Update ChangeLog.md

* Updated package.

* Update CosmosDB.Test.csproj

* Updated example, session records.

* updated session records.

* Fixes update container command on containers with client encryption policy.

* Update ChangeLog.md

* Update ChangeLog.md

* Support partition key and id paths to be part of client encryption policy

* Update ChangeLog.md

* Update New-AzCosmosDBSqlContainer.md

* Update PSClientEncryptionPolicy.cs

* Update src/CosmosDB/CosmosDB/ChangeLog.md

Co-authored-by: Beisi Zhou <zhoubeisi@gmail.com>

Co-authored-by: Beisi Zhou <zhoubeisi@gmail.com>
Co-authored-by: Yabo Hu <yabhu@microsoft.com>
  • Loading branch information
3 people authored Jun 27, 2022
1 parent 6568795 commit 8b22c86
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/CosmosDB/CosmosDB/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-->

## Upcoming Release
* Added support for partition key and id paths to be part of client encryption policy.
* Fixed bug related to Update-AzCosmosDBSqlContainer command on containers with Client Encryption Policy.

## Version 1.9.0
Expand Down
59 changes: 46 additions & 13 deletions src/CosmosDB/CosmosDB/Models/PSClientEncryptionPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public PSClientEncryptionPolicy(ClientEncryptionPolicy clientEncryptionPolicy)

if (ModelHelper.IsNotNullOrEmpty(clientEncryptionPolicy.IncludedPaths))
{
PSClientEncryptionPolicy.ValidateIncludedPaths(clientEncryptionPolicy.IncludedPaths);
PSClientEncryptionPolicy.ValidateIncludedPaths(clientEncryptionPolicy.IncludedPaths, (int)clientEncryptionPolicy.PolicyFormatVersion);

IncludedPaths = new List<PSClientEncryptionIncludedPath>();
foreach (ClientEncryptionIncludedPath key in clientEncryptionPolicy.IncludedPaths)
Expand Down Expand Up @@ -87,9 +87,12 @@ public static ClientEncryptionPolicy ToSDKModel(PSClientEncryptionPolicy pSClien
}
}

PSClientEncryptionPolicy.ValidatePartitionKeyPathsAreNotEncrypted(clientEncryptionPolicy.IncludedPaths, partitionKeyPathTokens);
PSClientEncryptionPolicy.ValidatePartitionKeyPathsAreNotEncrypted(
clientEncryptionPolicy.IncludedPaths,
partitionKeyPathTokens,
pSClientEncryptionPolicy.PolicyFormatVersion);

if(clientEncryptionPolicy.PolicyFormatVersion != 1)
if(clientEncryptionPolicy.PolicyFormatVersion < 1 || clientEncryptionPolicy.PolicyFormatVersion > 2)
{
throw new InvalidOperationException($"Invalid PolicyFormatVersion:{clientEncryptionPolicy.PolicyFormatVersion} used in Client Encryption Policy. ");
}
Expand All @@ -102,26 +105,44 @@ public static ClientEncryptionPolicy ToSDKModel(PSClientEncryptionPolicy pSClien
/// </summary>
/// <param name="clientEncryptionIncludedPath">Included paths of the client encryption policy.</param>
/// <param name="partitionKeyPathTokens">Tokens corresponding to validated partition key.</param>
private static void ValidatePartitionKeyPathsAreNotEncrypted(IEnumerable<ClientEncryptionIncludedPath> clientEncryptionIncludedPath, List<string> partitionKeyPathTokens)
/// <param name="policyFormatVersion">Client encryption policy format version.</param>
private static void ValidatePartitionKeyPathsAreNotEncrypted(
IEnumerable<ClientEncryptionIncludedPath> clientEncryptionIncludedPath,
List<string> partitionKeyPathTokens,
int policyFormatVersion)
{
Debug.Assert(partitionKeyPathTokens != null);
IEnumerable<string> propertiesToEncrypt = clientEncryptionIncludedPath.Select(p => p.Path.Substring(1));

foreach (string tokenInPath in partitionKeyPathTokens)
{
Debug.Assert(String.IsNullOrEmpty(tokenInPath));
if (propertiesToEncrypt.Contains(tokenInPath.Substring(1)))
Debug.Assert(String.IsNullOrEmpty(tokenInPath));

string topLevelPath = tokenInPath.Split('/')[1];
// paths in included paths start with "/". Get the ClientEncryptionIncludedPath and validate.
IEnumerable<ClientEncryptionIncludedPath> encryptedPartitionKeyPath = clientEncryptionIncludedPath.Where(p => p.Path.Substring(1).Equals(topLevelPath));

if (encryptedPartitionKeyPath.Any())
{
throw new ArgumentException($"Paths which are part of the partition key({tokenInPath}) may not be included in the Client Encryption Policy. ");
if (policyFormatVersion < 2)
{
throw new ArgumentException($"Path: {encryptedPartitionKeyPath.Select(et => et.Path).FirstOrDefault()} which is part of the partition key cannot be encrypted with PolicyFormatVersion: {policyFormatVersion}. Please use PolicyFormatVersion: 2. ");
}

// for the ClientEncryptionIncludedPath found check the encryption type.
if (encryptedPartitionKeyPath.Select(et => et.EncryptionType).FirstOrDefault() != "Deterministic")
{
throw new ArgumentException($"Path: {encryptedPartitionKeyPath.Select(et => et.Path).FirstOrDefault()} which is part of the partition key has to be encrypted with Deterministic type Encryption.");
}
}
}
}

private static void ValidateIncludedPaths(IEnumerable<ClientEncryptionIncludedPath> clientEncryptionIncludedPath)
private static void ValidateIncludedPaths(IEnumerable<ClientEncryptionIncludedPath> clientEncryptionIncludedPath, int policyFormatVersion)
{
List<string> includedPathsList = new List<string>();
foreach (ClientEncryptionIncludedPath path in clientEncryptionIncludedPath)
{
PSClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path);
PSClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path, policyFormatVersion);
if (includedPathsList.Contains(path.Path))
{
throw new ArgumentException($"Duplicate Path({path.Path}) found.", nameof(clientEncryptionIncludedPath));
Expand All @@ -131,7 +152,7 @@ private static void ValidateIncludedPaths(IEnumerable<ClientEncryptionIncludedPa
}
}

private static void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath)
private static void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath, int policyFormatVersion)
{
if (clientEncryptionIncludedPath == null)
{
Expand All @@ -144,8 +165,7 @@ private static void ValidateClientEncryptionIncludedPath(ClientEncryptionInclude
}

if (clientEncryptionIncludedPath.Path[0] != '/'
|| clientEncryptionIncludedPath.Path.LastIndexOf('/') != 0
|| string.Equals(clientEncryptionIncludedPath.Path.Substring(1), "id"))
|| clientEncryptionIncludedPath.Path.LastIndexOf('/') != 0)
{
throw new ArgumentException($"Invalid path '{clientEncryptionIncludedPath.Path ?? string.Empty}'.");
}
Expand All @@ -160,6 +180,19 @@ private static void ValidateClientEncryptionIncludedPath(ClientEncryptionInclude
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.EncryptionType));
}

if (string.Equals(clientEncryptionIncludedPath.Path.Substring(1), "id"))
{
if (policyFormatVersion < 2)
{
throw new ArgumentException($"Path: {clientEncryptionIncludedPath.Path} cannot be encrypted with PolicyFormatVersion: {policyFormatVersion}. Please use PolicyFormatVersion: 2. ");
}

if (clientEncryptionIncludedPath.EncryptionType != "Deterministic")
{
throw new ArgumentException($"Only Deterministic encryption type is supported for path: {clientEncryptionIncludedPath.Path}. ");
}
}

if (!string.Equals(clientEncryptionIncludedPath.EncryptionType, "Deterministic") &&
!string.Equals(clientEncryptionIncludedPath.EncryptionType, "Randomized"))
{
Expand Down
22 changes: 18 additions & 4 deletions src/CosmosDB/CosmosDB/Models/Sql/PSSqlClientEncryptionPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// limitations under the License.
// ----------------------------------------------------------------------------------
using Microsoft.Azure.Management.CosmosDB.Models;
using System;

namespace Microsoft.Azure.Commands.CosmosDB.Models
{
Expand All @@ -20,15 +21,28 @@ public class PSSqlClientEncryptionPolicy : PSClientEncryptionPolicy
{
public PSSqlClientEncryptionPolicy() : base()
{
}
}

public PSSqlClientEncryptionPolicy(ClientEncryptionPolicy clientEncryptionPolicy) : base(PSSqlClientEncryptionPolicy.SetSupportedPolicyVersionOnClientEncryptionPolicy(clientEncryptionPolicy))
public PSSqlClientEncryptionPolicy(ClientEncryptionPolicy clientEncryptionPolicy) : base(PSSqlClientEncryptionPolicy.SetSupportedPolicyVersionOnClientEncryptionPolicy(clientEncryptionPolicy, clientEncryptionPolicy.PolicyFormatVersion))
{
}

private static ClientEncryptionPolicy SetSupportedPolicyVersionOnClientEncryptionPolicy(ClientEncryptionPolicy clientEncryptionPolicy)
private static ClientEncryptionPolicy SetSupportedPolicyVersionOnClientEncryptionPolicy(ClientEncryptionPolicy clientEncryptionPolicy, int? policyFormatVersion = null)
{
clientEncryptionPolicy.PolicyFormatVersion = 1;
if (policyFormatVersion == null)
{
clientEncryptionPolicy.PolicyFormatVersion = 1;
}
else
{
if(policyFormatVersion > 2 || policyFormatVersion < 1)
{
throw new ArgumentException($"Supported versions of client encryption policy are 1 and 2. ");
}

clientEncryptionPolicy.PolicyFormatVersion = policyFormatVersion;
}

return clientEncryptionPolicy;
}
}
Expand Down
17 changes: 8 additions & 9 deletions src/CosmosDB/CosmosDB/help/New-AzCosmosDBSqlContainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,14 @@ Resource : Microsoft.Azure.Commands.CosmosDB.Models.PSSqlContainerGetPropertiesR

### Example 2: Create a new CosmosDB Sql Container with Client Encryption Policy
```powershell
$includedPath1 = [Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]::new("/path1","key1","Deterministic","AEAD_AES_256_CBC_HMAC_SHA256");
$includedPath2 = [Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]::new("/path2","key2","Randomized","AEAD_AES_256_CBC_HMAC_SHA256");
$listofIncludedPaths = New-Object Collections.Generic.List[Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]
$listofIncludedPaths.Add($includedPath1)
$listofIncludedPaths.Add($includedPath2)
$newClientEncryptionPolicy = New-Object Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionPolicy
$newClientEncryptionPolicy.IncludedPaths = $listofIncludedPaths
$newPSSqlClientEncryptionPolicy = [Microsoft.Azure.Commands.CosmosDB.Models.PSSqlClientEncryptionPolicy]::new($newClientEncryptionPolicy)
New-AzCosmosDBSqlContainer -AccountName myAccountName -DatabaseName myDatabaseName -ResourceGroupName myRgName -Name myContainerName -PartitionKeyPath /a/b/c -PartitionKeyKind Hash -ClientEncryptionPolicy $newPSSqlClientEncryptionPolicy
PS C:\> $includedPath1 = [Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]::new("/path1","key1","Deterministic","AEAD_AES_256_CBC_HMAC_SHA256");
PS C:\> $includedPath2 = [Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]::new("/path2","key2","Randomized","AEAD_AES_256_CBC_HMAC_SHA256");
PS C:\> $listofIncludedPaths = New-Object Collections.Generic.List[Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionIncludedPath]
PS C:\> $listofIncludedPaths.Add($includedPath1)
PS C:\> $listofIncludedPaths.Add($includedPath2)
PS C:\> $newClientEncryptionPolicy = [Microsoft.Azure.Management.CosmosDB.Models.ClientEncryptionPolicy]::new($listofIncludedPaths, 2)
PS C:\> $newPSSqlClientEncryptionPolicy = [Microsoft.Azure.Commands.CosmosDB.Models.PSSqlClientEncryptionPolicy]::new($newClientEncryptionPolicy)
PS C:\> New-AzCosmosDBSqlContainer -AccountName myAccountName -DatabaseName myDatabaseName -ResourceGroupName myRgName -Name myContainerName -PartitionKeyPath /a/b/c -PartitionKeyKind Hash -ClientEncryptionPolicy $newPSSqlClientEncryptionPolicy
```

```output
Expand Down

0 comments on commit 8b22c86

Please sign in to comment.