Skip to content

Commit 007a343

Browse files
auto commit
1 parent db45578 commit 007a343

File tree

10 files changed

+698
-46
lines changed

10 files changed

+698
-46
lines changed

.github/workflows/ci_examples_net.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,4 @@ jobs:
9494
shell: bash
9595
run: |
9696
dotnet run
97+
dotnet test

Examples/runtimes/net/src/migration/PlaintextToAWSDBE/MigrationUtils.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ This class contains shared functionality used by all migration steps.
1010
*/
1111
public class MigrationUtils
1212
{
13+
// Common attribute values used across all migration steps
14+
public static readonly string ENCRYPTED_AND_SIGNED_VALUE = "this will be encrypted and signed";
15+
public static readonly string SIGN_ONLY_VALUE = "this will never be encrypted, but it will be signed";
16+
public static readonly string DO_NOTHING_VALUE = "this will never be encrypted nor signed";
17+
1318
// Verify that a returned item matches the expected values
1419
public static bool VerifyReturnedItem(GetItemResponse response, string partitionKeyValue, string sortKeyValue,
15-
string encryptedAndSignedValue, string signOnlyValue, string doNothingValue)
20+
string encryptedAndSignedValue = null, string signOnlyValue = null, string doNothingValue = null)
1621
{
22+
// Use default values if not provided
23+
encryptedAndSignedValue = encryptedAndSignedValue ?? ENCRYPTED_AND_SIGNED_VALUE;
24+
signOnlyValue = signOnlyValue ?? SIGN_ONLY_VALUE;
25+
doNothingValue = doNothingValue ?? DO_NOTHING_VALUE;
26+
1727
var item = response.Item;
1828

1929
// Verify partition key

Examples/runtimes/net/src/migration/PlaintextToAWSDBE/awsdbe/MigrationStep1.cs

Lines changed: 157 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,174 @@ namespace Examples.migration.PlaintextToAWSDBE.awsdbe
1616
Migration Step 1: This is the first step in the migration process from
1717
plaintext to encrypted DynamoDB using the AWS Database Encryption SDK.
1818
19-
This step will be implemented in the future. It will demonstrate how to:
20-
1. Configure a DynamoDB client with encryption capabilities
21-
2. Write items that can still be read by plaintext clients (Step 0)
22-
3. Read both plaintext items and items written by this step
19+
In this example, we configure a DynamoDB Encryption client to do the following:
20+
1. Write items only in plaintext
21+
2. Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration
22+
23+
While this step configures your client to be ready to start reading encrypted items,
24+
we do not yet expect to be reading any encrypted items,
25+
as our client still writes plaintext items.
26+
Before you move on to step 2, ensure that these changes have successfully been deployed
27+
to all of your readers.
2328
2429
Running this example requires access to the DDB Table whose name
2530
is provided in the function parameter.
2631
This table must be configured with the following
2732
primary key configuration:
2833
- Partition key is named "partition_key" with type (S)
29-
- Sort key is named "sort_key" with type (S)
34+
- Sort key is named "sort_key" with type (N)
3035
*/
3136
public class MigrationStep1
3237
{
33-
// This method will be implemented in the future
3438
public static async Task<bool> MigrationStep1Example(string kmsKeyId, string ddbTableName, string partitionKeyValue, string sortKeyWriteValue, string sortKeyReadValue)
3539
{
36-
// TODO: Implement MigrationStep1
37-
throw new NotImplementedException("MigrationStep1 is not yet implemented");
40+
try
41+
{
42+
// 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
43+
// For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
44+
// We will use the `CreateMrkMultiKeyring` method to create this keyring,
45+
// as it will correctly handle both single region and Multi-Region KMS Keys.
46+
var matProv = new MaterialProviders(new MaterialProvidersConfig());
47+
var keyringInput = new CreateAwsKmsMrkMultiKeyringInput { Generator = kmsKeyId };
48+
var kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput);
49+
50+
// 2. Configure which attributes are encrypted and/or signed when writing new items.
51+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
52+
// we must explicitly configure how they should be treated during item encryption:
53+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
54+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
55+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
56+
var attributeActionsOnEncrypt = new Dictionary<string, CryptoAction>
57+
{
58+
["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY
59+
["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY
60+
["attribute1"] = CryptoAction.ENCRYPT_AND_SIGN,
61+
["attribute2"] = CryptoAction.SIGN_ONLY,
62+
["attribute3"] = CryptoAction.DO_NOTHING
63+
};
64+
65+
// 3. Configure which attributes we expect to be excluded in the signature
66+
// when reading items. There are two options for configuring this:
67+
//
68+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
69+
// When defining your DynamoDb schema and deciding on attribute names,
70+
// choose a distinguishing prefix (such as ":") for all attributes that
71+
// you do not want to include in the signature.
72+
// This has two main benefits:
73+
// - It is easier to reason about the security and authenticity of data within your item
74+
// when all unauthenticated data is easily distinguishable by their attribute name.
75+
// - If you need to add new unauthenticated attributes in the future,
76+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
77+
// and immediately start writing to that new attribute, without
78+
// any other configuration update needed.
79+
// Once you configure this field, it is not safe to update it.
80+
//
81+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
82+
// a set of attributes that should be considered unauthenticated when encountered
83+
// on read. Be careful if you use this configuration. Do not remove an attribute
84+
// name from this configuration, even if you are no longer writing with that attribute,
85+
// as old items may still include this attribute, and our configuration needs to know
86+
// to continue to exclude this attribute from the signature scope.
87+
// If you add new attribute names to this field, you must first deploy the update to this
88+
// field to all readers in your host fleet before deploying the update to start writing
89+
// with that new attribute.
90+
//
91+
// For this example, we will explicitly list the attributes that are not signed.
92+
var unsignedAttributes = new List<string> { "attribute3" };
93+
94+
// 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
95+
// This configuration uses PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ
96+
// which means:
97+
// - Write: Items are forced to be written as plaintext.
98+
// Items may not be written as encrypted items.
99+
// - Read: Items are allowed to be read as plaintext.
100+
// Items are allowed to be read as encrypted items.
101+
var tableConfig = new DynamoDbTableEncryptionConfig
102+
{
103+
LogicalTableName = ddbTableName,
104+
PartitionKeyName = "partition_key",
105+
SortKeyName = "sort_key",
106+
AttributeActionsOnEncrypt = attributeActionsOnEncrypt,
107+
Keyring = kmsKeyring,
108+
AllowedUnsignedAttributes = unsignedAttributes,
109+
PlaintextOverride = PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ
110+
};
111+
112+
var tableConfigs = new Dictionary<string, DynamoDbTableEncryptionConfig>
113+
{
114+
[ddbTableName] = tableConfig
115+
};
116+
117+
// 5. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
118+
var ddb = new Client.DynamoDbClient(
119+
new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs });
120+
121+
// 6. Put an item into our table using the above client.
122+
// This item will be stored in plaintext due to our PlaintextOverride configuration.
123+
string encryptedAndSignedValue = MigrationUtils.ENCRYPTED_AND_SIGNED_VALUE;
124+
string signOnlyValue = MigrationUtils.SIGN_ONLY_VALUE;
125+
string doNothingValue = MigrationUtils.DO_NOTHING_VALUE;
126+
var item = new Dictionary<string, AttributeValue>
127+
{
128+
["partition_key"] = new AttributeValue { S = partitionKeyValue },
129+
["sort_key"] = new AttributeValue { N = sortKeyWriteValue },
130+
["attribute1"] = new AttributeValue { S = encryptedAndSignedValue },
131+
["attribute2"] = new AttributeValue { S = signOnlyValue },
132+
["attribute3"] = new AttributeValue { S = doNothingValue }
133+
};
134+
135+
var putRequest = new PutItemRequest
136+
{
137+
TableName = ddbTableName,
138+
Item = item
139+
};
140+
141+
var putResponse = await ddb.PutItemAsync(putRequest);
142+
Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK);
143+
144+
// 7. Get an item back from the table using the same client.
145+
// If this is an item written in plaintext (i.e. any item written
146+
// during Step 0 or 1), then the item will still be in plaintext.
147+
// If this is an item that was encrypted client-side (i.e. any item written
148+
// during Step 2 or after), then the item will be decrypted client-side
149+
// and surfaced as a plaintext item.
150+
var key = new Dictionary<string, AttributeValue>
151+
{
152+
["partition_key"] = new AttributeValue { S = partitionKeyValue },
153+
["sort_key"] = new AttributeValue { N = sortKeyReadValue }
154+
};
155+
156+
var getRequest = new GetItemRequest
157+
{
158+
TableName = ddbTableName,
159+
Key = key,
160+
// In this example we configure a strongly consistent read
161+
// because we perform a read immediately after a write (for demonstrative purposes).
162+
// By default, reads are only eventually consistent.
163+
ConsistentRead = true
164+
};
165+
166+
var getResponse = await ddb.GetItemAsync(getRequest);
167+
Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK);
168+
169+
// 8. Verify we get the expected item back
170+
if (getResponse.Item == null)
171+
{
172+
throw new Exception("No item found");
173+
}
174+
175+
bool success = MigrationUtils.VerifyReturnedItem(getResponse, partitionKeyValue, sortKeyReadValue);
176+
if (success)
177+
{
178+
Console.WriteLine("MigrationStep1 completed successfully");
179+
}
180+
return success;
181+
}
182+
catch (Exception e)
183+
{
184+
Console.WriteLine($"Error in MigrationStep1: {e.Message}");
185+
throw;
186+
}
38187
}
39188
}
40189
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Amazon.DynamoDBv2;
5+
using Amazon.DynamoDBv2.Model;
6+
using System.Diagnostics;
7+
using Xunit;
8+
using Examples.migration.PlaintextToAWSDBE;
9+
using Examples.migration.PlaintextToAWSDBE.plaintext;
10+
11+
namespace Examples.migration.PlaintextToAWSDBE.awsdbe
12+
{
13+
/*
14+
Test for Migration Step 1: This tests the first step in the
15+
plaintext-to-encrypted database migration.
16+
17+
This test verifies that:
18+
1. Step 1 can successfully write plaintext items
19+
2. Step 1 can read items written by Step 0 (plaintext)
20+
3. Step 1 can read items written by itself (plaintext)
21+
4. Step 1 can read items written by Step 2 (encrypted)
22+
5. Step 1 can read items written by Step 3 (encrypted)
23+
24+
Running this test requires access to the DDB Table whose name
25+
is provided by TestUtils.TEST_DDB_TABLE_NAME.
26+
This table must be configured with the following
27+
primary key configuration:
28+
- Partition key is named "partition_key" with type (S)
29+
- Sort key is named "sort_key" with type (N)
30+
*/
31+
public class MigrationStep1Test
32+
{
33+
[Fact]
34+
public async Task TestMigrationStep1()
35+
{
36+
string kmsKeyID = TestUtils.TEST_KMS_KEY_ID;
37+
string tableName = TestUtils.TEST_DDB_TABLE_NAME;
38+
string partitionKey = Guid.NewGuid().ToString();
39+
string[] sortKeys = { "0", "1", "2", "3" };
40+
41+
try
42+
{
43+
// Given: Step 0 has succeeded
44+
bool success = await MigrationStep0.MigrationStep0Example(tableName, partitionKey, sortKeys[0], sortKeys[0]);
45+
Assert.True(success, "MigrationStep0 should complete successfully");
46+
47+
// Successfully executes step 1
48+
success = await MigrationStep1.MigrationStep1Example(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[1]);
49+
Assert.True(success, "MigrationStep1 should complete successfully");
50+
51+
// When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read plaintext values from Step 0)
52+
success = await MigrationStep1.MigrationStep1Example(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[0]);
53+
Assert.True(success, "MigrationStep1 should be able to read items written by Step 0");
54+
55+
// Given: Step 2 has succeeded
56+
success = await MigrationStep2.MigrationStep2Example(kmsKeyID, tableName, partitionKey, sortKeys[2], sortKeys[2]);
57+
Assert.True(success, "MigrationStep2 should complete successfully");
58+
59+
// When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read encrypted values from Step 2)
60+
success = await MigrationStep1.MigrationStep1Example(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[2]);
61+
Assert.True(success, "MigrationStep1 should be able to read items written by Step 2");
62+
63+
// Given: Step 3 has succeeded
64+
success = await MigrationStep3.MigrationStep3Example(kmsKeyID, tableName, partitionKey, sortKeys[3], sortKeys[3]);
65+
Assert.True(success, "MigrationStep3 should complete successfully");
66+
67+
// When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read encrypted values from Step 3)
68+
success = await MigrationStep1.MigrationStep1Example(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[3]);
69+
Assert.True(success, "MigrationStep1 should be able to read items written by Step 3");
70+
}
71+
finally
72+
{
73+
// Cleanup
74+
await MigrationUtils.CleanupItems(tableName, partitionKey, sortKeys);
75+
}
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)