From c2f40b7c97b822f258f953b572ba2e7a99403f89 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Thu, 7 Jul 2022 14:53:37 -0400 Subject: [PATCH] feat(integ-tests): expose adding IAM policies to the assertion provider (#20769) Currently the `AwsApiCall` construct will try and automatically create the correct IAM policy based on the service and api call being used. The assumption was that in most cases this would work, but it turns out that in the first couple use cases we've seen this is not the case. This PR adds another method `addToRolePolicy` on the `AssertionsProvider` construct and then makes the provider a `public` attribute on `AwsApICall`. This allows you to add additional policies. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/integ-tests/README.md | 28 +++++++++++ .../lib/assertions/providers/provider.ts | 38 ++++++++++++++ .../integ-tests/lib/assertions/sdk.ts | 16 +++++- .../integ-tests/rosetta/default.ts-fixture | 1 + .../integ-tests/test/assertions/sdk.test.ts | 49 +++++++++++++++++++ 5 files changed, 131 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index 17e3419c286ca..b27610cbd531f 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -264,6 +264,34 @@ integ.assertions.awsApiCall('SQS', 'receiveMessage', { }); ``` +By default, the `AwsApiCall` construct will automatically add the correct IAM policies +to allow the Lambda function to make the API call. It does this based on the `service` +and `api` that is provided. In the above example the service is `SQS` and the api is +`receiveMessage` so it will create a policy with `Action: 'sqs:ReceiveMessage`. + +There are some cases where the permissions do not exactly match the service/api call, for +example the S3 `listObjectsV2` api. In these cases it is possible to add the correct policy +by accessing the `provider` object. + +```ts +declare const app: App; +declare const stack: Stack; +declare const integ: IntegTest; + +const apiCall = integ.assertions.awsApiCall('S3', 'listObjectsV2', { + Bucket: 'mybucket', +}); + +apiCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['s3:GetObject', 's3:ListBucket'], + Resource: ['*'], +}); +``` + +Note that addToRolePolicy() uses direct IAM JSON policy blobs, not a iam.PolicyStatement +object like you will see in the rest of the CDK. + ### EqualsAssertion This library currently provides the ability to assert that two values are equal diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts index 1b4c64e963646..5a5e3378fa4fa 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts @@ -134,6 +134,24 @@ class SingletonFunction extends Construct { return new LambdaFunctionProvider(Stack.of(this), constructName); } + /** + * Add an IAM policy statement to the inline policy of the + * lambdas function's role + * + * **Please note**: this is a direct IAM JSON policy blob, *not* a `iam.PolicyStatement` + * object like you will see in the rest of the CDK. + * + * + * singleton.addToRolePolicy({ + * Effect: 'Allow', + * Action: 's3:GetObject', + * Resources: '*', + * }); + */ + public addToRolePolicy(statement: any): void { + this.policies.push(statement); + } + /** * Create a policy statement from a specific api call */ @@ -216,6 +234,26 @@ export class AssertionsProvider extends Construct { public addPolicyStatementFromSdkCall(service: string, api: string, resources?: string[]): void { this.handler.addPolicyStatementFromSdkCall(service, api, resources); } + + /** + * Add an IAM policy statement to the inline policy of the + * lambdas function's role + * + * **Please note**: this is a direct IAM JSON policy blob, *not* a `iam.PolicyStatement` + * object like you will see in the rest of the CDK. + * + * + * @example + * declare const provider: AssertionsProvider; + * provider.addToRolePolicy({ + * Effect: 'Allow', + * Action: 's3:GetObject', + * Resources: '*', + * }); + */ + public addToRolePolicy(statement: any): void { + this.handler.addToRolePolicy(statement); + } } function slugify(x: string): string { diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts index a7322738fa8d9..97972e4c568ba 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts @@ -9,6 +9,20 @@ import { AssertionsProvider, SDK_RESOURCE_TYPE_PREFIX } from './providers'; * an API call using the AWS SDK */ export interface IAwsApiCall extends IConstruct { + /** + * access the AssertionsProvider. This can be used to add additional IAM policies + * the the provider role policy + * + * @example + * declare const apiCall: AwsApiCall; + * apiCall.provider.addToRolePolicy({ + * Effect: 'Allow', + * Action: ['s3:GetObject'], + * Resource: ['*'], + * }); + */ + readonly provider: AssertionsProvider; + /** * Returns the value of an attribute of the custom resource of an arbitrary * type. Attributes are returned from the custom resource provider through the @@ -110,7 +124,7 @@ export class AwsApiCall extends Construct implements IAwsApiCall { private flattenResponse: string = 'false'; private readonly name: string; - protected provider: AssertionsProvider; + public readonly provider: AssertionsProvider; constructor(scope: Construct, id: string, props: AwsApiCallProps) { super(scope, id); diff --git a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture index 847ddd48128e2..7cf368abcf6db 100644 --- a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture @@ -11,6 +11,7 @@ import { AssertionType, LambdaInvokeFunction, Match, + AssertionsProvider, } from '@aws-cdk/integ-tests'; import { Construct } from 'constructs'; import { diff --git a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts index d8d3d70ec1694..7be64290b1b01 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -44,6 +44,55 @@ describe('AwsApiCall', () => { param2: 2, }, }); + + }); + + test('add policy to provider', () => { + // GIVEN + const app = new App(); + const deplossert = new DeployAssert(app); + + // WHEN + const apiCall = deplossert.awsApiCall('MyService', 'MyApi', { + param1: 'val1', + param2: 2, + }); + apiCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['s3:GetObject'], + Resource: ['*'], + }); + + Template.fromStack(deplossert.scope).hasResourceProperties('AWS::IAM::Role', { + Policies: [ + { + PolicyName: 'Inline', + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: [ + 'myservice:MyApi', + ], + Effect: 'Allow', + Resource: [ + '*', + ], + }, + { + Action: [ + 's3:GetObject', + ], + Effect: 'Allow', + Resource: [ + '*', + ], + }, + ], + }, + }, + ], + }); }); describe('get attribute', () => {