From b840784505232aa2399ab94a960e60f5d6d0faa1 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 4 Nov 2019 21:50:09 +0100 Subject: [PATCH] feat(custom-resources): implement IGrantable for AwsCustomResource (#4790) * feat(custom-resources): implement IGrantable for AwsCustomResource Closes #4710 * remove IPrincipal * doc --- packages/@aws-cdk/custom-resources/README.md | 7 ++- .../lib/aws-custom-resource.ts | 22 +++++++--- .../test/test.aws-custom-resource.ts | 43 +++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index 3cf89ddccdf7f..702d9f12cca61 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -78,7 +78,12 @@ getParameter.getData('Parameter.Value') IAM policy statements required to make the API calls are derived from the calls and allow by default the actions to be made on all resources (`*`). You can restrict the permissions by specifying your own list of statements with the -`policyStatements` prop. +`policyStatements` prop. The custom resource also implements `iam.IGrantable`, +making it possible to use the `grantXxx()` methods. + +As this custom resource uses a singleton Lambda function, it's important to note +that the function's role will eventually accumulate the permissions/grants from all +resources. Chained API calls can be achieved by creating dependencies: ```ts diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts index b76ff5c5bbf41..d70077059d3da 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts @@ -89,7 +89,7 @@ export interface AwsSdkCall { * * Example for ECS / updateService: 'service.deploymentConfiguration.maximumPercent' * - * @default return all data + * @default - return all data */ readonly outputPath?: string; } @@ -99,21 +99,21 @@ export interface AwsCustomResourceProps { * The AWS SDK call to make when the resource is created. * At least onCreate, onUpdate or onDelete must be specified. * - * @default the call when the resource is updated + * @default - the call when the resource is updated */ readonly onCreate?: AwsSdkCall; /** * The AWS SDK call to make when the resource is updated * - * @default no call + * @default - no call */ readonly onUpdate?: AwsSdkCall; /** * The AWS SDK call to make when the resource is deleted * - * @default no call + * @default - no call */ readonly onDelete?: AwsSdkCall; @@ -121,7 +121,14 @@ export interface AwsCustomResourceProps { * The IAM policy statements to allow the different calls. Use only if * resource restriction is needed. * - * @default extract the permissions from the calls + * The custom resource also implements `iam.IGrantable`, making it possible + * to use the `grantXxx()` methods. + * + * As this custom resource uses a singleton Lambda function, it's important + * to note the that function's role will eventually accumulate the + * permissions/grants from all resources. + * + * @default - extract the permissions from the calls */ readonly policyStatements?: iam.PolicyStatement[]; @@ -133,7 +140,9 @@ export interface AwsCustomResourceProps { readonly timeout?: cdk.Duration } -export class AwsCustomResource extends cdk.Construct { +export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { + public readonly grantPrincipal: iam.IPrincipal; + private readonly customResource: CustomResource; constructor(scope: cdk.Construct, id: string, props: AwsCustomResourceProps) { @@ -157,6 +166,7 @@ export class AwsCustomResource extends cdk.Construct { lambdaPurpose: 'AWS', timeout: props.timeout || cdk.Duration.seconds(30), }); + this.grantPrincipal = provider.grantPrincipal; if (props.policyStatements) { for (const statement of props.policyStatements) { diff --git a/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource.ts index 1e6189d558724..1fadaebc18738 100644 --- a/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource.ts @@ -264,6 +264,49 @@ export = { Timeout: 900 })); + test.done(); + }, + + 'implements IGrantable'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') + }); + const customResource = new AwsCustomResource(stack, 'AwsSdk', { + onCreate: { + service: 'service', + action: 'action', + physicalResourceId: 'id' + } + }); + + // WHEN + role.grantPassRole(customResource.grantPrincipal); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'service:Action', + Effect: 'Allow', + Resource: '*' + }, + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn' + ] + } + } + ], + Version: '2012-10-17' + } + })); + test.done(); } };