aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.51k stars 3.85k forks source link

(AWS EFS): (Circular Dependencies when trying to grant perms to imported task role for efs filesystem and access point) #27690

Open DLundAJB opened 10 months ago

DLundAJB commented 10 months ago

Describe the bug

When defining an EFS filesystem and an access point, with an imported task role that needs access to the filesystem. The template generated warns of circular dependencies for all infra defined in that stack.

Example code:

const taskRole = iam.Role.fromRoleArn(this, 'TaskRoleMaster', Fn.importValue(`${props.environmentName}-${props.projectName}-ECSTaskRole`))

const s3Bucket = new s3.Bucket(this, 'BucketEFSSync', {
      versioned: true,
    })

new s3deploy.BucketDeployment(this, 'DeployEfs', {
      sources: [s3deploy.Source.asset('src/resources/efs')],
      destinationBucket: s3Bucket,
    })

const efsSecurityGroup = new ec2.SecurityGroup(this, 'EfsSecurityGroup', {
      vpc: vpcEnvironment.vpc,
    })
efsSecurityGroup.connections.allowFrom(efsSecurityGroup, ec2.Port.tcp(2049))

const efsFileSystem = new efs.FileSystem(this, 'EfsFileSystem', {
      vpc: vpcEnvironment.vpc,
      vpcSubnets: vpcEnvironment.vpc.selectSubnets({ subnetGroupName: 'Application' }),
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      encrypted: true,
      securityGroup: efsSecurityGroup,
      enableAutomaticBackups: true,
    })
efsFileSystem.applyRemovalPolicy(RemovalPolicy.DESTROY)

const efsAccessPoint = efsFileSystem.addAccessPoint('AP', {
      createAcl: {
        ownerGid: '33',
        ownerUid: '33', 
        permissions: '0770',
      },
      posixUser: {
        uid: '33', 
        gid: '33',
      },
      path: 'sites/default/files',
    })

const efsPolicy = new iam.Policy(this, 'EFS', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            'elasticfilesystem:ClientRootAccess',
            'elasticfilesystem:ClientWrite',
            'elasticfilesystem:ClientMount',
            'elasticfilesystem:DescribeMountTargets',
          ],
          resources: [`arn:aws:elasticfilesystem:${Aws.REGION}:${Aws.ACCOUNT_ID}:file-system/${efsFileSystem.fileSystemId}`],
        }),
      ],
    })
taskRole.attachInlinePolicy(efsPolicy)
efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientWrite')
efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientRootAccess')

Further in the stack is a datasync task between s3 and efs, once generated there is a circular dependency between the filesystem, mount points, task role policy, data sync task.

However, using an inline defined task role does not seem to cause this circular dependency invalidation which is a quick fix for now but not one I would like to use permanently.

Expected Behavior

EFS filesystem and access point created, imported ecs task role has read/write and root access to efs and the inverse.

Current Behavior

Template generated when put in validator or deployment attempted returns circular dependencies error for EFS filesystem, mount points, access point, Datasync task, Task role policy.

Reproduction Steps

const taskRole = iam.Role.fromRoleArn(this, 'TaskRoleMaster', Fn.importValue(`${props.environmentName}-${props.projectName}-ECSTaskRole`))

    const efsSecurityGroup = new ec2.SecurityGroup(this, 'EfsSecurityGroup', {
      vpc: vpcEnvironment.vpc,
    })
    efsSecurityGroup.connections.allowFrom(efsSecurityGroup, ec2.Port.tcp(2049))

    const efsFileSystem = new efs.FileSystem(this, 'EfsFileSystem', {
      vpc: vpcEnvironment.vpc,
      vpcSubnets: vpcEnvironment.vpc.selectSubnets({ subnetGroupName: 'Application' }),
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      encrypted: true,
      securityGroup: efsSecurityGroup,
      enableAutomaticBackups: true,
    })
    efsFileSystem.applyRemovalPolicy(RemovalPolicy.DESTROY)

    const efsAccessPoint = efsFileSystem.addAccessPoint('AP', {
      createAcl: {
        ownerGid: '33',
        ownerUid: '33', 
        permissions: '0770',
      },
      posixUser: {
        uid: '33', 
        gid: '33',
      },
      path: 'sites/default/files',
    })

    const efsPolicy = new iam.Policy(this, 'EFS', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            'elasticfilesystem:ClientRootAccess',
            'elasticfilesystem:ClientWrite',
            'elasticfilesystem:ClientMount',
            'elasticfilesystem:DescribeMountTargets',
          ],
          resources: [`arn:aws:elasticfilesystem:${Aws.REGION}:${Aws.ACCOUNT_ID}:file-system/${efsFileSystem.fileSystemId}`],
        }),
      ],
    })
    taskRole.attachInlinePolicy(efsPolicy)
    efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientWrite')
    efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientRootAccess')

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.102.0

Framework Version

No response

Node.js Version

18.14.2

OS

Ubuntu

Language

TypeScript

Language Version

No response

Other information

No response

DanielLund commented 10 months ago

Further note: adding an inline file system policy for the task role removes the circular dependencies. New error while trying to deploy the file system: Invalid Request

pahud commented 10 months ago

This works for me

export class Demo extends DemoStack {
    constructor(scope: Construct, id: string, props: StackProps) {
        super(scope, id, props);

        const vpc = getVpc(this);

        const dummyRole = new iam.Role(this, 'DummyRole', {
            assumedBy: new iam.ServicePrincipal('elasticfilesystem.amazonaws.com'),
        });

        const taskRole = iam.Role.fromRoleArn(this, 'TaskRoleMaster', dummyRole.roleArn);

        const efsSecurityGroup = new ec2.SecurityGroup(this, 'EfsSecurityGroup', {
          vpc,
        })
        efsSecurityGroup.connections.allowFrom(efsSecurityGroup, ec2.Port.tcp(2049))

        const efsFileSystem = new efs.FileSystem(this, 'EfsFileSystem', {
          vpc,
          vpcSubnets: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_EGRESS }),
          performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
          encrypted: true,
          securityGroup: efsSecurityGroup,
          enableAutomaticBackups: true,
        })
        efsFileSystem.applyRemovalPolicy(RemovalPolicy.DESTROY)

        efsFileSystem.addAccessPoint('AP', {
          createAcl: {
            ownerGid: '33',
            ownerUid: '33', 
            permissions: '0770',
          },
          posixUser: {
            uid: '33', 
            gid: '33',
          },
        //   path: 'sites/default/files',
        })

        const efsPolicy = new iam.Policy(this, 'EFS', {
          statements: [
            new iam.PolicyStatement({
              actions: [
                'elasticfilesystem:ClientRootAccess',
                'elasticfilesystem:ClientWrite',
                'elasticfilesystem:ClientMount',
                'elasticfilesystem:DescribeMountTargets',
              ],
              resources: [`arn:aws:elasticfilesystem:${Aws.REGION}:${Aws.ACCOUNT_ID}:file-system/${efsFileSystem.fileSystemId}`],
            }),
          ],
        })
        taskRole.attachInlinePolicy(efsPolicy)
        // efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientWrite')
        // efsFileSystem.grant(taskRole, 'elasticfilesystem:ClientRootAccess')
    }
}
  1. I think the last two grant statements might not be required since you already have a relevant inline policy.
  2. when you specify path other than /, the path has to be already exist in the filesystem.
DanielLund commented 10 months ago

Hi @pahud thanks for the response, I can confirm removing the grant's has fixed it. If you're going to remove the path then you might as well remove the access point as the whole point of me using it was to create the path on stack creation. If a root directory path for an access point doesn't exist on the file system, Amazon EFS automatically creates that root directory with the ownership and permissions specified. Link

That aside, if someone was to create a file policy to enforce access via an imported task role then that problem will occur as although the inline policy is there that's for the task role to be able to perform efs actions. The grant calls were to enforce restricted access for only that task role (luckily it's not needed for us so we can just remove them, but just removing them doesn't really solve the actual problem)