Repository: cloudcomponents/cdk-constructs (language: python)

Issue:
fix(dynamodb-seeder): Grant `encryptionKey` access
When a table has an `encryptionKey`, the `SingletonFunction` does not have the proper access.

The manual policy entry, should be replaced by `grantWriteData`, which gives the appropriate access to the KMS key if it exists.

```
props.table.grantWriteData(handler);
```

I can, and may put a PR in for this myself, but I ran into some test failures that I didn't have time to research at the moment, and wanted to get this documented in the very least.


Discussion / hints:

Fix patch:
diff --git a/packages/cdk-contentful-webhook/lambda-file-sizes.json b/packages/cdk-contentful-webhook/lambda-file-sizes.json
index 36e0f090a..7bce275c3 100644
--- a/packages/cdk-contentful-webhook/lambda-file-sizes.json
+++ b/packages/cdk-contentful-webhook/lambda-file-sizes.json
@@ -1,1 +1,1 @@
-[{"timestamp":1615923664936,"files":[{"filename":"contentful-webhook/index.js","previous":1050356,"size":231381,"diff":-818975}]},{"timestamp":1615923376037,"files":[{"filename":"contentful-webhook/index.js","previous":1047464,"size":1050356,"diff":2892}]},{"timestamp":1615809010241,"files":[{"filename":"contentful-webhook/index.js","previous":221347,"size":1047464,"diff":826117}]},{"timestamp":1615022511778,"files":[{"filename":"contentful-webhook/index.js","previous":221344,"size":221347,"diff":3}]},{"timestamp":1609970894222,"files":[{"filename":"contentful-webhook/index.js","previous":217379,"size":221344,"diff":3965}]},{"timestamp":1609970375754,"files":[{"filename":"contentful-webhook/index.js","previous":203415,"size":217379,"diff":13964}]},{"timestamp":1607360020128,"files":[{"filename":"contentful-webhook/index.js","previous":203418,"size":203415,"diff":-3}]},{"timestamp":1600966253111,"files":[{"filename":"contentful-webhook/index.js","previous":203440,"size":203418,"diff":-22}]},{"timestamp":1599246565057,"files":[{"filename":"contentful-webhook/index.js","previous":202993,"size":203440,"diff":447}]},{"timestamp":1598552652514,"files":[{"filename":"contentful-webhook/index.js","previous":202992,"size":202993,"diff":1}]},{"timestamp":1596542526761,"files":[{"filename":"contentful-webhook/index.js","previous":207102,"size":202992,"diff":-4110}]},{"timestamp":1596540034638,"files":[{"filename":"contentful-webhook/index.js","previous":206593,"size":207102,"diff":509}]},{"timestamp":1596457456615,"files":[{"filename":"contentful-webhook/index.js","previous":206357,"size":206593,"diff":236}]},{"timestamp":1596440551356,"files":[{"filename":"contentful-webhook/index.js","previous":206424,"size":206357,"diff":-67}]},{"timestamp":1596437759972,"files":[{"filename":"contentful-webhook/index.js","previous":541950,"size":206424,"diff":-335526}]},{"timestamp":1596413723535,"files":[{"filename":"contentful-webhook/index.js","previous":542071,"size":541950,"diff":-121}]},{"timestamp":1596407623809,"files":[{"filename":"contentful-webhook/index.js","previous":191169,"size":542071,"diff":350902}]}]
+[{"timestamp":1620265629961,"files":[{"filename":"contentful-webhook/index.js","previous":231381,"size":231292,"diff":-89}]},{"timestamp":1615923664936,"files":[{"filename":"contentful-webhook/index.js","previous":1050356,"size":231381,"diff":-818975}]},{"timestamp":1615923376037,"files":[{"filename":"contentful-webhook/index.js","previous":1047464,"size":1050356,"diff":2892}]},{"timestamp":1615809010241,"files":[{"filename":"contentful-webhook/index.js","previous":221347,"size":1047464,"diff":826117}]},{"timestamp":1615022511778,"files":[{"filename":"contentful-webhook/index.js","previous":221344,"size":221347,"diff":3}]},{"timestamp":1609970894222,"files":[{"filename":"contentful-webhook/index.js","previous":217379,"size":221344,"diff":3965}]},{"timestamp":1609970375754,"files":[{"filename":"contentful-webhook/index.js","previous":203415,"size":217379,"diff":13964}]},{"timestamp":1607360020128,"files":[{"filename":"contentful-webhook/index.js","previous":203418,"size":203415,"diff":-3}]},{"timestamp":1600966253111,"files":[{"filename":"contentful-webhook/index.js","previous":203440,"size":203418,"diff":-22}]},{"timestamp":1599246565057,"files":[{"filename":"contentful-webhook/index.js","previous":202993,"size":203440,"diff":447}]},{"timestamp":1598552652514,"files":[{"filename":"contentful-webhook/index.js","previous":202992,"size":202993,"diff":1}]},{"timestamp":1596542526761,"files":[{"filename":"contentful-webhook/index.js","previous":207102,"size":202992,"diff":-4110}]},{"timestamp":1596540034638,"files":[{"filename":"contentful-webhook/index.js","previous":206593,"size":207102,"diff":509}]},{"timestamp":1596457456615,"files":[{"filename":"contentful-webhook/index.js","previous":206357,"size":206593,"diff":236}]},{"timestamp":1596440551356,"files":[{"filename":"contentful-webhook/index.js","previous":206424,"size":206357,"diff":-67}]},{"timestamp":1596437759972,"files":[{"filename":"contentful-webhook/index.js","previous":541950,"size":206424,"diff":-335526}]},{"timestamp":1596413723535,"files":[{"filename":"contentful-webhook/index.js","previous":542071,"size":541950,"diff":-121}]},{"timestamp":1596407623809,"files":[{"filename":"contentful-webhook/index.js","previous":191169,"size":542071,"diff":350902}]}]
diff --git a/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts b/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
index 8603b7f2d..c0c196493 100644
--- a/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
+++ b/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
@@ -45,6 +45,16 @@ export class DynamoDBSeeder extends Construct {
       }),
     );
 
+    if (props.table.encryptionKey) {
+      handler.addToRolePolicy(
+        new PolicyStatement({
+          effect: Effect.ALLOW,
+          actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey', 'kms:CreateGrant'],
+          resources: [props.table.encryptionKey.keyArn],
+        }),
+      );
+    }
+
     if (seedsBucket) {
       const objectKey = seeds.s3Location?.objectKey ?? '*';
 
diff --git a/packages/cdk-github-webhook/lambda-file-sizes.json b/packages/cdk-github-webhook/lambda-file-sizes.json
index a4c00677e..852cf043c 100644
--- a/packages/cdk-github-webhook/lambda-file-sizes.json
+++ b/packages/cdk-github-webhook/lambda-file-sizes.json
@@ -1,1 +1,1 @@
-[{"timestamp":1619038419269,"files":[{"filename":"github-webhook/index.js","previous":191797,"size":191792,"diff":-5}]},{"timestamp":1617394285426,"files":[{"filename":"github-webhook/index.js","previous":191760,"size":191797,"diff":37}]},{"timestamp":1616697789259,"files":[{"filename":"github-webhook/index.js","previous":191665,"size":191760,"diff":95}]},{"timestamp":1615923673723,"files":[{"filename":"github-webhook/index.js","previous":1011459,"size":191665,"diff":-819794}]},{"timestamp":1615923385141,"files":[{"filename":"github-webhook/index.js","previous":1008634,"size":1011459,"diff":2825}]},{"timestamp":1615809019316,"files":[{"filename":"github-webhook/index.js","previous":182432,"size":1008634,"diff":826202}]},{"timestamp":1615023549795,"files":[{"filename":"github-webhook/index.js","previous":182255,"size":182432,"diff":177}]},{"timestamp":1615022566740,"files":[{"filename":"github-webhook/index.js","previous":181954,"size":182255,"diff":301}]},{"timestamp":1614104217580,"files":[{"filename":"github-webhook/index.js","previous":181904,"size":181954,"diff":50}]},{"timestamp":1612464188131,"files":[{"filename":"github-webhook/index.js","previous":181855,"size":181904,"diff":49}]},{"timestamp":1611769329263,"files":[{"filename":"github-webhook/index.js","previous":181805,"size":181855,"diff":50}]},{"timestamp":1609970949480,"files":[{"filename":"github-webhook/index.js","previous":179020,"size":181805,"diff":2785}]},{"timestamp":1607360063925,"files":[{"filename":"github-webhook/index.js","previous":178533,"size":179020,"diff":487}]},{"timestamp":1606896280100,"files":[{"filename":"github-webhook/index.js","previous":177980,"size":178533,"diff":553}]},{"timestamp":1606329557431,"files":[{"filename":"github-webhook/index.js","previous":177960,"size":177980,"diff":20}]},{"timestamp":1604259061964,"files":[{"filename":"github-webhook/index.js","previous":177961,"size":177960,"diff":-1}]},{"timestamp":1600966304194,"files":[{"filename":"github-webhook/index.js","previous":177682,"size":177961,"diff":279}]},{"timestamp":1599246609676,"files":[{"filename":"github-webhook/index.js","previous":177721,"size":177682,"diff":-39}]},{"timestamp":1596542584192,"files":[{"filename":"github-webhook/index.js","previous":181657,"size":177721,"diff":-3936}]},{"timestamp":1596540416823,"files":[{"filename":"github-webhook/index.js","previous":181110,"size":181657,"diff":547}]},{"timestamp":1596457756702,"files":[{"filename":"github-webhook/index.js","previous":181095,"size":181110,"diff":15}]},{"timestamp":1596455025723,"files":[{"filename":"github-webhook/index.js","previous":473647,"size":181095,"diff":-292552}]},{"timestamp":1596407673941,"files":[{"filename":"github-webhook/index.js","previous":332785,"size":473647,"diff":140862}]}]
+[{"timestamp":1620265643898,"files":[{"filename":"github-webhook/index.js","previous":191792,"size":191427,"diff":-365}]},{"timestamp":1619038419269,"files":[{"filename":"github-webhook/index.js","previous":191797,"size":191792,"diff":-5}]},{"timestamp":1617394285426,"files":[{"filename":"github-webhook/index.js","previous":191760,"size":191797,"diff":37}]},{"timestamp":1616697789259,"files":[{"filename":"github-webhook/index.js","previous":191665,"size":191760,"diff":95}]},{"timestamp":1615923673723,"files":[{"filename":"github-webhook/index.js","previous":1011459,"size":191665,"diff":-819794}]},{"timestamp":1615923385141,"files":[{"filename":"github-webhook/index.js","previous":1008634,"size":1011459,"diff":2825}]},{"timestamp":1615809019316,"files":[{"filename":"github-webhook/index.js","previous":182432,"size":1008634,"diff":826202}]},{"timestamp":1615023549795,"files":[{"filename":"github-webhook/index.js","previous":182255,"size":182432,"diff":177}]},{"timestamp":1615022566740,"files":[{"filename":"github-webhook/index.js","previous":181954,"size":182255,"diff":301}]},{"timestamp":1614104217580,"files":[{"filename":"github-webhook/index.js","previous":181904,"size":181954,"diff":50}]},{"timestamp":1612464188131,"files":[{"filename":"github-webhook/index.js","previous":181855,"size":181904,"diff":49}]},{"timestamp":1611769329263,"files":[{"filename":"github-webhook/index.js","previous":181805,"size":181855,"diff":50}]},{"timestamp":1609970949480,"files":[{"filename":"github-webhook/index.js","previous":179020,"size":181805,"diff":2785}]},{"timestamp":1607360063925,"files":[{"filename":"github-webhook/index.js","previous":178533,"size":179020,"diff":487}]},{"timestamp":1606896280100,"files":[{"filename":"github-webhook/index.js","previous":177980,"size":178533,"diff":553}]},{"timestamp":1606329557431,"files":[{"filename":"github-webhook/index.js","previous":177960,"size":177980,"diff":20}]},{"timestamp":1604259061964,"files":[{"filename":"github-webhook/index.js","previous":177961,"size":177960,"diff":-1}]},{"timestamp":1600966304194,"files":[{"filename":"github-webhook/index.js","previous":177682,"size":177961,"diff":279}]},{"timestamp":1599246609676,"files":[{"filename":"github-webhook/index.js","previous":177721,"size":177682,"diff":-39}]},{"timestamp":1596542584192,"files":[{"filename":"github-webhook/index.js","previous":181657,"size":177721,"diff":-3936}]},{"timestamp":1596540416823,"files":[{"filename":"github-webhook/index.js","previous":181110,"size":181657,"diff":547}]},{"timestamp":1596457756702,"files":[{"filename":"github-webhook/index.js","previous":181095,"size":181110,"diff":15}]},{"timestamp":1596455025723,"files":[{"filename":"github-webhook/index.js","previous":473647,"size":181095,"diff":-292552}]},{"timestamp":1596407673941,"files":[{"filename":"github-webhook/index.js","previous":332785,"size":473647,"diff":140862}]}]


Test patch:
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap b/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
index 225045ea1..90518aaf6 100644
--- a/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
+++ b/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
@@ -193,6 +193,262 @@ Object {
 }
 `;
 
+exports[`customer managed encryption key 1`] = `
+Object {
+  "Parameters": Object {
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eArtifactHash79A71AFC": Object {
+      "Description": "Artifact hash for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE": Object {
+      "Description": "S3 bucket for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358": Object {
+      "Description": "S3 key for asset version \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+  },
+  "Resources": Object {
+    "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10": Object {
+      "DependsOn": Array [
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+      ],
+      "Properties": Object {
+        "Code": Object {
+          "S3Bucket": Object {
+            "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE",
+          },
+          "S3Key": Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                Object {
+                  "Fn::Select": Array [
+                    0,
+                    Object {
+                      "Fn::Split": Array [
+                        "||",
+                        Object {
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
+                        },
+                      ],
+                    },
+                  ],
+                },
+                Object {
+                  "Fn::Select": Array [
+                    1,
+                    Object {
+                      "Fn::Split": Array [
+                        "||",
+                        Object {
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
+                        },
+                      ],
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+        "Handler": "index.handler",
+        "Role": Object {
+          "Fn::GetAtt": Array [
+            "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+            "Arn",
+          ],
+        },
+        "Runtime": "nodejs12.x",
+        "Timeout": 900,
+      },
+      "Type": "AWS::Lambda::Function",
+    },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8": Object {
+      "Properties": Object {
+        "AssumeRolePolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": Object {
+                "Service": "lambda.amazonaws.com",
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "ManagedPolicyArns": Array [
+          Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
+                Object {
+                  "Ref": "AWS::Partition",
+                },
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+              ],
+            ],
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Role",
+    },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "dynamodb:BatchWriteItem",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableCD117FA1",
+                  "Arn",
+                ],
+              },
+            },
+            Object {
+              "Action": Array [
+                "kms:Encrypt",
+                "kms:Decrypt",
+                "kms:ReEncrypt*",
+                "kms:GenerateDataKey*",
+                "kms:DescribeKey",
+                "kms:CreateGrant",
+              ],
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableKey25666F95",
+                  "Arn",
+                ],
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "Roles": Array [
+          Object {
+            "Ref": "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Policy",
+    },
+    "DynamoDBSeederCustomResourceEF4D5C60": Object {
+      "DeletionPolicy": "Delete",
+      "Properties": Object {
+        "Seeds": Object {
+          "InlineSeeds": "[{\\"id\\":1,\\"column\\":\\"foo\\"},{\\"id\\":2,\\"column\\":\\"bar\\"}]",
+        },
+        "ServiceToken": Object {
+          "Fn::GetAtt": Array [
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
+            "Arn",
+          ],
+        },
+        "TableName": Object {
+          "Ref": "TableCD117FA1",
+        },
+      },
+      "Type": "Custom::DynamodbSeeder",
+      "UpdateReplacePolicy": "Delete",
+    },
+    "TableCD117FA1": Object {
+      "DeletionPolicy": "Retain",
+      "Properties": Object {
+        "AttributeDefinitions": Array [
+          Object {
+            "AttributeName": "id",
+            "AttributeType": "N",
+          },
+        ],
+        "KeySchema": Array [
+          Object {
+            "AttributeName": "id",
+            "KeyType": "HASH",
+          },
+        ],
+        "ProvisionedThroughput": Object {
+          "ReadCapacityUnits": 5,
+          "WriteCapacityUnits": 5,
+        },
+        "SSESpecification": Object {
+          "KMSMasterKeyId": Object {
+            "Fn::GetAtt": Array [
+              "TableKey25666F95",
+              "Arn",
+            ],
+          },
+          "SSEEnabled": true,
+          "SSEType": "KMS",
+        },
+      },
+      "Type": "AWS::DynamoDB::Table",
+      "UpdateReplacePolicy": "Retain",
+    },
+    "TableKey25666F95": Object {
+      "DeletionPolicy": "Retain",
+      "Properties": Object {
+        "Description": "Customer-managed key auto-created for encrypting DynamoDB table at Default/Table",
+        "EnableKeyRotation": true,
+        "KeyPolicy": Object {
+          "Statement": Array [
+            Object {
+              "Action": Array [
+                "kms:Create*",
+                "kms:Describe*",
+                "kms:Enable*",
+                "kms:List*",
+                "kms:Put*",
+                "kms:Update*",
+                "kms:Revoke*",
+                "kms:Disable*",
+                "kms:Get*",
+                "kms:Delete*",
+                "kms:ScheduleKeyDeletion",
+                "kms:CancelKeyDeletion",
+                "kms:GenerateDataKey",
+                "kms:TagResource",
+                "kms:UntagResource",
+              ],
+              "Effect": "Allow",
+              "Principal": Object {
+                "AWS": Object {
+                  "Fn::Join": Array [
+                    "",
+                    Array [
+                      "arn:",
+                      Object {
+                        "Ref": "AWS::Partition",
+                      },
+                      ":iam::",
+                      Object {
+                        "Ref": "AWS::AccountId",
+                      },
+                      ":root",
+                    ],
+                  ],
+                },
+              },
+              "Resource": "*",
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+      },
+      "Type": "AWS::KMS::Key",
+      "UpdateReplacePolicy": "Retain",
+    },
+  },
+}
+`;
+
 exports[`inline 1`] = `
 Object {
   "Parameters": Object {
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts b/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
index f1a6129a8..9e261d539 100644
--- a/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
+++ b/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
@@ -1,5 +1,5 @@
 import * as path from 'path';
-import { AttributeType, Table } from '@aws-cdk/aws-dynamodb';
+import { AttributeType, Table, TableEncryption } from '@aws-cdk/aws-dynamodb';
 import { Bucket } from '@aws-cdk/aws-s3';
 import { Stack } from '@aws-cdk/core';
 import 'jest-cdk-snapshot';
@@ -149,3 +149,33 @@ test('multiple seeders', () => {
   // THEN
   expect(stack).toMatchCdkSnapshot();
 });
+
+test('customer managed encryption key', () => {
+  // GIVEN
+  const stack = new Stack();
+  const table = new Table(stack, 'Table', {
+    partitionKey: {
+      name: 'id',
+      type: AttributeType.NUMBER,
+    },
+    encryption: TableEncryption.CUSTOMER_MANAGED,
+  });
+
+  // WHEN
+  new DynamoDBSeeder(stack, 'DynamoDBSeeder', {
+    table,
+    seeds: Seeds.fromInline([
+      {
+        id: 1,
+        column: 'foo',
+      },
+      {
+        id: 2,
+        column: 'bar',
+      },
+    ]),
+  });
+
+  // THEN
+  expect(stack).toMatchCdkSnapshot();
+});