diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.assets.json index 284398b143255..0e3a8e4816037 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.assets.json @@ -27,7 +27,7 @@ } } }, - "6efada41383a47bd56eb0a2e6091955fbf170a468c599d5b34732bcd1ba263d9": { + "23b0d2a14d3e7bc3dcf7cc5292fe80fa29dacb0aea32636c17ca9fce11d8aba9": { "source": { "path": "cdk-integ-cluster-snapshot.template.json", "packaging": "file" @@ -35,7 +35,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "6efada41383a47bd56eb0a2e6091955fbf170a468c599d5b34732bcd1ba263d9.json", + "objectKey": "23b0d2a14d3e7bc3dcf7cc5292fe80fa29dacb0aea32636c17ca9fce11d8aba9.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.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.template.json index 192d1f219ac85..63ac67b14613f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/cdk-integ-cluster-snapshot.template.json @@ -426,12 +426,12 @@ "Type": "AWS::RDS::DBCluster", "Properties": { "CopyTagsToSnapshot": true, - "DBClusterParameterGroupName": "default.aurora-mysql5.7", + "DBClusterParameterGroupName": "default.aurora-mysql8.0", "DBSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2", + "EngineVersion": "8.0.mysql_aurora.3.04.0", "MasterUserPassword": { "Fn::Join": [ "", @@ -474,7 +474,7 @@ "DBClusterIdentifier": { "Ref": "ClusterEB0386A7" }, - "DBInstanceClass": "db.t3.small", + "DBInstanceClass": "db.t3.medium", "DBSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, @@ -495,7 +495,7 @@ "DBClusterIdentifier": { "Ref": "ClusterEB0386A7" }, - "DBInstanceClass": "db.t3.small", + "DBInstanceClass": "db.t3.medium", "DBSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, @@ -1357,42 +1357,6 @@ } } }, - "FromSnapshotSecret9100F61C": { - "Type": "AWS::SecretsManager::Secret", - "Properties": { - "Description": { - "Fn::Join": [ - "", - [ - "Generated by the CDK for stack: ", - { - "Ref": "AWS::StackName" - } - ] - ] - }, - "GenerateSecretString": { - "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", - "GenerateStringKey": "password", - "PasswordLength": 30, - "SecretStringTemplate": "{\"username\":\"admin\"}" - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "FromSnapshotSecretAttachmentB62DA1C6": { - "Type": "AWS::SecretsManager::SecretTargetAttachment", - "Properties": { - "SecretId": { - "Ref": "FromSnapshotSecret9100F61C" - }, - "TargetId": { - "Ref": "FromSnapshotEE0682C5" - }, - "TargetType": "AWS::RDS::DBCluster" - } - }, "cdkintegclustersnapshotFromSnapshotSnapshotSecretD93327943fdaad7efa858a3daf9490cf0a702aeb": { "Type": "AWS::SecretsManager::Secret", "Properties": { @@ -1486,12 +1450,12 @@ "Type": "AWS::RDS::DBCluster", "Properties": { "CopyTagsToSnapshot": true, - "DBClusterParameterGroupName": "default.aurora-mysql5.7", + "DBClusterParameterGroupName": "default.aurora-mysql8.0", "DBSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, "Engine": "aurora-mysql", - "EngineVersion": "5.7.mysql_aurora.2.10.2", + "EngineVersion": "8.0.mysql_aurora.3.04.0", "MasterUserPassword": { "Fn::Join": [ "", @@ -1528,7 +1492,7 @@ "DBClusterIdentifier": { "Ref": "FromSnapshotEE0682C5" }, - "DBInstanceClass": "db.t3.small", + "DBInstanceClass": "db.t3.medium", "DBSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, @@ -1549,7 +1513,7 @@ "DBClusterIdentifier": { "Ref": "FromSnapshotEE0682C5" }, - "DBInstanceClass": "db.t3.small", + "DBInstanceClass": "db.t3.medium", "DBSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/manifest.json index c3ef0d63d6ace..456f60dd33d44 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/manifest.json @@ -17,7 +17,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}/6efada41383a47bd56eb0a2e6091955fbf170a468c599d5b34732bcd1ba263d9.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/23b0d2a14d3e7bc3dcf7cc5292fe80fa29dacb0aea32636c17ca9fce11d8aba9.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -333,18 +333,6 @@ "data": "FromSnapshotSecurityGroupfromcdkintegclustersnapshotFromSnapshotRotationSingleUserSecurityGroup8B231219IndirectPort7C6DDFDF" } ], - "/cdk-integ-cluster-snapshot/FromSnapshot/Secret/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "FromSnapshotSecret9100F61C" - } - ], - "/cdk-integ-cluster-snapshot/FromSnapshot/Secret/Attachment/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "FromSnapshotSecretAttachmentB62DA1C6" - } - ], "/cdk-integ-cluster-snapshot/FromSnapshot/SnapshotSecret/Resource": [ { "type": "aws:cdk:logicalId", @@ -416,6 +404,24 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "FromSnapshotSecret9100F61C": [ + { + "type": "aws:cdk:logicalId", + "data": "FromSnapshotSecret9100F61C", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } + ], + "FromSnapshotSecretAttachmentB62DA1C6": [ + { + "type": "aws:cdk:logicalId", + "data": "FromSnapshotSecretAttachmentB62DA1C6", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "cdk-integ-cluster-snapshot" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/tree.json index 0e549e758e4da..1671fd358b3f4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.js.snapshot/tree.json @@ -758,12 +758,12 @@ "aws:cdk:cloudformation:type": "AWS::RDS::DBCluster", "aws:cdk:cloudformation:props": { "copyTagsToSnapshot": true, - "dbClusterParameterGroupName": "default.aurora-mysql5.7", + "dbClusterParameterGroupName": "default.aurora-mysql8.0", "dbSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2", + "engineVersion": "8.0.mysql_aurora.3.04.0", "masterUsername": { "Fn::Join": [ "", @@ -820,7 +820,7 @@ "dbClusterIdentifier": { "Ref": "ClusterEB0386A7" }, - "dbInstanceClass": "db.t3.small", + "dbInstanceClass": "db.t3.medium", "dbSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, @@ -849,7 +849,7 @@ "dbClusterIdentifier": { "Ref": "ClusterEB0386A7" }, - "dbInstanceClass": "db.t3.small", + "dbInstanceClass": "db.t3.medium", "dbSubnetGroupName": { "Ref": "ClusterSubnetsDCFA5CB7" }, @@ -2210,76 +2210,6 @@ "version": "0.0.0" } }, - "Secret": { - "id": "Secret", - "path": "cdk-integ-cluster-snapshot/FromSnapshot/Secret", - "children": { - "Resource": { - "id": "Resource", - "path": "cdk-integ-cluster-snapshot/FromSnapshot/Secret/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::SecretsManager::Secret", - "aws:cdk:cloudformation:props": { - "description": { - "Fn::Join": [ - "", - [ - "Generated by the CDK for stack: ", - { - "Ref": "AWS::StackName" - } - ] - ] - }, - "generateSecretString": { - "passwordLength": 30, - "secretStringTemplate": "{\"username\":\"admin\"}", - "generateStringKey": "password", - "excludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_secretsmanager.CfnSecret", - "version": "0.0.0" - } - }, - "Attachment": { - "id": "Attachment", - "path": "cdk-integ-cluster-snapshot/FromSnapshot/Secret/Attachment", - "children": { - "Resource": { - "id": "Resource", - "path": "cdk-integ-cluster-snapshot/FromSnapshot/Secret/Attachment/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::SecretsManager::SecretTargetAttachment", - "aws:cdk:cloudformation:props": { - "secretId": { - "Ref": "FromSnapshotSecret9100F61C" - }, - "targetId": { - "Ref": "FromSnapshotEE0682C5" - }, - "targetType": "AWS::RDS::DBCluster" - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_secretsmanager.CfnSecretTargetAttachment", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_secretsmanager.SecretTargetAttachment", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_rds.DatabaseSecret", - "version": "0.0.0" - } - }, "SnapshotSecret": { "id": "SnapshotSecret", "path": "cdk-integ-cluster-snapshot/FromSnapshot/SnapshotSecret", @@ -2446,12 +2376,12 @@ "aws:cdk:cloudformation:type": "AWS::RDS::DBCluster", "aws:cdk:cloudformation:props": { "copyTagsToSnapshot": true, - "dbClusterParameterGroupName": "default.aurora-mysql5.7", + "dbClusterParameterGroupName": "default.aurora-mysql8.0", "dbSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, "engine": "aurora-mysql", - "engineVersion": "5.7.mysql_aurora.2.10.2", + "engineVersion": "8.0.mysql_aurora.3.04.0", "masterUserPassword": { "Fn::Join": [ "", @@ -2502,7 +2432,7 @@ "dbClusterIdentifier": { "Ref": "FromSnapshotEE0682C5" }, - "dbInstanceClass": "db.t3.small", + "dbInstanceClass": "db.t3.medium", "dbSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, @@ -2531,7 +2461,7 @@ "dbClusterIdentifier": { "Ref": "FromSnapshotEE0682C5" }, - "dbInstanceClass": "db.t3.small", + "dbInstanceClass": "db.t3.medium", "dbSubnetGroupName": { "Ref": "FromSnapshotSubnets9ED4B8EE" }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.ts index ea4afa157caa4..a7619f5ff2aac 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.cluster-snapshot.ts @@ -17,11 +17,11 @@ class TestStack extends Stack { const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2, natGateways: 1, restrictDefaultSecurityGroup: false }); const instanceProps = { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), isFromLegacyInstanceProps: true, }; const cluster = new rds.DatabaseCluster(this, 'Cluster', { - engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_10_2 }), + engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_04_0 }), writer: ClusterInstance.provisioned('Instance1', { ...instanceProps, }), @@ -42,7 +42,7 @@ class TestStack extends Stack { const fromSnapshot = new rds.DatabaseClusterFromSnapshot(this, 'FromSnapshot', { snapshotIdentifier: snapshoter.snapshotArn, snapshotCredentials: rds.SnapshotCredentials.fromGeneratedSecret('admin'), - engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_10_2 }), + engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_04_0 }), writer: ClusterInstance.provisioned('Instance1', { ...instanceProps, }), diff --git a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts index 00d86c2630e9c..8fbe0d5656c95 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/cluster.ts @@ -2,11 +2,10 @@ import { Construct } from 'constructs'; import { IAuroraClusterInstance, IClusterInstance, InstanceType } from './aurora-cluster-instance'; import { IClusterEngine } from './cluster-engine'; import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref'; -import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { NetworkType } from './instance'; import { IParameterGroup, ParameterGroup } from './parameter-group'; -import { applyDefaultRotationOptions, defaultDeletionProtection, renderCredentials, setupS3ImportExport, helperRemovalPolicy, renderUnless } from './private/util'; +import { applyDefaultRotationOptions, defaultDeletionProtection, renderCredentials, setupS3ImportExport, helperRemovalPolicy, renderUnless, renderSnapshotCredentials } from './private/util'; import { BackupProps, Credentials, InstanceProps, PerformanceInsightRetention, RotationSingleUserOptions, RotationMultiUserOptions, SnapshotCredentials } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBCluster, CfnDBClusterProps, CfnDBInstance } from './rds.generated'; @@ -1149,38 +1148,27 @@ export class DatabaseClusterFromSnapshot extends DatabaseClusterNew { if (!props.credentials && !props.snapshotCredentials) { Annotations.of(this).addWarningV2('@aws-cdk/aws-rds:generatedCredsNotApplied', 'Generated credentials will not be applied to cluster. Use `snapshotCredentials` instead. `addRotationSingleUser()` and `addRotationMultiUser()` cannot be used on this cluster.'); } - const deprecatedCredentials = renderCredentials(this, props.engine, props.credentials); - let credentials = props.snapshotCredentials; - let secret = credentials?.secret; - if (!secret && credentials?.generatePassword) { - if (!credentials.username) { - throw new Error('`snapshotCredentials` `username` must be specified when `generatePassword` is set to true'); - } + const deprecatedCredentials = !FeatureFlags.of(this).isEnabled(cxapi.RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS) + ? renderCredentials(this, props.engine, props.credentials) + : undefined; - secret = new DatabaseSecret(this, 'SnapshotSecret', { - username: credentials.username, - encryptionKey: credentials.encryptionKey, - excludeCharacters: credentials.excludeCharacters, - replaceOnPasswordCriteriaChanges: credentials.replaceOnPasswordCriteriaChanges, - replicaRegions: credentials.replicaRegions, - }); - } + const credentials = renderSnapshotCredentials(this, props.snapshotCredentials); const cluster = new CfnDBCluster(this, 'Resource', { ...this.newCfnProps, snapshotIdentifier: props.snapshotIdentifier, - masterUserPassword: secret?.secretValueFromJson('password')?.unsafeUnwrap() ?? credentials?.password?.unsafeUnwrap(), // Safe usage + masterUserPassword: credentials?.secret?.secretValueFromJson('password')?.unsafeUnwrap() ?? credentials?.password?.unsafeUnwrap(), // Safe usage }); this.clusterIdentifier = cluster.ref; this.clusterResourceIdentifier = cluster.attrDbClusterResourceId; - if (secret) { - this.secret = secret.attach(this); + if (credentials?.secret) { + this.secret = credentials.secret.attach(this); } - if (deprecatedCredentials.secret) { + if (deprecatedCredentials?.secret) { const deprecatedSecret = deprecatedCredentials.secret.attach(this); if (!this.secret) { this.secret = deprecatedSecret; diff --git a/packages/aws-cdk-lib/aws-rds/lib/private/util.ts b/packages/aws-cdk-lib/aws-rds/lib/private/util.ts index b1b8f89dd8ad9..0be418255144a 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/private/util.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/private/util.ts @@ -5,7 +5,7 @@ import * as s3 from '../../../aws-s3'; import { RemovalPolicy } from '../../../core'; import { DatabaseSecret } from '../database-secret'; import { IEngine } from '../engine'; -import { CommonRotationUserOptions, Credentials } from '../props'; +import { CommonRotationUserOptions, Credentials, SnapshotCredentials } from '../props'; /** * The default set of characters we exclude from generated passwords for database users. @@ -108,6 +108,32 @@ export function renderCredentials(scope: Construct, engine: IEngine, credentials return renderedCredentials; } +/** + * Renders the credentials for an instance or cluster using provided snapshot credentials + */ +export function renderSnapshotCredentials(scope: Construct, credentials?: SnapshotCredentials) { + let renderedCredentials = credentials; + + let secret = renderedCredentials?.secret; + if (!secret && renderedCredentials?.generatePassword) { + if (!renderedCredentials.username) { + throw new Error('`snapshotCredentials` `username` must be specified when `generatePassword` is set to true'); + } + + renderedCredentials = SnapshotCredentials.fromSecret( + new DatabaseSecret(scope, 'SnapshotSecret', { + username: renderedCredentials.username, + encryptionKey: renderedCredentials.encryptionKey, + excludeCharacters: renderedCredentials.excludeCharacters, + replaceOnPasswordCriteriaChanges: renderedCredentials.replaceOnPasswordCriteriaChanges, + replicaRegions: renderedCredentials.replicaRegions, + }), + ); + } + + return renderedCredentials; +} + /** * The RemovalPolicy that should be applied to a "helper" resource, if the base resource has the given removal policy * diff --git a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts index 43bd2f63466b5..a759faa15ffdb 100644 --- a/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/cluster.test.ts @@ -6,7 +6,10 @@ import * as logs from '../../aws-logs'; import * as s3 from '../../aws-s3'; import * as cdk from '../../core'; import { RemovalPolicy, Stack, Annotations as CoreAnnotations } from '../../core'; -import { AURORA_CLUSTER_CHANGE_SCOPE_OF_INSTANCE_PARAMETER_GROUP_WITH_EACH_PARAMETERS } from '../../cx-api'; +import { + RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS, + AURORA_CLUSTER_CHANGE_SCOPE_OF_INSTANCE_PARAMETER_GROUP_WITH_EACH_PARAMETERS, +} from '../../cx-api'; import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, CfnDBCluster, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup, DatabaseSecret, @@ -3346,6 +3349,56 @@ describe('cluster', () => { }); }); + test('secret from deprecated credentials is created with feature flag unset', () => { + // GIVEN + const stack = testStack(); + stack.node.setContext(RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS, false); + + const vpc = new ec2.Vpc(stack, 'VPC'); + + const secret = new DatabaseSecret(stack, 'DBSecret', { + username: 'admin', + encryptionKey: new kms.Key(stack, 'PasswordKey'), + }); + + // WHEN + new DatabaseClusterFromSnapshot(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + snapshotIdentifier: 'mySnapshot', + snapshotCredentials: SnapshotCredentials.fromSecret(secret), + writer: ClusterInstance.serverlessV2('writer'), + vpc, + }); + + // THEN + Template.fromStack(stack).resourceCountIs('AWS::SecretsManager::Secret', 2); + }); + + test('secret from deprecated credentials is not created with feature flag set', () => { + // GIVEN + const stack = testStack(); + stack.node.setContext(RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS, true); + + const vpc = new ec2.Vpc(stack, 'VPC'); + + const secret = new DatabaseSecret(stack, 'DBSecret', { + username: 'admin', + encryptionKey: new kms.Key(stack, 'PasswordKey'), + }); + + // WHEN + new DatabaseClusterFromSnapshot(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + snapshotIdentifier: 'mySnapshot', + snapshotCredentials: SnapshotCredentials.fromSecret(secret), + writer: ClusterInstance.serverlessV2('writer'), + vpc, + }); + + // THEN + Template.fromStack(stack).resourceCountIs('AWS::SecretsManager::Secret', 1); + }); + test('create a cluster from a snapshot with encrypted storage', () => { const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); diff --git a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md index 7004c5ec5e873..a4af49626f5a3 100644 --- a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +++ b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md @@ -59,6 +59,7 @@ Flags come in three types: | [@aws-cdk/aws-efs:denyAnonymousAccess](#aws-cdkaws-efsdenyanonymousaccess) | EFS denies anonymous clients accesses | 2.93.0 | (default) | | [@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId](#aws-cdkaws-efsmounttargetorderinsensitivelogicalid) | When enabled, mount targets will have a stable logicalId that is linked to the associated subnet. | 2.93.0 | (fix) | | [@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion](#aws-cdkaws-lambda-nodejsuselatestruntimeversion) | Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default | 2.93.0 | (default) | +| [@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials](#aws-cdkaws-rdspreventrenderingdeprecatedcredentials) | When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials. | V2NEXT | (fix) | | [@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier](#aws-cdkaws-appsyncusearnforsourceapiassociationidentifier) | When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id. | 2.97.0 | (fix) | | [@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters](#aws-cdkaws-rdsauroraclusterchangescopeofinstanceparametergroupwitheachparameters) | When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change. | 2.97.0 | (fix) | @@ -111,6 +112,7 @@ The following json shows the current recommended set of flags, as `cdk init` wou "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true } @@ -1117,6 +1119,29 @@ shipped as part of the runtime environment. **Compatibility with old behavior:** Pass `runtime: lambda.Runtime.NODEJS_16_X` to `Function` construct to restore the previous behavior. +### @aws-cdk/aws-rds:preventRenderingDeprecatedCredentials + +*When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials.* (fix) + +The `credentials` property on the `DatabaseClusterFromSnapshotProps` +interface was deprecated with the new `snapshotCredentials` property being +recommended. Before deprecating `credentials`, a secret would be generated +while rendering credentials if the `credentials` property was undefined or +if a secret wasn't provided via the `credentials` property. This behavior +is replicated with the new `snapshotCredentials` property, but the original +`credentials` secret can still be created resulting in an extra database +secret. + +Set this flag to prevent rendering deprecated `credentials` and creating an +extra database secret when only using `snapshotCredentials` to create an RDS +database cluster from a snapshot. + +| Since | Default | Recommended | +| ----- | ----- | ----- | +| (not in v1) | | | +| V2NEXT | `false` | `true` | + + ### @aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier *When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id.* (fix) diff --git a/packages/aws-cdk-lib/cx-api/lib/features.ts b/packages/aws-cdk-lib/cx-api/lib/features.ts index 74ebe0a171452..ee273c772e0e4 100644 --- a/packages/aws-cdk-lib/cx-api/lib/features.ts +++ b/packages/aws-cdk-lib/cx-api/lib/features.ts @@ -93,6 +93,7 @@ export const EFS_MOUNTTARGET_ORDERINSENSITIVE_LOGICAL_ID = '@aws-cdk/aws-efs:mou export const AUTOSCALING_GENERATE_LAUNCH_TEMPLATE = '@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig'; export const ENABLE_OPENSEARCH_MULTIAZ_WITH_STANDBY = '@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby'; export const LAMBDA_NODEJS_USE_LATEST_RUNTIME = '@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion'; +export const RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS = '@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials'; export const AURORA_CLUSTER_CHANGE_SCOPE_OF_INSTANCE_PARAMETER_GROUP_WITH_EACH_PARAMETERS = '@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters'; export const APPSYNC_ENABLE_USE_ARN_IDENTIFIER_SOURCE_API_ASSOCIATION = '@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier'; @@ -894,6 +895,7 @@ export const FLAGS: Record = { recommendedValue: true, compatibilityWithOldBehaviorMd: 'Pass `runtime: lambda.Runtime.NODEJS_16_X` to `Function` construct to restore the previous behavior.', }, + ////////////////////////////////////////////////////////////////////// [EFS_MOUNTTARGET_ORDERINSENSITIVE_LOGICAL_ID]: { type: FlagType.BugFix, @@ -909,6 +911,7 @@ export const FLAGS: Record = { introducedIn: { v2: '2.93.0' }, recommendedValue: true, }, + ////////////////////////////////////////////////////////////////////// [AURORA_CLUSTER_CHANGE_SCOPE_OF_INSTANCE_PARAMETER_GROUP_WITH_EACH_PARAMETERS]: { type: FlagType.BugFix, @@ -937,6 +940,28 @@ export const FLAGS: Record = { introducedIn: { v2: '2.97.0' }, recommendedValue: true, }, + + ////////////////////////////////////////////////////////////////////// + [RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS]: { + type: FlagType.BugFix, + summary: 'When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials.', + detailsMd: ` + The \`credentials\` property on the \`DatabaseClusterFromSnapshotProps\` + interface was deprecated with the new \`snapshotCredentials\` property being + recommended. Before deprecating \`credentials\`, a secret would be generated + while rendering credentials if the \`credentials\` property was undefined or + if a secret wasn't provided via the \`credentials\` property. This behavior + is replicated with the new \`snapshotCredentials\` property, but the original + \`credentials\` secret can still be created resulting in an extra database + secret. + + Set this flag to prevent rendering deprecated \`credentials\` and creating an + extra database secret when only using \`snapshotCredentials\` to create an RDS + database cluster from a snapshot. + `, + introducedIn: { v2: 'V2NEXT' }, + recommendedValue: true, + }, }; const CURRENT_MV = 'v2';