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

Client Encryption: Adds fix to allow partition key path and id to be part of client encryption policy. #3211

Merged
merged 18 commits into from
Jun 9, 2022
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
23 changes: 23 additions & 0 deletions Microsoft.Azure.Cosmos/src/PartitionKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ namespace Microsoft.Azure.Cosmos
/// </summary>
public static readonly string SystemKeyPath = Documents.PartitionKey.SystemKeyPath;

/// <summary>
/// The raw partition key value.
/// </summary>
public object RawPartitionKeyValue { get; }
kr-santosh marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets the value provided at initialization.
/// </summary>
Expand All @@ -57,10 +62,12 @@ public PartitionKey(string partitionKeyValue)
if (partitionKeyValue == null)
{
this.InternalKey = PartitionKey.NullPartitionKeyInternal;
this.RawPartitionKeyValue = null;
}
else
{
this.InternalKey = new Documents.PartitionKey(partitionKeyValue).InternalKey;
this.RawPartitionKeyValue = partitionKeyValue;
}
this.IsNone = false;
}
Expand All @@ -73,6 +80,7 @@ public PartitionKey(bool partitionKeyValue)
{
this.InternalKey = partitionKeyValue ? TruePartitionKeyInternal : FalsePartitionKeyInternal;
this.IsNone = false;
this.RawPartitionKeyValue = partitionKeyValue;
}

/// <summary>
Expand All @@ -83,6 +91,18 @@ public PartitionKey(double partitionKeyValue)
{
this.InternalKey = new Documents.PartitionKey(partitionKeyValue).InternalKey;
this.IsNone = false;
this.RawPartitionKeyValue = partitionKeyValue;
}

/// <summary>
/// Creates a new partition key value.
/// </summary>
/// <param name="partitionKeyValue">The value to use as partition key.</param>
public PartitionKey(long partitionKeyValue)
kr-santosh marked this conversation as resolved.
Show resolved Hide resolved
{
this.InternalKey = new Documents.PartitionKey(partitionKeyValue).InternalKey;
this.IsNone = false;
this.RawPartitionKeyValue = partitionKeyValue;
}

/// <summary>
Expand All @@ -93,6 +113,7 @@ internal PartitionKey(object value)
{
this.InternalKey = new Documents.PartitionKey(value).InternalKey;
this.IsNone = false;
this.RawPartitionKeyValue = value;
}

/// <summary>
Expand All @@ -103,6 +124,7 @@ internal PartitionKey(PartitionKeyInternal partitionKeyInternal)
{
this.InternalKey = partitionKeyInternal;
this.IsNone = false;
this.RawPartitionKeyValue = partitionKeyInternal;
}

/// <summary>
Expand All @@ -114,6 +136,7 @@ private PartitionKey(PartitionKeyInternal partitionKeyInternal, bool isNone = fa
{
this.InternalKey = partitionKeyInternal;
this.IsNone = isNone;
this.RawPartitionKeyValue = partitionKeyInternal;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public ClientEncryptionPolicy(IEnumerable<ClientEncryptionIncludedPath> included
{
ClientEncryptionPolicy.ValidateIncludedPaths(includedPaths);
this.IncludedPaths = includedPaths;
this.PolicyFormatVersion = 1;
this.PolicyFormatVersion = 2;
kr-santosh marked this conversation as resolved.
Show resolved Hide resolved
}

[JsonConstructor]
Expand Down Expand Up @@ -55,22 +55,22 @@ public IEnumerable<ClientEncryptionIncludedPath> IncludedPaths
internal IDictionary<string, JToken> AdditionalProperties { get; private set; }

/// <summary>
/// Ensures that partition key paths are not specified in the client encryption policy for encryption.
/// Ensures that partition key paths specified in the client encryption policy for encryption are encrypted using Deterministic encryption algorithm.
/// </summary>
/// <param name="partitionKeyPathTokens">Tokens corresponding to validated partition key.</param>
internal void ValidatePartitionKeyPathsAreNotEncrypted(IReadOnlyList<IReadOnlyList<string>> partitionKeyPathTokens)
internal void ValidatePartitionKeyPathsIfEncrypted(IReadOnlyList<IReadOnlyList<string>> partitionKeyPathTokens)
{
Debug.Assert(partitionKeyPathTokens != null);
IEnumerable<string> propertiesToEncrypt = this.IncludedPaths.Select(p => p.Path.Substring(1));

foreach (IReadOnlyList<string> tokensInPath in partitionKeyPathTokens)
{
Debug.Assert(tokensInPath != null);
if (tokensInPath.Count > 0)
{
string topLevelToken = tokensInPath.First();
if (propertiesToEncrypt.Contains(topLevelToken))
if (string.Equals(this.IncludedPaths.Where(p => p.Path.Substring(1).Equals(topLevelToken)).Select(et => et.EncryptionType).FirstOrDefault(), "Randomized"))
kr-santosh marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentException($"Paths which are part of the partition key may not be included in the {nameof(ClientEncryptionPolicy)}.", nameof(ContainerProperties.ClientEncryptionPolicy));
throw new ArgumentException($"Path: /{topLevelToken} which is part of the partition key has to be encrypted with Deterministic type Encryption.");
}
}
}
Expand Down Expand Up @@ -104,8 +104,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 @@ -120,6 +119,12 @@ private static void ValidateClientEncryptionIncludedPath(ClientEncryptionInclude
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.EncryptionType));
}

if (string.Equals(clientEncryptionIncludedPath.Path.Substring(1), "id") &&
string.Equals(clientEncryptionIncludedPath.EncryptionType, "Randomized"))
{
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
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ internal void ValidateRequiredProperties()

if (this.ClientEncryptionPolicy != null)
{
this.ClientEncryptionPolicy.ValidatePartitionKeyPathsAreNotEncrypted(this.PartitionKeyPathTokens);
this.ClientEncryptionPolicy.ValidatePartitionKeyPathsIfEncrypted(this.PartitionKeyPathTokens);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,48 @@ public async Task ClientEncryptionPolicyFailureTest()
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
EncryptionType = "Randomized"
},
new ClientEncryptionIncludedPath()
{
Path = "/path1",
ClientEncryptionKeyId = "dekId1",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
EncryptionType = "Randomized"
},
};

ContainerProperties setting = new ContainerProperties()
{
Id = containerName,
PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection<string> { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey)
};

await this.cosmosDatabase.CreateContainerAsync(setting);
Assert.Fail("Creating container should have failed.");
}
catch (ArgumentException ex)
{
Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key has to be encrypted with Deterministic type Encryption."));
}

try
{
Collection<ClientEncryptionIncludedPath> pathsToEncryptWithPartitionKey = new Collection<ClientEncryptionIncludedPath>()
{
new ClientEncryptionIncludedPath()
{
Path = partitionKeyPath,
ClientEncryptionKeyId = "dekId1",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
EncryptionType = "Deterministic"
},
new ClientEncryptionIncludedPath()
{
Path = "/id",
ClientEncryptionKeyId = "dekId1",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
EncryptionType = "Randomized"
},
};

ContainerProperties setting = new ContainerProperties()
Expand All @@ -1528,7 +1570,7 @@ public async Task ClientEncryptionPolicyFailureTest()
}
catch (ArgumentException ex)
{
Assert.IsTrue(ex.Message.Contains("Paths which are part of the partition key may not be included in the ClientEncryptionPolicy."));
Assert.IsTrue(ex.Message.Contains("Only Deterministic encryption type supported for path: /id. "));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public async Task ContainerContractTest()
Assert.AreEqual(4, spatialPath.SpatialTypes.Count); // All SpatialTypes are returned

Assert.AreEqual(1, responseProperties.ClientEncryptionPolicy.IncludedPaths.Count());
Assert.IsTrue(responseProperties.ClientEncryptionPolicy.PolicyFormatVersion <= 1);
Assert.IsTrue(responseProperties.ClientEncryptionPolicy.PolicyFormatVersion <= 2);
ClientEncryptionIncludedPath clientEncryptionIncludedPath = responseProperties.ClientEncryptionPolicy.IncludedPaths.First();
Assert.IsTrue(this.VerifyClientEncryptionIncludedPath(clientEncryptionIncludedPath1, clientEncryptionIncludedPath));
}
Expand Down