From 1574aca2cd8b7f2c77e47686a6caea6eba39f53c Mon Sep 17 00:00:00 2001 From: Penny <991850+scub@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:26:31 -0500 Subject: [PATCH 1/2] fix(rds): addProxy can use kms encrypted secrets --- packages/aws-cdk-lib/aws-rds/lib/proxy.ts | 3 ++ .../aws-cdk-lib/aws-rds/test/proxy.test.ts | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/aws-cdk-lib/aws-rds/lib/proxy.ts b/packages/aws-cdk-lib/aws-rds/lib/proxy.ts index 1e66307be4406..107a189474b04 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/proxy.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/proxy.ts @@ -457,6 +457,9 @@ export class DatabaseProxy extends DatabaseProxyBase for (const secret of props.secrets) { secret.grantRead(role); + if (secret.encryptionKey !== undefined) { + secret.encryptionKey.grantDecrypt(role); + } } const securityGroups = props.securityGroups ?? [ diff --git a/packages/aws-cdk-lib/aws-rds/test/proxy.test.ts b/packages/aws-cdk-lib/aws-rds/test/proxy.test.ts index 6546164210972..39d372533a51a 100644 --- a/packages/aws-cdk-lib/aws-rds/test/proxy.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/proxy.test.ts @@ -1,6 +1,7 @@ import { Match, Template } from '../../assertions'; import * as ec2 from '../../aws-ec2'; import { AccountPrincipal, Role } from '../../aws-iam'; +import { Key } from '../../aws-kms'; import * as secretsmanager from '../../aws-secretsmanager'; import * as cdk from '../../core'; import * as cxapi from '../../cx-api'; @@ -371,6 +372,51 @@ describe('proxy', () => { }).toThrow(/When the Proxy contains multiple Secrets, you must pass a dbUser explicitly to grantConnect/); }); + test('new Proxy with kms encrypted Secrets has permissions to kms:Decrypt that secret using its key', () => { + // GIVEN + const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.AURORA, + instanceProps: { vpc }, + }); + + const kmsKey = new Key(stack, 'Key'); + + const kmsEncryptedSecret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: kmsKey }); + + // WHEN + new rds.DatabaseProxy(stack, 'Proxy', { + proxyTarget: rds.ProxyTarget.fromCluster(cluster), + vpc, + secrets: [kmsEncryptedSecret], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret'], + Effect: 'Allow', + Resource: { + Ref: 'SecretA720EF05', + }, + }, + { + Action: 'kms:Decrypt', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + }, + ], + }, + Roles: [{ Ref: 'ProxyIAMRole2FE8AB0F' }], + }); + }); + test('DBProxyTargetGroup should have dependency on the proxy targets', () => { // GIVEN const cluster = new rds.DatabaseCluster(stack, 'cluster', { From f07eb3fb011d8f67691a16ba1d90d031206d13f2 Mon Sep 17 00:00:00 2001 From: Penny <991850+scub@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:02:31 -0500 Subject: [PATCH 2/2] yarn integ aws-rds/integ.proxy --- .../aws-cdk-rds-proxy.assets.json | 6 +- .../aws-cdk-rds-proxy.template.json | 126 +++++++++++++++ .../test/integ.proxy.js.snapshot/cdk.out | 2 +- ...efaultTestDeployAssert1DC3D9D5.assets.json | 2 +- .../test/integ.proxy.js.snapshot/integ.json | 2 +- .../integ.proxy.js.snapshot/manifest.json | 10 +- .../test/integ.proxy.js.snapshot/tree.json | 143 ++++++++++++++++++ .../test/aws-rds/test/integ.proxy.ts | 3 + 8 files changed, 286 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.assets.json index be93d83fbc681..87aa673a6026d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.assets.json @@ -1,7 +1,7 @@ { - "version": "35.0.0", + "version": "36.0.0", "files": { - "938b3109faa6eac41e1d4a7b5d76197a74908e45302636596d841968426d6321": { + "994f5c55a476da40df81f2f7a742118ee54549be6e75cfa22d25171f36360de6": { "source": { "path": "aws-cdk-rds-proxy.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "938b3109faa6eac41e1d4a7b5d76197a74908e45302636596d841968426d6321.json", + "objectKey": "994f5c55a476da40df81f2f7a742118ee54549be6e75cfa22d25171f36360de6.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.template.json index 21aa37f0796d9..3fb1ddf7e4fdf 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/aws-cdk-rds-proxy.template.json @@ -391,6 +391,116 @@ } } }, + "SecretEncryptionKey40C82244": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:CreateGrant", + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Condition": { + "StringEquals": { + "kms:ViaService": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": "kms:Decrypt", + "Condition": { + "StringEquals": { + "kms:ViaService": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "dbProxyIAMRole662F3AB8", + "Arn" + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "dbInstanceSubnetGroupD062EC9E": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { @@ -471,6 +581,12 @@ "GenerateStringKey": "password", "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"master\"}" + }, + "KmsKeyId": { + "Fn::GetAtt": [ + "SecretEncryptionKey40C82244", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", @@ -567,6 +683,16 @@ "Resource": { "Ref": "dbInstanceSecretAttachment88CFBDAE" } + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SecretEncryptionKey40C82244", + "Arn" + ] + } } ], "Version": "2012-10-17" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/cdk.out index c5cb2e5de6344..1f0068d32659a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"35.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/databaseproxyintegtestDefaultTestDeployAssert1DC3D9D5.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/databaseproxyintegtestDefaultTestDeployAssert1DC3D9D5.assets.json index c1fc7e45b88a1..a2b39265333af 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/databaseproxyintegtestDefaultTestDeployAssert1DC3D9D5.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/databaseproxyintegtestDefaultTestDeployAssert1DC3D9D5.assets.json @@ -1,5 +1,5 @@ { - "version": "35.0.0", + "version": "36.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/integ.json index 45dc395b13d22..5edeed336de2c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "35.0.0", + "version": "36.0.0", "testCases": { "database-proxy-integ-test/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/manifest.json index 579b5da69d673..c829d0d008eac 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "35.0.0", + "version": "36.0.0", "artifacts": { "aws-cdk-rds-proxy.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/938b3109faa6eac41e1d4a7b5d76197a74908e45302636596d841968426d6321.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/994f5c55a476da40df81f2f7a742118ee54549be6e75cfa22d25171f36360de6.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -172,6 +172,12 @@ "data": "vpcVPCGW7984C166" } ], + "/aws-cdk-rds-proxy/SecretEncryptionKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SecretEncryptionKey40C82244" + } + ], "/aws-cdk-rds-proxy/dbInstance/SubnetGroup/Default": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/tree.json index d9e55b3be753c..a7918f614ed2d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.js.snapshot/tree.json @@ -651,6 +651,132 @@ "version": "0.0.0" } }, + "SecretEncryptionKey": { + "id": "SecretEncryptionKey", + "path": "aws-cdk-rds-proxy/SecretEncryptionKey", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-rds-proxy/SecretEncryptionKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "keyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:CreateGrant", + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Condition": { + "StringEquals": { + "kms:ViaService": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": "kms:Decrypt", + "Condition": { + "StringEquals": { + "kms:ViaService": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "dbProxyIAMRole662F3AB8", + "Arn" + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Key", + "version": "0.0.0" + } + }, "dbInstance": { "id": "dbInstance", "path": "aws-cdk-rds-proxy/dbInstance", @@ -786,6 +912,12 @@ "secretStringTemplate": "{\"username\":\"master\"}", "generateStringKey": "password", "excludeCharacters": "\"@/\\" + }, + "kmsKeyId": { + "Fn::GetAtt": [ + "SecretEncryptionKey40C82244", + "Arn" + ] } } }, @@ -952,6 +1084,16 @@ "Resource": { "Ref": "dbInstanceSecretAttachment88CFBDAE" } + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SecretEncryptionKey40C82244", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -1024,6 +1166,7 @@ "auth": [ { "authScheme": "SECRETS", + "clientPasswordAuthType": "POSTGRES_SCRAM_SHA_256", "iamAuth": "DISABLED", "secretArn": { "Ref": "dbInstanceSecretAttachment88CFBDAE" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.ts index f293338ee941e..2af1af8022492 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.proxy.ts @@ -2,12 +2,14 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as cdk from 'aws-cdk-lib'; import { RemovalPolicy } from 'aws-cdk-lib'; import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as kms from 'aws-cdk-lib/aws-kms'; import * as rds from 'aws-cdk-lib/aws-rds'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-proxy'); const vpc = new ec2.Vpc(stack, 'vpc', { maxAzs: 2, restrictDefaultSecurityGroup: false }); +const kmsKey = new kms.Key(stack, 'SecretEncryptionKey'); const dbInstance = new rds.DatabaseInstance(stack, 'dbInstance', { engine: rds.DatabaseInstanceEngine.postgres({ @@ -15,6 +17,7 @@ const dbInstance = new rds.DatabaseInstance(stack, 'dbInstance', { }), instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), credentials: rds.Credentials.fromUsername('master', { + encryptionKey: kmsKey, excludeCharacters: '"@/\\', }), vpc,