Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rds): default DatabaseCluser and DatabaseInstance storageEncrypted to true (under feature flag) #32695

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

blimmer
Copy link
Contributor

@blimmer blimmer commented Dec 31, 2024

Issue # (if applicable)

Closes #32398

Reason for this change

Today, if you don't remember to pass storageEncrypted: true to your DatabaseInstance or DatabaseCluster, your database's storage will be unencrypted.

In almost all cases, your database storage should be encrypted. Here are reasons I think we should use AWS-managed keys by default:

  • AWS Well-Architected security pillar says all data should be encrypted at rest.
  • There is no additional fee to use the AWS-managed KMS key
  • When creating RDS instances and clusters via the AWS Console UI, encryption with AWS-managed keys is enabled by default.
  • Adding encryption to a previously unencrypted database is an involved process that requires recreating your database from a snapshot. Applying encryption to an unencrypted database is time-consuming and a bad developer and user experience.
  • Other L2 constructs apply encryption by default, including Neptune and DocDb
  • If you don't encrypt your storage, you'll see warnings in the RDS UI telling you that your storage is unencrypted.
  • Users can easily retain the existing behavior.

Description of changes

This PR introduces a new feature flag, @aws-cdk/aws-rds:enableEncryptionAtRestByDefault. When set to true, storageEncrypted will default to true instead of undefined (which means "false").

To provide backward compatibility, this PR also introduces a new property, storageEncryptedLegacyDefaultValue. This allows leaving the StorageEncrypted property unset in the CloudFormation template, to avoid replacing Database instances/clusters.

Describe any new or updated permissions being added

N/A

Description of how you validated changes

Unit and integration tests.

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@blimmer blimmer marked this pull request as ready for review December 31, 2024 04:45
@github-actions github-actions bot added effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2 labels Dec 31, 2024
@aws-cdk-automation aws-cdk-automation requested a review from a team December 31, 2024 04:45
@github-actions github-actions bot added the admired-contributor [Pilot] contributed between 13-24 PRs to the CDK label Dec 31, 2024
@@ -35,6 +35,8 @@ class TestStack extends cdk.Stack {
vpc,
Copy link
Contributor Author

@blimmer blimmer Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change affected a lot of integration tests. I opted to use the legacy behaviors for these tests because re-running the creation of all these databases would be extremely slow and expensive.

If someone at AWS with a "free"/"expensed" account would like to rerun all these failing tests, we could go that route, too.

I added explicit new tests for this behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think manually adding "StorageEncrypted": true to the templates would make those tests pass without the need to deploy the related resources (we expect the flag to work given that you added the new test).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past, I've received PR feedback that you should never manually update snapshot files. I just confirmed that I could add this property manually to the snapshots and that would be an alternative solution to updating the stack definitions.

I'd like a +1 from an approver that this would be OK before I spend the time doing it, based on previous feedback that snapshots should never be manually edited.

Copy link
Contributor

@lpizzinidev lpizzinidev Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've received PR feedback that you should never manually update snapshot files

👍 Sounds good!

About the isStorageLegacyUnencrypted comment.
Thanks for clarifying, I'm not sure why CFN defaults to unencrypted clusters and instances.
However, I left some comments on the implementation for small adjustments.

Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request linter has failed. See the aws-cdk-automation comment below for failure reasons. If you believe this pull request should receive an exemption, please comment and provide a justification.

A comment requesting an exemption should contain the text Exemption Request. Additionally, if clarification is needed add Clarification Request to a comment.

Copy link

codecov bot commented Dec 31, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.21%. Comparing base (7538a84) to head (abc94e0).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #32695   +/-   ##
=======================================
  Coverage   82.21%   82.21%           
=======================================
  Files         119      119           
  Lines        6876     6876           
  Branches     1162     1162           
=======================================
  Hits         5653     5653           
  Misses       1120     1120           
  Partials      103      103           
Flag Coverage Δ
suite.unit 82.21% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
packages/aws-cdk ∅ <ø> (∅)
packages/aws-cdk-lib/core 82.21% <ø> (ø)

@aws-cdk-automation aws-cdk-automation dismissed their stale review December 31, 2024 18:10

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@aws-cdk-automation aws-cdk-automation added the pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. label Dec 31, 2024
Copy link
Contributor

@lpizzinidev lpizzinidev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To provide backward compatibility, this PR also introduces a new property, isStorageLegacyUnencrypted. This allows leaving the StorageEncrypted property unset in the CloudFormation template, to avoid replacing Database instances/clusters.

Shouldn't the default behavior be applied when @aws-cdk/aws-rds:enableEncryptionAtRestByDefault: false? 🤔
The extra variable seems to add unnecessary complexity here.

@@ -35,6 +35,8 @@ class TestStack extends cdk.Stack {
vpc,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think manually adding "StorageEncrypted": true to the templates would make those tests pass without the need to deploy the related resources (we expect the flag to work given that you added the new test).

@aws-cdk-automation aws-cdk-automation removed the pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. label Jan 3, 2025
@blimmer
Copy link
Contributor Author

blimmer commented Jan 3, 2025

@lpizzinidev - thanks for the review. I commented on your inline comment, and below is a comment that you requested changed for (it's annoying that GitHub won't let you respond inline to those).

Suggestion to reuse feature flag vs isStorageLegacyUnencrypted

This would work for v2, but if the feature flag is ever removed (it looks like all v1 flags were removed from v2 - see docs), this would no longer work. My understanding is that when v3 is released, the feature flag will be removed, and we will need to remember to re-add something like isStorageLegacyUnencrypted.

This is very similar to the isFromLegacyInstanceProps provided for a similar reason, to prevent replacing database resources (docs).

Unfortunately, because the behavior is to pass undefined today, it makes it a bit challenging to work with. CloudFormation reports that changing undefined -> false causes resource replacement. I feel fairly strongly that the new behavior should be to always explicitly set the value to true or false, which led me to this solution.

If there's another way I should be thinking about this (like retaining that feature flag indefinitely - I don't know if that's an option), let me know.

@blimmer blimmer requested a review from lpizzinidev January 4, 2025 18:14
@blimmer
Copy link
Contributor Author

blimmer commented Jan 4, 2025

@lpizzinidev - thanks for the thorough review and great comments. This is ready for re-review.

Copy link
Contributor

@lpizzinidev lpizzinidev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 🚀 Left comments for some final adjustments

@blimmer blimmer requested a review from lpizzinidev January 6, 2025 21:15
@blimmer
Copy link
Contributor Author

blimmer commented Jan 6, 2025

@lpizzinidev - thanks! should be good to go.

Copy link
Contributor

@lpizzinidev lpizzinidev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 👍

@aws-cdk-automation aws-cdk-automation added the pr/needs-maintainer-review This PR needs a review from a Core Team Member label Jan 7, 2025
@blimmer blimmer force-pushed the enable-rds-encryption-by-default branch from 14795d0 to bddf5b9 Compare February 2, 2025 18:30
Copy link
Member

@godwingrs22 godwingrs22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @blimmer for raising this issue and for this contribution! I have left few comments to confirm my understanding. I also suggested few modifications around the feature flag. Looking forward to your thoughts on the suggested changes!

Comment on lines +191 to +194
// If a KMS key is provided, enable encryption at rest
if (storageEncryptionKey) {
return true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, when storageEncryptionKey is provided, the code forces storageEncrypted to true, regardless of what value the user explicitly set for storageEncrypted. This means even if a user specifically sets storageEncrypted: false, the encryption is still enabled, which seems to contradict the purpose of the storageEncrypted property.

In that case do we still need the same behaviour in the new approach? Would it be better to validate and throw error with clear message (when feature flag is enabled). Something like:

// Validation: Cannot disable encryption while providing an encryption key
  if (storageEncryptionKey && storageEncrypted === false) {
    throw new Error(
      'Cannot set storageEncrypted to false when storageEncryptionKey is provided. ' +
      'Either enable encryption or remove the storageEncryptionKey.'
    );
  }

Please correct me if my understanding is wrong.

Comment on lines +196 to +202
if (storageEncryptedLegacyDefaultValue) {
if (storageEncrypted) {
throw new ValidationError('Cannot set `storageEncrypted` to `true` when `storageEncryptedLegacyDefaultValue` is `true`.', scope);
}

return undefined;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't fully understood the usage of this new property. Could you help me to explain further with different scenarios?

Comment on lines +1152 to 1154
* @default - If the `@aws-cdk/aws-rds:enableEncryptionAtRestByDefault` feature flag is set, true by default or if storageEncryptionKey is provided. If the flag is not set, undefined by default unless storageEncryptionKey is provided.
*/
readonly storageEncrypted?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not quite clear on the default behaviour based on reading the doc string. Would be nice to rephrase it.

*
* The legacy behavior left the `StorageEncrypted` CloudFormation property unset by default. This resulted in
* cluster storage being unencrypted. The new behavior always sets the `StorageEncrypted` property to `true` or
* `false` for clarity. However, CloudFormation will replace the Cluster if you change `StorageEncrypted` from
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new behavior always sets the StorageEncrypted property to true or false for clarity.

I don't think it is true based on the method implementation. I could see it may return undefined if the storageEncryptedLegacyDefaultValue is true and storageEncrypted is undefined or false.

Comment on lines +178 to +184
export function getStorageEncryptedProperty(
scope: IConstruct,
storageEncrypted?: boolean,
storageEncryptedLegacyDefaultValue?: boolean,
storageEncryptionKey?: kms.IKey,
): boolean | undefined {
const featureFlagEnabled = FeatureFlags.of(scope).isEnabled(cxapi.RDS_ENABLE_ENCRYPTION_AT_REST_BY_DEFAULT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me if I understood the requirement wrongly. We want to make storageEncrypted default to true which will enable encryption at rest. If storageEncryptionKey is provided, then will use the provided kms key; if not, will use the default AWS managed key for encryption. If customer explicitly set storageEncrypted as false, then encryption will be disabled.

  1. If feature flag is false (existing behaviour):

Existing CDK projects will have default feature flag as false. This preserves backward compatibility:

// No explicit settings - remains undefined
new DatabaseCluster(this, 'Cluster', {});

// With encryption key - forces true
new DatabaseCluster(this, 'Cluster', {
  storageEncryptionKey: key
});

// With encryption key - forces true, ignores storageEncrypted value provided by customer
new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: false,
  storageEncryptionKey: key
});

//Explicit false
new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: false
});

// If storageEncrypted is true and no encryption key specified, uses default aws managed key
new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: true
});
  1. If feature flag is true (new behaviour)

New CDK projects will have the feature flag enabled by default, making storageEncrypted: true the default:

// No explicit settings - defaults to true
new DatabaseCluster(this, 'Cluster', {});  // storageEncrypted: true and uses default AWS managed key for encryption

// Explicit false is respected
new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: false
});  // storageEncrypted: false

// Uses provided encryption key
new DatabaseCluster(this, 'Cluster2', {
  storageEncryptionKey: key  // default storageEncrypted is true and takes the user provided key for encryption
});

// Explicit encryption configuration
new DatabaseCluster(this, 'Cluster1', {
  storageEncrypted: true, //explicit true
  storageEncryptionKey: key // Enables encryption with user provided key
});

// Invalid configuration - will throw error
new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: false,
  storageEncryptionKey: key
});  // Error: Cannot set storageEncrypted to false when storageEncryptionKey is provided
    

If the above requirements are true, i think the following code might work without the need for additional property.

export function getStorageEncrypted(
  scope: IConstruct,
  storageEncrypted?: boolean,
  storageEncryptionKey?: kms.IKey,
): boolean | undefined {
  const featureFlagEnabled = FeatureFlags.of(scope).isEnabled(cxapi.RDS_ENABLE_ENCRYPTION_AT_REST_BY_DEFAULT);

  // Feature flag is OFF - maintain existing behavior
  if (!featureFlagEnabled) {
    return storageEncryptionKey ? true : storageEncrypted;
  }

  // Feature flag is ON - new behavior
  // Validation: Cannot disable encryption while providing an encryption key
  if (storageEncryptionKey && storageEncrypted === false) {
    throw new Error(
      'Cannot set storageEncrypted to false when storageEncryptionKey is provided. ' +
      'Either enable encryption or remove the storageEncryptionKey.'
    );
  }

  if (storageEncrypted !== undefined) {
    return storageEncrypted;
  }

  // Default to true when feature flag is ON and no explicit value provided
  return true;
}

Let me know if this makes sense or if I've misunderstood any of the requirements.

Copy link
Contributor Author

@blimmer blimmer Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @godwingrs22 - the extra property is required when CDK v3 is released and the feature flag is removed. That's because the legacy default behavior leaves the storageEncrypted property undefined in the template. In effect, this means "unencrypted".

Without the extra property, when the feature flag is removed, you'd think that users could do this:

new DatabaseCluster(this, 'Cluster', {
  storageEncrypted: false
});

However, changing the StorageEncrypted property from undefined to false reports that the cluster/instance will be replaced. We do not want to force our users to replace their cluster/instance when the feature flag is removed.

Therefore, I think we have to provide a way to retain the legacy behavior of not setting the StorageEncryption property at all essentially forever to prevent replacing existing databases that used the old pre-feature-flag behavior.

Does that make sense why I think we need this extra property?

See also #32695 (comment) where I discussed this above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @blimmer , Thanks for the clarification. Yes it makes sense to have the new property to preserve the legacy behaviour if the feature flag is removed in future.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: abc94e0
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

Copy link
Member

@godwingrs22 godwingrs22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @blimmer, Thanks for the clarification. LGTM. I'll internally check with RDS service team once to see if there is any feedback from them on enabling the encryption at rest by default.

Comment on lines +178 to +184
export function getStorageEncryptedProperty(
scope: IConstruct,
storageEncrypted?: boolean,
storageEncryptedLegacyDefaultValue?: boolean,
storageEncryptionKey?: kms.IKey,
): boolean | undefined {
const featureFlagEnabled = FeatureFlags.of(scope).isEnabled(cxapi.RDS_ENABLE_ENCRYPTION_AT_REST_BY_DEFAULT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @blimmer , Thanks for the clarification. Yes it makes sense to have the new property to preserve the legacy behaviour if the feature flag is removed in future.

@blimmer
Copy link
Contributor Author

blimmer commented Mar 10, 2025

OK, I will pause for your response before fixing up the PR to prep for final review / merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
admired-contributor [Pilot] contributed between 13-24 PRs to the CDK effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2 pr/needs-maintainer-review This PR needs a review from a Core Team Member
Projects
None yet
5 participants