import { FileSystem } from '@aws-cdk/aws-efs'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; import { Arn } from '@aws-cdk/core'; import { EfsAccessPoints } from './efs-access-points'; import { ApplicationLoadBalancedEc2Service, ApplicationLoadBalancedFargateService } from '@aws-cdk/aws-ecs-patterns'; /** * We use this `AwsCustomResource` construct to create the EFS filesystem policy via the AWS SDK. This * is a stop-gap until support for creating EFS filesystem policies via CloudFormation and CDK is available. */ export class EfsFileSystemPolicy extends AwsCustomResource { constructor( fileSystem: FileSystem, accessPoints: EfsAccessPoints, ecsOnEc2Service?: ApplicationLoadBalancedEc2Service, ecsOnFargateService?: ApplicationLoadBalancedFargateService, ) { // We'll be using this value several times throughout. const fileSystemArn = Arn.format( {service: 'elasticfilesystem', resource: 'file-system', resourceName: fileSystem.fileSystemId}, fileSystem.stack, ); /* Initialize statement list with equivalent of: * Disable root access by default * Enforce read-only access by default * Enforce in-transit encryption for all clients */ const statements: object[] = [ { Sid: 'DisableRootAccessAndEnforceReadOnlyByDefault', Effect: 'Allow', Action: 'elasticfilesystem:ClientMount', Principal: { AWS: '*', }, Resource: fileSystemArn, }, { Sid: 'EnforceInTransitEncryption', Effect: 'Deny', Action: ['*'], Principal: { AWS: '*', }, Resource: fileSystemArn, Condition: { Bool: {'aws:SecureTransport': false}, }, }, ]; if (ecsOnEc2Service) { // Allow r/w for ECS on EC2 Service to Common, EcsPrivate, and EcsShared statements.push({ Sid: 'EcsOnEc2CloudCmdTaskReadWriteAccess', Effect: 'Allow', Action: [ 'elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite', ], Principal: { AWS: ecsOnEc2Service.taskDefinition.taskRole.roleArn, }, Resource: fileSystemArn, Condition: { StringEquals: { 'elasticfilesystem:AccessPointArn': [ accessPoints.get('Common')?.getResponseField('AccessPointArn'), accessPoints.get('EcsPrivate')?.getResponseField('AccessPointArn'), accessPoints.get('EcsShared')?.getResponseField('AccessPointArn'), ].filter((arn) => arn), }, }, }); if (ecsOnFargateService) { // Allow readonly for ECS on EC2 Service to FargateShared, if exists. statements.push({ Sid: 'EcsOnEc2CloudCmdTaskReadAccess', Effect: 'Allow', Action: [ 'elasticfilesystem:ClientMount', ], Principal: { AWS: ecsOnEc2Service.taskDefinition.taskRole.roleArn, }, Resource: fileSystemArn, Condition: { StringEquals: { 'elasticfilesystem:AccessPointArn': [ accessPoints.get('FargateShared')?.getResponseField('AccessPointArn'), ].filter((arn) => arn), }, }, }); } } if (ecsOnFargateService) { // Allow r/w for ECS on Fargate Service to Common, FargatePrivate, and FargateShared statements.push({ Sid: 'EcsOnFargateCloudCmdTaskReadWriteAccess', Effect: 'Allow', Action: [ 'elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite', ], Principal: { AWS: ecsOnFargateService.taskDefinition.taskRole.roleArn, }, Resource: fileSystemArn, Condition: { StringEquals: { 'elasticfilesystem:AccessPointArn': [ accessPoints.get('Common')?.getResponseField('AccessPointArn'), accessPoints.get('FargatePrivate')?.getResponseField('AccessPointArn'), accessPoints.get('FargateShared')?.getResponseField('AccessPointArn'), ].filter((arn) => arn), }, }, }); if (ecsOnEc2Service) { // Allow readonly for ECS on Fargate Service to EcsShared, if exists. statements.push({ Sid: 'EcsOnFargateCloudCmdTaskReadAccess', Effect: 'Allow', Action: [ 'elasticfilesystem:ClientMount', ], Principal: { AWS: ecsOnFargateService.taskDefinition.taskRole.roleArn, }, Resource: fileSystemArn, Condition: { StringEquals: { 'elasticfilesystem:AccessPointArn': [ accessPoints.get('EcsShared')?.getResponseField('AccessPointArn'), ].filter((arn) => arn), }, }, }); } } const createOrUpdateFileSystemPolicy = { action: 'putFileSystemPolicy', parameters: { FileSystemId: fileSystem.fileSystemId, Policy: fileSystem.stack.toJsonString({ Version: '2012-10-17', Statement: statements, }), }, physicalResourceId: PhysicalResourceId.fromResponse('FileSystemId'), service: 'EFS', }; // Create the actual policy based on the statements assembled above. super( fileSystem.stack, 'EfsFileSystemPolicy', { onCreate: createOrUpdateFileSystemPolicy, onUpdate: createOrUpdateFileSystemPolicy, policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE, }), }, ); } };