diff --git a/packages/@aws-cdk/aws-efs/README.md b/packages/@aws-cdk/aws-efs/README.md index 444a3a69d7480..bccae05b20547 100644 --- a/packages/@aws-cdk/aws-efs/README.md +++ b/packages/@aws-cdk/aws-efs/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- @@ -44,10 +34,10 @@ Access (IA) storage class. ```ts const fileSystem = new efs.FileSystem(this, 'MyEfsFileSystem', { vpc: new ec2.Vpc(this, 'VPC'), - encrypted: true, // file system is not encrypted by default lifecyclePolicy: efs.LifecyclePolicy.AFTER_14_DAYS, // files are not transitioned to infrequent access (IA) storage by default performanceMode: efs.PerformanceMode.GENERAL_PURPOSE, // default }); + ``` ⚠️ An Amazon EFS file system's performance mode can't be changed after the file system has been created. diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index 60af6fde51752..99e390a89257d 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,6 +1,10 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import { ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Tags } from '@aws-cdk/core'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports +import { FeatureFlags } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { AccessPoint, AccessPointOptions } from './access-point'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; @@ -122,7 +126,8 @@ export interface FileSystemProps { /** * Defines if the data at rest in the file system is encrypted or not. * - * @default false + * @default - If your application has the '@aws-cdk/aws-efs:defaultEncryptionAtRest' feature flag set, the default is true, otherwise, the default is false. + * @link https://docs.aws.amazon.com/cdk/latest/guide/featureflags.html */ readonly encrypted?: boolean; @@ -249,8 +254,13 @@ export class FileSystem extends Resource implements IFileSystem { throw new Error('Property provisionedThroughputPerSecond is required when throughputMode is PROVISIONED'); } + // we explictly use 'undefined' to represent 'false' to maintain backwards compatibility since + // its considered an actual change in CloudFormations eyes, even though they have the same meaning. + const encrypted = props.encrypted ?? (FeatureFlags.of(this).isEnabled( + cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST) ? true : undefined); + const filesystem = new CfnFileSystem(this, 'Resource', { - encrypted: props.encrypted, + encrypted: encrypted, kmsKeyId: props.kmsKey?.keyArn, lifecyclePolicies: (props.lifecyclePolicy ? [{ transitionToIa: props.lifecyclePolicy }] : undefined), performanceMode: props.performanceMode, diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 727748483bf92..0c7a65bd99f14 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -103,8 +103,8 @@ "resource-attribute:@aws-cdk/aws-efs.FileSystem.fileSystemArn" ] }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index 190b3b7a9d18a..ff976ae112aa1 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -1,7 +1,8 @@ -import { expect as expectCDK, haveResource, ResourcePart, countResources } from '@aws-cdk/assert-internal'; +import { ABSENT, expect as expectCDK, haveResource, ResourcePart, countResources } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import { RemovalPolicy, Size, Stack, Tags } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode } from '../lib'; let stack = new Stack(); @@ -12,6 +13,53 @@ beforeEach(() => { vpc = new ec2.Vpc(stack, 'VPC'); }); +test(`when ${cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST} is enabled, encryption is enabled by default`, () => { + + const customStack = new Stack(); + customStack.node.setContext(cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST, true); + + const customVpc = new ec2.Vpc(customStack, 'VPC'); + new FileSystem(customVpc, 'EfsFileSystem', { + vpc: customVpc, + }); + + expectCDK(customStack).to(haveResource('AWS::EFS::FileSystem', { + Encrypted: true, + })); + +}); + +test(`when ${cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST} is disabled, encryption is disabled by default`, () => { + + const customStack = new Stack(); + customStack.node.setContext(cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST, false); + + const customVpc = new ec2.Vpc(customStack, 'VPC'); + new FileSystem(customVpc, 'EfsFileSystem', { + vpc: customVpc, + }); + + expectCDK(customStack).to(haveResource('AWS::EFS::FileSystem', { + Encrypted: ABSENT, + })); + +}); + +test(`when ${cxapi.EFS_DEFAULT_ENCRYPTION_AT_REST} is missing, encryption is disabled by default`, () => { + + const customStack = new Stack(); + + const customVpc = new ec2.Vpc(customStack, 'VPC'); + new FileSystem(customVpc, 'EfsFileSystem', { + vpc: customVpc, + }); + + expectCDK(customStack).to(haveResource('AWS::EFS::FileSystem', { + Encrypted: ABSENT, + })); + +}); + test('default file system is created correctly', () => { // WHEN new FileSystem(stack, 'EfsFileSystem', { diff --git a/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json b/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json index 8c1fa47948791..3ceefb75f5c10 100644 --- a/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json +++ b/packages/@aws-cdk/aws-efs/test/integ.efs.expected.json @@ -453,6 +453,7 @@ "FileSystem8A8E25C0": { "Type": "AWS::EFS::FileSystem", "Properties": { + "Encrypted": true, "FileSystemTags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json index 3d17a0e6ca6bf..6d9334ab8999f 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json @@ -453,6 +453,7 @@ "Efs9E8BF36B": { "Type": "AWS::EFS::FileSystem", "Properties": { + "Encrypted": true, "FileSystemTags": [ { "Key": "Name", @@ -738,17 +739,14 @@ "Code": { "ZipFile": "\nimport json\nimport os\nimport string\nimport random\nimport datetime\n\nMSG_FILE_PATH = '/mnt/msg/content'\n\ndef randomString(stringLength=10):\n letters = string.ascii_lowercase\n return ''.join(random.choice(letters) for i in range(stringLength))\n\ndef lambda_handler(event, context):\n with open(MSG_FILE_PATH, 'a') as f:\n f.write(f\"{datetime.datetime.utcnow():%Y-%m-%d-%H:%M:%S} \" + randomString(5) + ' ')\n\n file = open(MSG_FILE_PATH, \"r\")\n file_content = file.read()\n file.close()\n\n return {\n 'statusCode': 200,\n 'body': str(file_content)\n }\n" }, - "Handler": "index.lambda_handler", "Role": { "Fn::GetAtt": [ "MyLambdaServiceRole4539ECB6", "Arn" ] }, - "Runtime": "python3.7", "FileSystemConfigs": [ { - "LocalMountPath": "/mnt/msg", "Arn": { "Fn::Join": [ "", @@ -771,9 +769,12 @@ } ] ] - } + }, + "LocalMountPath": "/mnt/msg" } ], + "Handler": "index.lambda_handler", + "Runtime": "python3.7", "VpcConfig": { "SecurityGroupIds": [ { @@ -983,17 +984,14 @@ "Code": { "ZipFile": "\nimport json\nimport os\nimport string\nimport random\nimport datetime\n\nMSG_FILE_PATH = '/mnt/msg/content'\n\ndef randomString(stringLength=10):\n letters = string.ascii_lowercase\n return ''.join(random.choice(letters) for i in range(stringLength))\n\ndef lambda_handler(event, context):\n with open(MSG_FILE_PATH, 'a') as f:\n f.write(f\"{datetime.datetime.utcnow():%Y-%m-%d-%H:%M:%S} \" + randomString(5) + ' ')\n\n file = open(MSG_FILE_PATH, \"r\")\n file_content = file.read()\n file.close()\n\n return {\n 'statusCode': 200,\n 'body': str(file_content)\n }\n" }, - "Handler": "index.lambda_handler", "Role": { "Fn::GetAtt": [ "MyLambda2ServiceRoleD09B370C", "Arn" ] }, - "Runtime": "python3.7", "FileSystemConfigs": [ { - "LocalMountPath": "/mnt/msg", "Arn": { "Fn::Join": [ "", @@ -1016,9 +1014,12 @@ } ] ] - } + }, + "LocalMountPath": "/mnt/msg" } ], + "Handler": "index.lambda_handler", + "Runtime": "python3.7", "VpcConfig": { "SecurityGroupIds": [ { diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index cdb2217a16b4e..a2525fafa71c0 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -135,6 +135,12 @@ export const RDS_LOWERCASE_DB_IDENTIFIER = '@aws-cdk/aws-rds:lowercaseDbIdentifi */ export const APIGATEWAY_USAGEPLANKEY_ORDERINSENSITIVE_ID = '@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId'; +/** + * Enable this feature flag to have elastic file systems encrypted at rest by default. + * + * Encryption can also be configured explicitly using the `encrypted` property. + */ +export const EFS_DEFAULT_ENCRYPTION_AT_REST = '@aws-cdk/aws-efs:defaultEncryptionAtRest'; /** * This map includes context keys and values for feature flags that enable * capabilities "from the future", which we could not introduce as the default @@ -159,6 +165,7 @@ export const FUTURE_FLAGS: { [key: string]: any } = { [S3_GRANT_WRITE_WITHOUT_ACL]: true, [ECS_REMOVE_DEFAULT_DESIRED_COUNT]: true, [RDS_LOWERCASE_DB_IDENTIFIER]: true, + [EFS_DEFAULT_ENCRYPTION_AT_REST]: true, // We will advertise this flag when the feature is complete // [NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: 'true', @@ -187,6 +194,7 @@ const FUTURE_FLAGS_DEFAULTS: { [key: string]: boolean } = { [S3_GRANT_WRITE_WITHOUT_ACL]: false, [ECS_REMOVE_DEFAULT_DESIRED_COUNT]: false, [RDS_LOWERCASE_DB_IDENTIFIER]: false, + [EFS_DEFAULT_ENCRYPTION_AT_REST]: false, }; export function futureFlagDefault(flag: string): boolean {