From 86d2853a9a919669694a2448805a092839a7f4db Mon Sep 17 00:00:00 2001 From: Clement Allen Date: Mon, 6 Jan 2025 06:42:33 +0000 Subject: [PATCH] feat(kms): add sign and verify related grant methods (#32681) ### Issue # Closes #23185 ### Reason for this change Adds `grant` methods for signing and verifying signatures with KMS ### Description of changes Three new `grant` methods have been added: - `grantSign()`: Adds `'kms:Sign'` to the principal - `grantVerify()`: Adds `'kms:Verify'` to the principal - `grantSignVerify()`: Adds `['kms:Sign', 'kms:Verify']` to the principal ### Description of how you validated changes - Added new unit tests to verify the output of the 3 new `grant` methods - Added a new integ test for `grantSignVerify()` ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- ...efaultTestDeployAssert49AF830A.assets.json | 19 ++ ...aultTestDeployAssert49AF830A.template.json | 36 +++ .../aws-cdk-kms-grants.assets.json | 19 ++ .../aws-cdk-kms-grants.template.json | 134 +++++++++ .../test/integ.key-grants.js.snapshot/cdk.out | 1 + .../integ.key-grants.js.snapshot/integ.json | 12 + .../manifest.json | 125 +++++++++ .../integ.key-grants.js.snapshot/tree.json | 257 ++++++++++++++++++ .../test/aws-kms/test/integ.key-grants.ts | 22 ++ packages/aws-cdk-lib/aws-kms/README.md | 23 +- packages/aws-cdk-lib/aws-kms/lib/alias.ts | 15 + packages/aws-cdk-lib/aws-kms/lib/key.ts | 36 +++ .../aws-cdk-lib/aws-kms/lib/private/perms.ts | 8 + .../aws-cdk-lib/aws-kms/test/alias.test.ts | 172 ++++++++++++ packages/aws-cdk-lib/aws-kms/test/key.test.ts | 117 ++++++++ 15 files changed, 995 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets.json new file mode 100644 index 0000000000000..063e98be87c78 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.assets.json new file mode 100644 index 0000000000000..978848d43b40e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "b4e66c953e71602d86ea5f3d329c603e84dd3debf0b8bf7d953940feba966fea": { + "source": { + "path": "aws-cdk-kms-grants.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b4e66c953e71602d86ea5f3d329c603e84dd3debf0b8bf7d953940feba966fea.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.template.json new file mode 100644 index 0000000000000..741aa06523eac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/aws-cdk-kms-grants.template.json @@ -0,0 +1,134 @@ +{ + "Resources": { + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "RoleDefaultPolicy5FFB7DAB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Sign", + "kms:Verify" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyKey6AB29FA6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "RoleDefaultPolicy5FFB7DAB", + "Roles": [ + { + "Ref": "Role1ABCC5F0" + } + ] + } + }, + "MyKey6AB29FA6": { + "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": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/cdk.out new file mode 100644 index 0000000000000..c6e612584e352 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/integ.json new file mode 100644 index 0000000000000..854fd7ac7de87 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "38.0.1", + "testCases": { + "KeyGrantsIntegTest/DefaultTest": { + "stacks": [ + "aws-cdk-kms-grants" + ], + "assertionStack": "KeyGrantsIntegTest/DefaultTest/DeployAssert", + "assertionStackName": "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/manifest.json new file mode 100644 index 0000000000000..c70d0aa53b0d2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/manifest.json @@ -0,0 +1,125 @@ +{ + "version": "38.0.1", + "artifacts": { + "aws-cdk-kms-grants.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-kms-grants.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-kms-grants": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-kms-grants.template.json", + "terminationProtection": false, + "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}/b4e66c953e71602d86ea5f3d329c603e84dd3debf0b8bf7d953940feba966fea.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-kms-grants.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-kms-grants.assets" + ], + "metadata": { + "/aws-cdk-kms-grants/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], + "/aws-cdk-kms-grants/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RoleDefaultPolicy5FFB7DAB" + } + ], + "/aws-cdk-kms-grants/MyKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyKey6AB29FA6" + } + ], + "/aws-cdk-kms-grants/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-kms-grants/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-kms-grants" + }, + "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.template.json", + "terminationProtection": false, + "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}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "KeyGrantsIntegTestDefaultTestDeployAssert49AF830A.assets" + ], + "metadata": { + "/KeyGrantsIntegTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/KeyGrantsIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "KeyGrantsIntegTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/tree.json new file mode 100644 index 0000000000000..f61eafbb46c61 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.js.snapshot/tree.json @@ -0,0 +1,257 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-kms-grants": { + "id": "aws-cdk-kms-grants", + "path": "aws-cdk-kms-grants", + "children": { + "Role": { + "id": "Role", + "path": "aws-cdk-kms-grants/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "aws-cdk-kms-grants/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-kms-grants/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-cdk-kms-grants/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-kms-grants/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "kms:Sign", + "kms:Verify" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyKey6AB29FA6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "RoleDefaultPolicy5FFB7DAB", + "roles": [ + { + "Ref": "Role1ABCC5F0" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "MyKey": { + "id": "MyKey", + "path": "aws-cdk-kms-grants/MyKey", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-kms-grants/MyKey/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": "*" + } + ], + "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" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-kms-grants/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-kms-grants/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "KeyGrantsIntegTest": { + "id": "KeyGrantsIntegTest", + "path": "KeyGrantsIntegTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "KeyGrantsIntegTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "KeyGrantsIntegTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "KeyGrantsIntegTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "KeyGrantsIntegTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "KeyGrantsIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.ts new file mode 100644 index 0000000000000..32d7e1fc8e430 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-grants.ts @@ -0,0 +1,22 @@ +import { AccountRootPrincipal, Role } from 'aws-cdk-lib/aws-iam'; +import { App, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { Key } from 'aws-cdk-lib/aws-kms'; + +const app = new App(); + +const stack = new Stack(app, 'aws-cdk-kms-grants'); + +const role = new Role(stack, 'Role', { + assumedBy: new AccountRootPrincipal(), +}); + +const key = new Key(stack, 'MyKey', { removalPolicy: RemovalPolicy.DESTROY }); + +key.grantSignVerify(role); + +new IntegTest(app, 'KeyGrantsIntegTest', { + testCases: [ + stack, + ], +}); diff --git a/packages/aws-cdk-lib/aws-kms/README.md b/packages/aws-cdk-lib/aws-kms/README.md index a149a74b5ca79..11adf4c9b5971 100644 --- a/packages/aws-cdk-lib/aws-kms/README.md +++ b/packages/aws-cdk-lib/aws-kms/README.md @@ -236,9 +236,30 @@ runs the risk of the key becoming unmanageable if that user or role is deleted. It is highly recommended that the key policy grants access to the account root, rather than specific principals. See https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html for more information. +### Signing and Verification key policies + +Creating signatures and verifying them with KMS requires specific permissions. +The respective policies can be attached to a principal via the `grantSign` and `grantVerify` methods. + +```ts +const key = new kms.Key(this, 'MyKey'); +const user = new iam.User(this, 'MyUser'); +key.grantSign(user); // Adds 'kms:Sign' to the principal's policy +key.grantVerify(user); // Adds 'kms:Verify' to the principal's policy +``` + +If both sign and verify permissions are required, they can be applied with one method called `grantSignVerify`. + +```ts +const key = new kms.Key(this, 'MyKey'); +const user = new iam.User(this, 'MyUser'); +key.grantSignVerify(user); // Adds 'kms:Sign' and 'kms:Verify' to the principal's policy +``` + + ### HMAC specific key policies -HMAC keys have a different key policy than other KMS keys. They have a policy for generating and for verifying a MAC. +HMAC keys have a different key policy than other KMS keys. They have a policy for generating and for verifying a MAC. The respective policies can be attached to a principal via the `grantGenerateMac` and `grantVerifyMac` methods. ```ts diff --git a/packages/aws-cdk-lib/aws-kms/lib/alias.ts b/packages/aws-cdk-lib/aws-kms/lib/alias.ts index 0045866c3a8f8..1c98b4ebfadd0 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/alias.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/alias.ts @@ -114,6 +114,18 @@ abstract class AliasBase extends Resource implements IAlias { return this.aliasTargetKey.grantEncryptDecrypt(grantee); } + public grantSign(grantee: iam.IGrantable): iam.Grant { + return this.aliasTargetKey.grantSign(grantee); + } + + public grantVerify(grantee: iam.IGrantable): iam.Grant { + return this.aliasTargetKey.grantVerify(grantee); + } + + public grantSignVerify(grantee: iam.IGrantable): iam.Grant { + return this.aliasTargetKey.grantSignVerify(grantee); + } + grantGenerateMac(grantee: iam.IGrantable): iam.Grant { return this.aliasTargetKey.grantGenerateMac(grantee); } @@ -188,6 +200,9 @@ export class Alias extends AliasBase { public grantDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } public grantEncrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantSign(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantVerify(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } + public grantSignVerify(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } public grantGenerateMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } public grantVerifyMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); } } diff --git a/packages/aws-cdk-lib/aws-kms/lib/key.ts b/packages/aws-cdk-lib/aws-kms/lib/key.ts index e16ec4210ea08..68c5a102f0e47 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/key.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/key.ts @@ -74,6 +74,21 @@ export interface IKey extends IResource { */ grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant; + /** + * Grant sign permissions using this key to the given principal + */ + grantSign(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant verify permissions using this key to the given principal + */ + grantVerify(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant sign and verify permissions using this key to the given principal + */ + grantSignVerify(grantee: iam.IGrantable): iam.Grant; + /** * Grant permissions to generating MACs to the given principal */ @@ -216,6 +231,27 @@ abstract class KeyBase extends Resource implements IKey { return this.grant(grantee, ...[...perms.DECRYPT_ACTIONS, ...perms.ENCRYPT_ACTIONS]); } + /** + * Grant sign permissions using this key to the given principal + */ + public grantSign(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, ...perms.SIGN_ACTIONS); + } + + /** + * Grant verify permissions using this key to the given principal + */ + public grantVerify(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, ...perms.VERIFY_ACTIONS); + } + + /** + * Grant sign and verify permissions using this key to the given principal + */ + public grantSignVerify(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, ...[...perms.SIGN_ACTIONS, ...perms.VERIFY_ACTIONS]); + } + /** * Grant permissions to generating MACs to the given principal */ diff --git a/packages/aws-cdk-lib/aws-kms/lib/private/perms.ts b/packages/aws-cdk-lib/aws-kms/lib/private/perms.ts index 1de42d3606c58..e86af3738eeba 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/private/perms.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/private/perms.ts @@ -27,6 +27,14 @@ export const DECRYPT_ACTIONS = [ 'kms:Decrypt', ]; +export const SIGN_ACTIONS = [ + 'kms:Sign', +]; + +export const VERIFY_ACTIONS = [ + 'kms:Verify', +]; + export const GENERATE_HMAC_ACTIONS = [ 'kms:GenerateMac', ]; diff --git a/packages/aws-cdk-lib/aws-kms/test/alias.test.ts b/packages/aws-cdk-lib/aws-kms/test/alias.test.ts index b65ac9dab5313..e21bba91f98b4 100644 --- a/packages/aws-cdk-lib/aws-kms/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-kms/test/alias.test.ts @@ -86,6 +86,17 @@ test('fails if alias contains illegal characters', () => { })).toThrow('a-zA-Z0-9:/_-'); }); +test('fails if alias starts with "aws/"', () => { + const app = new App(); + const stack = new Stack(app, 'Test'); + + expect(() => { + new Key(stack, 'Key', { + alias: `alias/aws/${Aws.ACCOUNT_ID}`, + }); + }).toThrow('Alias cannot start with alias/aws/: alias/aws/'); +}); + test('fails if alias starts with "alias/aws/"', () => { const app = new App(); const stack = new Stack(app, 'Test'); @@ -290,6 +301,167 @@ test('adds alias prefix if its token with valid string prefix', () => { }); }); +test('grants correct permissions for grant method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grant(user, 'kms:CreateAlias'); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:CreateAlias', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantDecrypt method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantDecrypt(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Decrypt', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantEncrypt method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantEncrypt(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*'], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantEncryptDecrypt method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantEncryptDecrypt(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['kms:Decrypt', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*'], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantSign method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantSign(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Sign', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantVerify method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantVerify(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Verify', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grants correct permissions for grantSignVerify method', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const key = new Key(stack, 'Key'); + const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); + const user = new iam.User(stack, 'User'); + + alias.grantSignVerify(user); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['kms:Sign', 'kms:Verify'], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + test('does not add alias again if already set', () => { const app = new App(); const stack = new Stack(app, 'my-stack'); diff --git a/packages/aws-cdk-lib/aws-kms/test/key.test.ts b/packages/aws-cdk-lib/aws-kms/test/key.test.ts index 199af3cb1659d..e5c982b14172c 100644 --- a/packages/aws-cdk-lib/aws-kms/test/key.test.ts +++ b/packages/aws-cdk-lib/aws-kms/test/key.test.ts @@ -253,6 +253,123 @@ describe('key policies', () => { }); }); + test('sign', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'Key'); + const user = new iam.User(stack, 'User'); + + // WHEN + key.grantSign(user); + + // THEN + // Key policy should be unmodified by the grant. + Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] } }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Sign', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('verify', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'Key'); + const user = new iam.User(stack, 'User'); + + // WHEN + key.grantVerify(user); + + // THEN + // Key policy should be unmodified by the grant. + Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] } }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Verify', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('signverify', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'Key'); + const user = new iam.User(stack, 'User'); + + // WHEN + key.grantSignVerify(user); + + // THEN + // Key policy should be unmodified by the grant. + Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] } }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['kms:Sign', 'kms:Verify'], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + test('grant for a principal in a dependent stack works correctly', () => { const app = new cdk.App(); const principalStack = new cdk.Stack(app, 'PrincipalStack');