Skip to content

Commit 5771d79

Browse files
authored
feat(efs): implement IResourceWithPolicy (#24453)
Implement `IResourceWithPolicy` in `FileSystem`. Currently, `FileSystem` implements `FileSystemPolicy`. However, it does not implement `iam.IResourceWithPolicy`, which means that `iam.Grant.addToPrincipalOrResource` and `iam.Grant.addToPrincipalAndResource` cannot be used and is not compatible with other resources with resource policies. This PR makes it easier to extend `grantXxx`. Closes #15805. (Already implemented `FileSystemPolicy`) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent d8a80b4 commit 5771d79

File tree

8 files changed

+230
-13
lines changed

8 files changed

+230
-13
lines changed

packages/@aws-cdk/aws-efs/README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ const importedFileSystem = efs.FileSystem.fromFileSystemAttributes(this, 'existi
6666
You can use both IAM identity policies and resource policies to control client access to Amazon EFS resources in a way that is scalable and optimized for cloud environments. Using IAM, you can permit clients to perform specific actions on a file system, including read-only, write, and root access.
6767

6868
```ts
69-
const myFileSystemPolicy = new PolicyDocument({
70-
statements: [new PolicyStatement({
69+
import * as iam from '@aws-cdk/aws-iam';
70+
71+
const myFileSystemPolicy = new iam.PolicyDocument({
72+
statements: [new iam.PolicyStatement({
7173
actions: [
7274
'elasticfilesystem:ClientWrite',
7375
'elasticfilesystem:ClientMount',
7476
],
75-
principals: [new AccountRootPrincipal()],
77+
principals: [new iam.AccountRootPrincipal()],
7678
resources: ['*'],
7779
conditions: {
7880
Bool: {
@@ -88,6 +90,19 @@ const fileSystem = new efs.FileSystem(this, 'MyEfsFileSystem', {
8890
});
8991
```
9092

93+
Alternatively, a resource policy can be added later using `addToResourcePolicy(statement)`. Note that this will not work with imported FileSystem.
94+
95+
```ts
96+
import * as iam from '@aws-cdk/aws-iam';
97+
98+
declare const statement: iam.PolicyStatement;
99+
const fileSystem = new efs.FileSystem(this, 'MyEfsFileSystem', {
100+
vpc: new ec2.Vpc(this, 'VPC'),
101+
});
102+
103+
fileSystem.addToResourcePolicy(statement);
104+
```
105+
91106
### Permissions
92107

93108
If you need to grant file system permissions to another resource, you can use the `.grant()` API.

packages/@aws-cdk/aws-efs/lib/efs-file-system.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ec2 from '@aws-cdk/aws-ec2';
22
import * as iam from '@aws-cdk/aws-iam';
33
import * as kms from '@aws-cdk/aws-kms';
4-
import { ArnFormat, FeatureFlags, IResource, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core';
4+
import { ArnFormat, FeatureFlags, Lazy, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core';
55
import * as cxapi from '@aws-cdk/cx-api';
66
import { Construct, DependencyGroup, IDependable } from 'constructs';
77
import { AccessPoint, AccessPointOptions } from './access-point';
@@ -106,7 +106,7 @@ export enum ThroughputMode {
106106
/**
107107
* Represents an Amazon EFS file system
108108
*/
109-
export interface IFileSystem extends ec2.IConnectable, IResource {
109+
export interface IFileSystem extends ec2.IConnectable, iam.IResourceWithPolicy {
110110
/**
111111
* The ID of the file system, assigned by Amazon EFS.
112112
*
@@ -284,6 +284,15 @@ abstract class FileSystemBase extends Resource implements IFileSystem {
284284
*/
285285
public abstract readonly mountTargetsAvailable: IDependable;
286286

287+
/**
288+
* @internal
289+
*/
290+
protected _resource?: CfnFileSystem;
291+
/**
292+
* @internal
293+
*/
294+
protected _fileSystemPolicy?: iam.PolicyDocument;
295+
287296
/**
288297
* Grant the actions defined in actions to the given grantee
289298
* on this File System resource.
@@ -298,6 +307,28 @@ abstract class FileSystemBase extends Resource implements IFileSystem {
298307
resourceArns: [this.fileSystemArn],
299308
});
300309
}
310+
311+
/**
312+
* Adds a statement to the resource policy associated with this file system.
313+
* A resource policy will be automatically created upon the first call to `addToResourcePolicy`.
314+
*
315+
* Note that this does not work with imported file systems.
316+
*
317+
* @param statement The policy statement to add
318+
*/
319+
public addToResourcePolicy(
320+
statement: iam.PolicyStatement,
321+
): iam.AddToResourcePolicyResult {
322+
if (!this._resource) {
323+
return { statementAdded: false };
324+
}
325+
this._fileSystemPolicy = this._fileSystemPolicy ?? new iam.PolicyDocument({ statements: [] });
326+
this._fileSystemPolicy.addStatements(statement);
327+
return {
328+
statementAdded: true,
329+
policyDependable: this,
330+
};
331+
}
301332
}
302333

303334
/**
@@ -370,20 +401,21 @@ export class FileSystem extends FileSystemBase {
370401
lifecyclePolicies.push({ transitionToPrimaryStorageClass: props.outOfInfrequentAccessPolicy });
371402
}
372403

373-
const filesystem = new CfnFileSystem(this, 'Resource', {
404+
this._resource = new CfnFileSystem(this, 'Resource', {
374405
encrypted: encrypted,
375406
kmsKeyId: props.kmsKey?.keyArn,
376407
lifecyclePolicies: lifecyclePolicies.length > 0 ? lifecyclePolicies : undefined,
377408
performanceMode: props.performanceMode,
378409
throughputMode: props.throughputMode,
379410
provisionedThroughputInMibps: props.provisionedThroughputPerSecond?.toMebibytes(),
380411
backupPolicy: props.enableAutomaticBackups ? { status: 'ENABLED' } : undefined,
381-
fileSystemPolicy: props.fileSystemPolicy,
412+
fileSystemPolicy: Lazy.any({ produce: () => this._fileSystemPolicy }),
382413
});
383-
filesystem.applyRemovalPolicy(props.removalPolicy);
414+
this._resource.applyRemovalPolicy(props.removalPolicy);
384415

385-
this.fileSystemId = filesystem.ref;
386-
this.fileSystemArn = filesystem.attrArn;
416+
this.fileSystemId = this._resource.ref;
417+
this.fileSystemArn = this._resource.attrArn;
418+
this._fileSystemPolicy = props.fileSystemPolicy;
387419

388420
Tags.of(this).add('Name', props.fileSystemName || this.node.path);
389421

packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,4 +457,106 @@ test('can specify file system policy', () => {
457457
],
458458
},
459459
});
460+
});
461+
462+
test('can add statements to file system policy', () => {
463+
// WHEN
464+
const statement1 = new iam.PolicyStatement({
465+
actions: [
466+
'elasticfilesystem:ClientMount',
467+
],
468+
principals: [new iam.ArnPrincipal('arn:aws:iam::111122223333:role/Testing_Role1')],
469+
resources: ['arn:aws:elasticfilesystem:us-east-2:111122223333:file-system/fs-1234abcd'],
470+
conditions: {
471+
Bool: {
472+
'elasticfilesystem:AccessedViaMountTarget': 'true',
473+
},
474+
},
475+
});
476+
const statement2 = new iam.PolicyStatement({
477+
actions: [
478+
'elasticfilesystem:ClientMount',
479+
'elasticfilesystem:ClientWrite',
480+
],
481+
principals: [new iam.ArnPrincipal('arn:aws:iam::111122223333:role/Testing_Role2')],
482+
resources: ['arn:aws:elasticfilesystem:us-east-2:111122223333:file-system/fs-1234abcd'],
483+
conditions: {
484+
Bool: {
485+
'elasticfilesystem:AccessedViaMountTarget': 'true',
486+
},
487+
},
488+
});
489+
const fileSystem = new FileSystem(stack, 'EfsFileSystem', { vpc });
490+
fileSystem.addToResourcePolicy(statement1);
491+
fileSystem.addToResourcePolicy(statement2);
492+
493+
// THEN
494+
Template.fromStack(stack).hasResourceProperties('AWS::EFS::FileSystem', {
495+
FileSystemPolicy: {
496+
Statement: [
497+
{
498+
Effect: 'Allow',
499+
Principal: {
500+
AWS: 'arn:aws:iam::111122223333:role/Testing_Role1',
501+
},
502+
Action: 'elasticfilesystem:ClientMount',
503+
Resource: 'arn:aws:elasticfilesystem:us-east-2:111122223333:file-system/fs-1234abcd',
504+
Condition: {
505+
Bool: {
506+
'elasticfilesystem:AccessedViaMountTarget': 'true',
507+
},
508+
},
509+
},
510+
{
511+
Effect: 'Allow',
512+
Principal: {
513+
AWS: 'arn:aws:iam::111122223333:role/Testing_Role2',
514+
},
515+
Action: [
516+
'elasticfilesystem:ClientMount',
517+
'elasticfilesystem:ClientWrite',
518+
],
519+
Resource: 'arn:aws:elasticfilesystem:us-east-2:111122223333:file-system/fs-1234abcd',
520+
Condition: {
521+
Bool: {
522+
'elasticfilesystem:AccessedViaMountTarget': 'true',
523+
},
524+
},
525+
},
526+
],
527+
},
528+
});
529+
});
530+
531+
test('imported file system can not add statements to file system policy', () => {
532+
// WHEN
533+
const statement = new iam.PolicyStatement({
534+
actions: [
535+
'elasticfilesystem:ClientMount',
536+
],
537+
principals: [new iam.ArnPrincipal('arn:aws:iam::111122223333:role/Testing_Role')],
538+
resources: ['arn:aws:elasticfilesystem:us-east-2:111122223333:file-system/fs-1234abcd'],
539+
conditions: {
540+
Bool: {
541+
'elasticfilesystem:AccessedViaMountTarget': 'true',
542+
},
543+
},
544+
});
545+
546+
const fileSystem = new FileSystem(stack, 'FileSystem', { vpc });
547+
const importedFileSystem = FileSystem.fromFileSystemAttributes(stack, 'ImportedFileSystem', {
548+
securityGroup: fileSystem.connections.securityGroups[0],
549+
fileSystemArn: fileSystem.fileSystemArn,
550+
});
551+
const fileSystemResult = fileSystem.addToResourcePolicy(statement);
552+
const importedFileSystemResult = importedFileSystem.addToResourcePolicy(statement);
553+
554+
// THEN
555+
expect(fileSystemResult).toStrictEqual({
556+
statementAdded: true,
557+
policyDependable: fileSystem,
558+
});
559+
expect(importedFileSystemResult).toStrictEqual({
560+
statementAdded: false,
561+
});
460562
});

packages/@aws-cdk/aws-efs/test/integ.efs-filesystem-policy.js.snapshot/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7d5832d3a930b18cb36abc1ffaed24a2bf408a8130ba7fab108ff6f6fa75dd98.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/80b43aa6ff9bb3df4306a753de53b67b3d2eca66f7851894c1186c46edea991d.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [

packages/@aws-cdk/aws-efs/test/integ.efs-filesystem-policy.js.snapshot/test-efs-integ.assets.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"version": "30.1.0",
33
"files": {
4-
"7d5832d3a930b18cb36abc1ffaed24a2bf408a8130ba7fab108ff6f6fa75dd98": {
4+
"80b43aa6ff9bb3df4306a753de53b67b3d2eca66f7851894c1186c46edea991d": {
55
"source": {
66
"path": "test-efs-integ.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "7d5832d3a930b18cb36abc1ffaed24a2bf408a8130ba7fab108ff6f6fa75dd98.json",
12+
"objectKey": "80b43aa6ff9bb3df4306a753de53b67b3d2eca66f7851894c1186c46edea991d.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk/aws-efs/test/integ.efs-filesystem-policy.js.snapshot/test-efs-integ.template.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,34 @@
391391
}
392392
},
393393
"Resource": "*"
394+
},
395+
{
396+
"Action": "elasticfilesystem:ClientRootAccess",
397+
"Condition": {
398+
"Bool": {
399+
"elasticfilesystem:AccessedViaMountTarget": "true"
400+
}
401+
},
402+
"Effect": "Allow",
403+
"Principal": {
404+
"AWS": {
405+
"Fn::Join": [
406+
"",
407+
[
408+
"arn:",
409+
{
410+
"Ref": "AWS::Partition"
411+
},
412+
":iam::",
413+
{
414+
"Ref": "AWS::AccountId"
415+
},
416+
":root"
417+
]
418+
]
419+
}
420+
},
421+
"Resource": "*"
394422
}
395423
],
396424
"Version": "2012-10-17"

packages/@aws-cdk/aws-efs/test/integ.efs-filesystem-policy.js.snapshot/tree.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,34 @@
646646
}
647647
},
648648
"Resource": "*"
649+
},
650+
{
651+
"Action": "elasticfilesystem:ClientRootAccess",
652+
"Condition": {
653+
"Bool": {
654+
"elasticfilesystem:AccessedViaMountTarget": "true"
655+
}
656+
},
657+
"Effect": "Allow",
658+
"Principal": {
659+
"AWS": {
660+
"Fn::Join": [
661+
"",
662+
[
663+
"arn:",
664+
{
665+
"Ref": "AWS::Partition"
666+
},
667+
":iam::",
668+
{
669+
"Ref": "AWS::AccountId"
670+
},
671+
":root"
672+
]
673+
]
674+
}
675+
},
676+
"Resource": "*"
649677
}
650678
],
651679
"Version": "2012-10-17"

packages/@aws-cdk/aws-efs/test/integ.efs-filesystem-policy.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ const fileSystem = new FileSystem(stack, 'FileSystem', {
3030
vpc,
3131
fileSystemPolicy: myFileSystemPolicy,
3232
});
33+
fileSystem.addToResourcePolicy(new PolicyStatement({
34+
actions: [
35+
'elasticfilesystem:ClientRootAccess',
36+
],
37+
principals: [new AccountRootPrincipal()],
38+
resources: ['*'],
39+
conditions: {
40+
Bool: {
41+
'elasticfilesystem:AccessedViaMountTarget': 'true',
42+
},
43+
},
44+
}));
3345

3446
const accessPoint = new AccessPoint(stack, 'AccessPoint', {
3547
fileSystem,

0 commit comments

Comments
 (0)