diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.assets.json new file mode 100644 index 0000000000000..8900597aaf3ac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "2b69b4bc6b7625472eab3367e63c8231391414b38c68f5e6fe105dc7009cb1d9": { + "source": { + "path": "aws-cdk-route53-dnssec.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2b69b4bc6b7625472eab3367e63c8231391414b38c68f5e6fe105dc7009cb1d9.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-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.template.json new file mode 100644 index 0000000000000..fc62abbefa928 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/aws-cdk-route53-dnssec.template.json @@ -0,0 +1,152 @@ +{ + "Resources": { + "KmsKskBaseF6291F14": { + "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:DescribeKey", + "kms:GetPublicKey", + "kms:Sign" + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":route53:::hostedzone/", + { + "Ref": "HostedZoneDB99F866" + } + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "dnssec-route53.amazonaws.com" + }, + "Resource": "*" + }, + { + "Action": "kms:CreateGrant", + "Condition": { + "Bool": { + "kms:GrantIsForAWSResource": true + } + }, + "Effect": "Allow", + "Principal": { + "Service": "dnssec-route53.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "KeySpec": "ECC_NIST_P256", + "KeyUsage": "SIGN_VERIFY" + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "HostedZoneDB99F866": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "cdk.test." + } + }, + "HostedZoneKeySigningKey3418AFB4": { + "Type": "AWS::Route53::KeySigningKey", + "Properties": { + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + }, + "KeyManagementServiceArn": { + "Fn::GetAtt": [ + "KmsKskBaseF6291F14", + "Arn" + ] + }, + "Name": "awscdkroute53dnssecHostedZoneKeySigningKeyEE4B885F", + "Status": "ACTIVE" + } + }, + "HostedZoneDNSSEC79297416": { + "Type": "AWS::Route53::DNSSEC", + "Properties": { + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + }, + "DependsOn": [ + "HostedZoneKeySigningKey3418AFB4" + ] + } + }, + "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-route53/test/integ.dnssec.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integ.json new file mode 100644 index 0000000000000..219c30d80be27 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "enableLookups": true, + "version": "36.0.0", + "testCases": { + "integ-test/DefaultTest": { + "stacks": [ + "aws-cdk-route53-dnssec" + ], + "diffAssets": true, + "assertionStack": "integ-test/DefaultTest/DeployAssert", + "assertionStackName": "integtestDefaultTestDeployAssert24D5C536" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.assets.json new file mode 100644 index 0000000000000..3555eb95abb24 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integtestDefaultTestDeployAssert24D5C536.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-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/integtestDefaultTestDeployAssert24D5C536.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-route53/test/integ.dnssec.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/manifest.json new file mode 100644 index 0000000000000..4bfc9985ca933 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/manifest.json @@ -0,0 +1,131 @@ +{ + "version": "36.0.0", + "artifacts": { + "aws-cdk-route53-dnssec.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-route53-dnssec.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-route53-dnssec": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-route53-dnssec.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}/2b69b4bc6b7625472eab3367e63c8231391414b38c68f5e6fe105dc7009cb1d9.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-route53-dnssec.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-route53-dnssec.assets" + ], + "metadata": { + "/aws-cdk-route53-dnssec/KmsKskBase/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "KmsKskBaseF6291F14" + } + ], + "/aws-cdk-route53-dnssec/HostedZone/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "HostedZoneDB99F866" + } + ], + "/aws-cdk-route53-dnssec/HostedZone/KeySigningKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "HostedZoneKeySigningKey3418AFB4" + } + ], + "/aws-cdk-route53-dnssec/HostedZone/DNSSEC": [ + { + "type": "aws:cdk:logicalId", + "data": "HostedZoneDNSSEC79297416" + } + ], + "/aws-cdk-route53-dnssec/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-route53-dnssec/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-route53-dnssec" + }, + "integtestDefaultTestDeployAssert24D5C536.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestDefaultTestDeployAssert24D5C536.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestDefaultTestDeployAssert24D5C536": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestDefaultTestDeployAssert24D5C536.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": [ + "integtestDefaultTestDeployAssert24D5C536.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": [ + "integtestDefaultTestDeployAssert24D5C536.assets" + ], + "metadata": { + "/integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-test/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-route53/test/integ.dnssec.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/tree.json new file mode 100644 index 0000000000000..d8ea7567fad28 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.js.snapshot/tree.json @@ -0,0 +1,272 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-route53-dnssec": { + "id": "aws-cdk-route53-dnssec", + "path": "aws-cdk-route53-dnssec", + "children": { + "KmsKskBase": { + "id": "KmsKskBase", + "path": "aws-cdk-route53-dnssec/KmsKskBase", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-dnssec/KmsKskBase/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:DescribeKey", + "kms:GetPublicKey", + "kms:Sign" + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":route53:::hostedzone/", + { + "Ref": "HostedZoneDB99F866" + } + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "dnssec-route53.amazonaws.com" + }, + "Resource": "*" + }, + { + "Action": "kms:CreateGrant", + "Condition": { + "Bool": { + "kms:GrantIsForAWSResource": true + } + }, + "Effect": "Allow", + "Principal": { + "Service": "dnssec-route53.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "keySpec": "ECC_NIST_P256", + "keyUsage": "SIGN_VERIFY" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Key", + "version": "0.0.0" + } + }, + "HostedZone": { + "id": "HostedZone", + "path": "aws-cdk-route53-dnssec/HostedZone", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-dnssec/HostedZone/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::HostedZone", + "aws:cdk:cloudformation:props": { + "name": "cdk.test." + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnHostedZone", + "version": "0.0.0" + } + }, + "KeySigningKey": { + "id": "KeySigningKey", + "path": "aws-cdk-route53-dnssec/HostedZone/KeySigningKey", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-dnssec/HostedZone/KeySigningKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::KeySigningKey", + "aws:cdk:cloudformation:props": { + "hostedZoneId": { + "Ref": "HostedZoneDB99F866" + }, + "keyManagementServiceArn": { + "Fn::GetAtt": [ + "KmsKskBaseF6291F14", + "Arn" + ] + }, + "name": "awscdkroute53dnssecHostedZoneKeySigningKeyEE4B885F", + "status": "ACTIVE" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnKeySigningKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.KeySigningKey", + "version": "0.0.0" + } + }, + "DNSSEC": { + "id": "DNSSEC", + "path": "aws-cdk-route53-dnssec/HostedZone/DNSSEC", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::DNSSEC", + "aws:cdk:cloudformation:props": { + "hostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnDNSSEC", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.HostedZone", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-route53-dnssec/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-route53-dnssec/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "integ-test": { + "id": "integ-test", + "path": "integ-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-test/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.3.0" + } + } + }, + "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-route53/test/integ.dnssec.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.ts new file mode 100644 index 0000000000000..0aa5379c2674b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.dnssec.ts @@ -0,0 +1,23 @@ +import * as cdk from 'aws-cdk-lib'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as route53 from 'aws-cdk-lib/aws-route53'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-route53-dnssec'); + +const kmsKey = new kms.Key(stack, 'KmsKskBase', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, +}); + +const hostedZone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'cdk.test', +}); +hostedZone.enableDnssec({ kmsKey }); + +new IntegTest(app, 'integ-test', { + testCases: [stack], + diffAssets: true, + enableLookups: true, +}); diff --git a/packages/aws-cdk-lib/aws-route53/README.md b/packages/aws-cdk-lib/aws-route53/README.md index 8b48599cc4e3a..8c406369f3675 100644 --- a/packages/aws-cdk-lib/aws-route53/README.md +++ b/packages/aws-cdk-lib/aws-route53/README.md @@ -345,6 +345,45 @@ new route53.PublicHostedZone(this, 'HostedZone', { }); ``` +## Enabling DNSSEC + +DNSSEC can be enabled for Hosted Zones. For detailed information, see +[Configuring DNSSEC signing in Amazon Route 53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-configuring-dnssec.html). + +Enabling DNSSEC requires an asymmetric KMS Customer-Managed Key using the `ECC_NIST_P256` key spec. +Additionally, that KMS key must be in `us-east-1`. + +```ts +const kmsKey = new kms.Key(this, 'KmsCMK', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, +}); +const hostedZone = new route53.HostedZone(this, 'HostedZone', { + zoneName: 'example.com', +}); +// Enable DNSSEC signing for the zone +hostedZone.enableDnssec({ kmsKey }); +``` + +The necessary permissions for Route 53 to use the key will automatically be added when using +this configuration. If it is necessary to create a key signing key manually, that can be done +using the `KeySigningKey` construct: + +```ts +declare const hostedZone: route53.HostedZone; +declare const kmsKey: kms.Key; +new route53.KeySigningKey(this, 'KeySigningKey', { + hostedZone, + kmsKey, + keySigningKeyName: 'ksk', + status: route53.KeySigningKeyStatus.ACTIVE, +}); +``` + +When directly constructing the `KeySigningKey` resource, enabling DNSSEC signing for the hosted +zone will be need to be done explicitly (either using the `CfnDNSSEC` construct or via another +means). + ## Imports If you don't know the ID of the Hosted Zone to import, you can use the diff --git a/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts b/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts index d9361ee2b2d08..76c0e9caa3b25 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts @@ -1,11 +1,13 @@ import { Construct } from 'constructs'; import { HostedZoneProviderProps } from './hosted-zone-provider'; import { HostedZoneAttributes, IHostedZone, PublicHostedZoneAttributes } from './hosted-zone-ref'; +import { IKeySigningKey, KeySigningKey } from './key-signing-key'; import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set'; -import { CfnHostedZone } from './route53.generated'; +import { CfnHostedZone, CfnDNSSEC, CfnKeySigningKey } from './route53.generated'; import { makeGrantDelegation, makeHostedZoneArn, validateZoneName } from './util'; import * as ec2 from '../../aws-ec2'; import * as iam from '../../aws-iam'; +import * as kms from '../../aws-kms'; import * as cxschema from '../../cloud-assembly-schema'; import { ContextProvider, Duration, Lazy, Resource, Stack } from '../../core'; @@ -56,6 +58,30 @@ export interface HostedZoneProps extends CommonHostedZoneProps { readonly vpcs?: ec2.IVpc[]; } +/** + * Options for enabling key signing from a hosted zone. + */ +export interface ZoneSigningOptions { + /** + * The customer-managed KMS key that that will be used to sign the records. + * + * The KMS Key must be unique for each KSK within a hosted zone. Additionally, the + * KMS key must be an asymetric customer-managed key using the ECC_NIST_P256 algorithm. + * + * @see https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-configuring-dnssec-cmk-requirements.html + */ + readonly kmsKey: kms.IKey; + + /** + * The name for the key signing key. + * + * This name must be unique within a hosted zone. + * + * @default an autogenerated name + */ + readonly keySigningKeyName?: string; +} + /** * Container for records, and records contain information about how to route traffic for a * specific domain, such as example.com and its subdomains (acme.example.com, zenith.example.com) @@ -166,6 +192,11 @@ export class HostedZone extends Resource implements IHostedZone { */ protected readonly vpcs = new Array(); + /** + * The key signing key used to sign the hosted zone. + */ + private keySigningKey?: IKeySigningKey; + constructor(scope: Construct, id: string, props: HostedZoneProps) { super(scope, id); @@ -202,6 +233,29 @@ export class HostedZone extends Resource implements IHostedZone { public grantDelegation(grantee: iam.IGrantable): iam.Grant { return makeGrantDelegation(grantee, this.hostedZoneArn); } + + /** + * Enable DNSSEC for this hosted zone. + * + * This will create a key signing key with the given options and enable DNSSEC signing + * for the hosted zone. + */ + public enableDnssec(options: ZoneSigningOptions): IKeySigningKey { + if (this.keySigningKey) { + throw new Error('DNSSEC is already enabled for this hosted zone'); + } + this.keySigningKey = new KeySigningKey(this, 'KeySigningKey', { + hostedZone: this, + keySigningKeyName: options.keySigningKeyName, + kmsKey: options.kmsKey, + }); + const dnssec = new CfnDNSSEC(this, 'DNSSEC', { + hostedZoneId: this.hostedZoneId, + }); + // The KSK must exist and be in an 'ACTIVE' status before DNSSEC can be enabled. + dnssec.addDependency(this.keySigningKey.node.defaultChild as CfnKeySigningKey); + return this.keySigningKey; + } } /** diff --git a/packages/aws-cdk-lib/aws-route53/lib/index.ts b/packages/aws-cdk-lib/aws-route53/lib/index.ts index 2a6128e50c2bd..4083ca39bfab6 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/index.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/index.ts @@ -2,6 +2,7 @@ export * from './alias-record-target'; export * from './hosted-zone'; export * from './hosted-zone-provider'; export * from './hosted-zone-ref'; +export * from './key-signing-key'; export * from './record-set'; export * from './vpc-endpoint-service-domain-name'; export * from './geo-location'; diff --git a/packages/aws-cdk-lib/aws-route53/lib/key-signing-key.ts b/packages/aws-cdk-lib/aws-route53/lib/key-signing-key.ts new file mode 100644 index 0000000000000..0f9ad36b3ead1 --- /dev/null +++ b/packages/aws-cdk-lib/aws-route53/lib/key-signing-key.ts @@ -0,0 +1,184 @@ +import { Construct } from 'constructs'; +import { IHostedZone } from './hosted-zone-ref'; +import { CfnKeySigningKey } from './route53.generated'; +import * as iam from '../../aws-iam'; +import * as kms from '../../aws-kms'; +import { Resource, IResource, Lazy, Names } from '../../core'; + +/** + * Properties for constructing a Key Signing Key. + */ +export interface KeySigningKeyProps { + /** + * The hosted zone that this key will be used to sign. + */ + readonly hostedZone: IHostedZone; + + /** + * The customer-managed KMS key that that will be used to sign the records. + * + * The KMS Key must be unique for each KSK within a hosted zone. Additionally, the + * KMS key must be an asymetric customer-managed key using the ECC_NIST_P256 algorithm. + * + * @see https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-configuring-dnssec-cmk-requirements.html + */ + readonly kmsKey: kms.IKey; + + /** + * The name for the key signing key. + * + * This name must be unique within a hosted zone. + * + * @default an autogenerated name + */ + readonly keySigningKeyName?: string; + + /** + * The status of the key signing key. + * + * @default ACTIVE + */ + readonly status?: KeySigningKeyStatus; +} + +/** + * The status for a Key Signing Key. + */ +export enum KeySigningKeyStatus { + /** The KSK is being used for signing. */ + ACTIVE = 'ACTIVE', + /** The KSK is not being used for signing. */ + INACTIVE = 'INACTIVE', +} + +/** + * A Key Signing Key for a Route 53 Hosted Zone. + */ +export interface IKeySigningKey extends IResource { + /** + * The hosted zone that the key signing key signs. + * + * @attribute + */ + readonly hostedZone: IHostedZone; + + /** + * The name of the key signing key. + * + * @attribute + */ + readonly keySigningKeyName: string; + + /** + * The ID of the key signing key, derived from the hosted zone ID and its name. + * + * @attribute + */ + readonly keySigningKeyId: string; +} + +/** + * The attributes of a key signing key. + */ +export interface KeySigningKeyAttributes { + /** + * The hosted zone that the key signing key signs. + * + * @attribute + */ + readonly hostedZone: IHostedZone; + + /** + * The name of the key signing key. + * + * @attribute + */ + readonly keySigningKeyName: string; +} + +/** + * A Key Signing Key for a Route 53 Hosted Zone. + * + * @resource AWS::Route53::KeySigningKey + */ +export class KeySigningKey extends Resource implements IKeySigningKey { + + /** + * Imports a key signing key from its attributes. + */ + public static fromKeySigningKeyAttributes(scope: Construct, id: string, attrs: KeySigningKeyAttributes): IKeySigningKey { + class Import extends Resource implements IKeySigningKey { + public readonly keySigningKeyName: string; + public readonly hostedZone: IHostedZone; + + constructor() { + super(scope, id); + this.keySigningKeyName = attrs.keySigningKeyName; + this.hostedZone = attrs.hostedZone; + } + + get keySigningKeyId() { + return `${this.hostedZone.hostedZoneId}|${this.keySigningKeyName}`; + } + } + + return new Import(); + } + + public readonly hostedZone: IHostedZone; + public readonly keySigningKeyName: string; + public readonly keySigningKeyId: string; + + constructor(scope: Construct, id: string, props: KeySigningKeyProps) { + super(scope, id, { + physicalName: props.keySigningKeyName ?? Lazy.string({ + produce: () => Names.uniqueResourceName(this, { maxLength: 128, allowedSpecialCharacters: '_' }), + }), + }); + + this.grantKeyPermissionsForZone(props.kmsKey, props.hostedZone); + + const resource = new CfnKeySigningKey(this, 'Resource', { + hostedZoneId: props.hostedZone.hostedZoneId, + keyManagementServiceArn: props.kmsKey.keyArn, + name: this.physicalName, + status: props.status ?? KeySigningKeyStatus.ACTIVE, + }); + + this.keySigningKeyId = resource.ref; + // These values are easier to derive from the props than via parsing the `.ref` + // attribute of the resource using `Fn::Split` + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-keysigningkey.html#aws-resource-route53-keysigningkey-return-values + this.hostedZone = props.hostedZone; + this.keySigningKeyName = this.physicalName; + } + + private grantKeyPermissionsForZone(key: kms.IKey, zone: IHostedZone): iam.Grant[] { + // Grants are based on the recommended configuration to avoid the confused deputy problem + // at https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-managing-permissions.html#KMS-key-policy-for-DNSSEC + return [ + key.grant( + new iam.ServicePrincipal('dnssec-route53.amazonaws.com', { + conditions: { + ArnEquals: { + 'aws:SourceArn': zone.hostedZoneArn, + }, + }, + }), + 'kms:DescribeKey', + 'kms:GetPublicKey', + 'kms:Sign', + ), + key.grant( + new iam.ServicePrincipal('dnssec-route53.amazonaws.com', { + conditions: { + Bool: { + 'kms:GrantIsForAWSResource': true, + }, + }, + }), + 'kms:CreateGrant', + ), + ]; + } +} diff --git a/packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts b/packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts index f08c8e1530dab..aa0de897d6007 100644 --- a/packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts +++ b/packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts @@ -2,8 +2,9 @@ import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Match, Template } from '../../assertions'; import * as ec2 from '../../aws-ec2'; import * as iam from '../../aws-iam'; +import * as kms from '../../aws-kms'; import * as cdk from '../../core'; -import { HostedZone, PrivateHostedZone, PublicHostedZone } from '../lib'; +import { HostedZone, PrivateHostedZone, PublicHostedZone, ZoneSigningOptions } from '../lib'; describe('hosted zone', () => { describe('Hosted Zone', () => { @@ -500,4 +501,93 @@ describe('Hosted Zone with dot', () => { Name: 'testZone.', }); }); -}); \ No newline at end of file +}); + +describe('key signing key', () => { + test('enabling works via method', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + const zone = new HostedZone(stack, 'HostedZone', { + zoneName: 'testZone', + }); + + // WHEN + zone.enableDnssec({ + kmsKey: key, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResource('AWS::Route53::DNSSEC', { + Properties: { + HostedZoneId: stack.resolve(zone.hostedZoneId), + }, + }); + template.hasResource('AWS::Route53::KeySigningKey', { + Properties: { + KeyManagementServiceArn: stack.resolve(key.keyArn), + HostedZoneId: stack.resolve(zone.hostedZoneId), + Status: 'ACTIVE', + }, + }); + }); + + test('enabling sets KSK name correctly', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + const zone = new HostedZone(stack, 'HostedZone', { + zoneName: 'testZone', + }); + + // WHEN + zone.enableDnssec({ + kmsKey: key, + keySigningKeyName: 'testksk', + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResource('AWS::Route53::DNSSEC', { + DependsOn: Match.anyValue(), + Properties: { + HostedZoneId: stack.resolve(zone.hostedZoneId), + }, + }); + template.hasResource('AWS::Route53::KeySigningKey', { + Properties: { + KeyManagementServiceArn: stack.resolve(key.keyArn), + HostedZoneId: stack.resolve(zone.hostedZoneId), + Status: 'ACTIVE', + Name: 'testksk', + }, + }); + }); + + test('attempting to enable DNSSEC twice fails', () => { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + const zone = new HostedZone(stack, 'HostedZone', { + zoneName: 'testZone', + }); + + // WHEN + zone.enableDnssec({ + kmsKey: key, + }); + + // THEN + expect(() => zone.enableDnssec({ kmsKey: key })).toThrow('DNSSEC is already enabled for this hosted zone'); + }); +}); diff --git a/packages/aws-cdk-lib/aws-route53/test/key-signing-key.test.ts b/packages/aws-cdk-lib/aws-route53/test/key-signing-key.test.ts new file mode 100644 index 0000000000000..1cb819b8710f4 --- /dev/null +++ b/packages/aws-cdk-lib/aws-route53/test/key-signing-key.test.ts @@ -0,0 +1,149 @@ +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; +import { Match, Template } from '../../assertions'; +import * as kms from '../../aws-kms'; +import * as cdk from '../../core'; +import { HostedZone, KeySigningKey, KeySigningKeyStatus } from '../lib'; + +describe('key signing key', () => { + test('basic creation', () => { + const stack = new cdk.Stack(); + const zone = new HostedZone(stack, 'TestZone', { + zoneName: 'testZone', + }); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + + new KeySigningKey(stack, 'TestKSK', { + hostedZone: zone, + kmsKey: key, + keySigningKeyName: 'testkey', + status: KeySigningKeyStatus.ACTIVE, + }); + + Template.fromStack(stack).hasResource('AWS::Route53::KeySigningKey', { + Properties: { + HostedZoneId: stack.resolve(zone.hostedZoneId), + KeyManagementServiceArn: stack.resolve(key.keyArn), + Name: 'testkey', + Status: 'ACTIVE', + }, + }); + }); + + test('name is auto-generated when omitted', () => { + const stack = new cdk.Stack(); + const zone = new HostedZone(stack, 'TestZone', { + zoneName: 'testZone', + }); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + + new KeySigningKey(stack, 'TestKSK', { + hostedZone: zone, + kmsKey: key, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Route53::KeySigningKey', { + Name: Match.stringLikeRegexp('^[a-zA-Z0-9_]{3,128}$'), + }); + }); + + test('status defaults to active', () => { + const stack = new cdk.Stack(); + const zone = new HostedZone(stack, 'TestZone', { + zoneName: 'testZone', + }); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + + new KeySigningKey(stack, 'TestKSK', { + hostedZone: zone, + kmsKey: key, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Route53::KeySigningKey', { + Status: 'ACTIVE', + }); + }); + + test('importing works correctly with imported zone', () => { + const stack = new cdk.Stack(); + const zone = HostedZone.fromHostedZoneId(stack, 'TestZone', 'Z123456789012EXAMPLE'); + + const ksk = KeySigningKey.fromKeySigningKeyAttributes(stack, 'TestKSK', { + hostedZone: zone, + keySigningKeyName: 'testksk', + }); + + expect(ksk.keySigningKeyId).toBe(`${zone.hostedZoneId}|testksk`); + }); + + test('importing works correctly with created zone', () => { + const stack = new cdk.Stack(); + const zone = new HostedZone(stack, 'TestZone', { + zoneName: 'testZone', + }); + + const ksk = KeySigningKey.fromKeySigningKeyAttributes(stack, 'TestKSK', { + hostedZone: zone, + keySigningKeyName: 'testksk', + }); + + expect(ksk.keySigningKeyId).toBe(`${zone.hostedZoneId}|testksk`); + }); + + test('grants the required permissions on the KMS key', () => { + const stack = new cdk.Stack(); + const zone = new HostedZone(stack, 'TestZone', { + zoneName: 'testzone', + }); + const key = new kms.Key(stack, 'TestKey', { + keySpec: kms.KeySpec.ECC_NIST_P256, + keyUsage: kms.KeyUsage.SIGN_VERIFY, + }); + + new KeySigningKey(stack, 'TestKSK', { + hostedZone: zone, + kmsKey: key, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + KeyPolicy: { + Statement: Match.arrayWith([ + { + Effect: 'Allow', + Principal: { + Service: 'dnssec-route53.amazonaws.com', + }, + Action: ['kms:DescribeKey', 'kms:GetPublicKey', 'kms:Sign'], + Resource: '*', + Condition: { + ArnEquals: { + 'aws:SourceArn': stack.resolve(zone.hostedZoneArn), + }, + }, + }, + { + Effect: 'Allow', + Principal: { + Service: 'dnssec-route53.amazonaws.com', + }, + Action: 'kms:CreateGrant', + Resource: '*', + Condition: { + Bool: { + 'kms:GrantIsForAWSResource': true, + }, + }, + }, + ]), + }, + }); + }); +}); diff --git a/packages/aws-cdk-lib/rosetta/aws_route53/default.ts-fixture b/packages/aws-cdk-lib/rosetta/aws_route53/default.ts-fixture index 64766986db800..3653c7001b00f 100644 --- a/packages/aws-cdk-lib/rosetta/aws_route53/default.ts-fixture +++ b/packages/aws-cdk-lib/rosetta/aws_route53/default.ts-fixture @@ -5,6 +5,7 @@ import * as route53 from 'aws-cdk-lib/aws-route53'; import * as targets from 'aws-cdk-lib/aws-route53-targets'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; +import * as kms from 'aws-cdk-lib/aws-kms'; class Fixture extends Stack { constructor(scope: Construct, id: string) {