diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 3d912ec561254..fb044f4eb72bf 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -368,9 +368,12 @@ export class Rule extends Resource implements IRule { // Leaving it in for backwards compatibility. stackName: `${targetStack.stackName}-EventBusPolicy-support-${targetRegion}-${sourceAccount}`, }); + const statementPrefix = `Allow-account-${sourceAccount}-`; new CfnEventBusPolicy(eventBusPolicyStack, 'GivePermToOtherAccount', { action: 'events:PutEvents', - statementId: `Allow-account-${sourceAccount}-${this.node.addr}`, + statementId: statementPrefix + Names.uniqueResourceName(this, { + maxLength: 64 - statementPrefix.length, + }), principal: sourceAccount, }); } diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json new file mode 100644 index 0000000000000..28858ec3614c2 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72": { + "source": { + "path": "asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494": { + "source": { + "path": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json new file mode 100644 index 0000000000000..acbf2ffcabff2 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json @@ -0,0 +1,156 @@ +{ + "Resources": { + "AwsApiCallEventBridgedescribeEventBus": { + "Type": "Custom::DeployAssert@SdkCallEventBridgedescribeEventBus", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "EventBridge", + "api": "describeEventBus", + "flattenResponse": "true", + "salt": "1666817700798" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036": { + "Type": "Custom::DeployAssert@AssertEquals", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "actual": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeEventBus", + "apiCallResponse.Policy" + ] + }, + "expected": "{\"$ObjectLike\":{\"Statement\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"Sid\":{\"$StringLike\":\"Allow-account-987654321\"},\"Principal\":{\"AWS\":\"arn:aws:iam::987654321:root\"},\"Resource\":{\"$StringLike\":\"arn:aws:events:us-east-1:12345678\"}}}]}}}", + "salt": "1666817700798" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "eventbridge:DescribeEventBus" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "events:DescribeEventBus" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAssertEqualsEventBridgedescribeEventBusfd3cf7d971587606ecf8442a4cb30f1b": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036", + "data" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json new file mode 100644 index 0000000000000..37430d4ba4941 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4": { + "source": { + "path": "EventBusPolicy-987654321-test-region-12345678.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json new file mode 100644 index 0000000000000..40569324086e3 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json @@ -0,0 +1,46 @@ +{ + "Resources": { + "GivePermToOtherAccount": { + "Type": "AWS::Events::EventBusPolicy", + "Properties": { + "StatementId": "Allow-account-987654321-FromCrossAccountRuleStackMyRule68A189ED", + "Action": "events:PutEvents", + "Principal": "987654321" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json new file mode 100644 index 0000000000000..90bb64e7eb6b5 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70": { + "source": { + "path": "FromCrossAccountRuleStack.template.json", + "packaging": "file" + }, + "destinations": { + "987654321-test-region": { + "bucketName": "cdk-hnb659fds-assets-987654321-test-region", + "objectKey": "c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-file-publishing-role-987654321-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json new file mode 100644 index 0000000000000..5d2cea910dadf --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json @@ -0,0 +1,74 @@ +{ + "Resources": { + "MyRuleA44AB831": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:test-region:12345678:event-bus/default" + ] + ] + }, + "Id": "SQS" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json new file mode 100644 index 0000000000000..37baa48413aff --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c": { + "source": { + "path": "ToCrossAccountRuleStack.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json new file mode 100644 index 0000000000000..4c3f53989a1f1 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json @@ -0,0 +1,81 @@ +{ + "Resources": { + "Queue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "IntegTestCrossEnvRule", + "ReceiveMessageWaitTimeSeconds": 20 + } + }, + "FromCrossAccountRuleStackMyRule68A189EDSQS1A422535": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sqs:test-region:12345678:IntegTestCrossEnvRule" + ] + ] + }, + "Id": "SQS" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js new file mode 100644 index 0000000000000..09ec17c1ae178 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js @@ -0,0 +1,611 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`); + const response = await this.processEvent(this.event.ResourceProperties); + console.log(`Event output : ${JSON.stringify(response)}`); + await this.respond({ + status: "SUCCESS", + reason: "OK", + data: response + }); + } catch (e) { + console.log(e); + await this.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + data: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.data); + } + } else { + result = { + data: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS.VERSION}`); + const service = new AWS[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + return request2.flattenResponse === "true" ? flatData : respond; + } +}; + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + const provider = createResourceHandler(event, context); + await provider.handle(); +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } + switch (event.ResourceType) { + case ASSERT_RESOURCE_TYPE: + return new AssertionHandler(event, context); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json new file mode 100644 index 0000000000000..5ac40c3def2c4 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "CrossAccountDeploy/DefaultTest": { + "stacks": [ + "ToCrossAccountRuleStack" + ], + "assertionStack": "CrossAccountDeploy/DefaultTest/DeployAssert", + "assertionStackName": "CrossAccountDeployDefaultTestDeployAssertB5328BEF" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..2d333b795f71e --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json @@ -0,0 +1,257 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "FromCrossAccountRuleStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "FromCrossAccountRuleStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "FromCrossAccountRuleStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://987654321/test-region", + "properties": { + "templateFile": "FromCrossAccountRuleStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-deploy-role-987654321-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-cfn-exec-role-987654321-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-987654321-test-region/c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "FromCrossAccountRuleStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-lookup-role-987654321-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "EventBusPolicy-987654321-test-region-12345678", + "FromCrossAccountRuleStack.assets" + ], + "metadata": { + "/FromCrossAccountRuleStack/MyRule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyRuleA44AB831" + } + ], + "/FromCrossAccountRuleStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/FromCrossAccountRuleStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "FromCrossAccountRuleStack" + }, + "ToCrossAccountRuleStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "ToCrossAccountRuleStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "ToCrossAccountRuleStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/test-region", + "properties": { + "templateFile": "ToCrossAccountRuleStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-test-region/0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "ToCrossAccountRuleStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "FromCrossAccountRuleStack", + "ToCrossAccountRuleStack.assets" + ], + "metadata": { + "/ToCrossAccountRuleStack/Queue": [ + { + "type": "aws:cdk:logicalId", + "data": "Queue" + } + ], + "/ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FromCrossAccountRuleStackMyRule68A189EDSQS1A422535" + } + ], + "/ToCrossAccountRuleStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/ToCrossAccountRuleStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "ToCrossAccountRuleStack" + }, + "EventBusPolicy-987654321-test-region-12345678.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "EventBusPolicy-987654321-test-region-12345678.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "EventBusPolicy-987654321-test-region-12345678": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/test-region", + "properties": { + "templateFile": "EventBusPolicy-987654321-test-region-12345678.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-test-region/462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "EventBusPolicy-987654321-test-region-12345678.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + }, + "stackName": "ToCrossAccountRuleStack-EventBusPolicy-support-test-region-987654321" + }, + "dependencies": [ + "EventBusPolicy-987654321-test-region-12345678.assets" + ], + "metadata": { + "/EventBusPolicy-987654321-test-region-12345678/GivePermToOtherAccount": [ + { + "type": "aws:cdk:logicalId", + "data": "GivePermToOtherAccount" + } + ], + "/EventBusPolicy-987654321-test-region-12345678/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/EventBusPolicy-987654321-test-region-12345678/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "EventBusPolicy-987654321-test-region-12345678" + }, + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "CrossAccountDeployDefaultTestDeployAssertB5328BEF": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "ToCrossAccountRuleStack", + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets" + ], + "metadata": { + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeEventBus" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAssertEqualsEventBridgedescribeEventBusfd3cf7d971587606ecf8442a4cb30f1b" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "CrossAccountDeploy/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json new file mode 100644 index 0000000000000..d535a83fdf760 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json @@ -0,0 +1,355 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "FromCrossAccountRuleStack": { + "id": "FromCrossAccountRuleStack", + "path": "FromCrossAccountRuleStack", + "children": { + "MyRule": { + "id": "MyRule", + "path": "FromCrossAccountRuleStack/MyRule", + "children": { + "Resource": { + "id": "Resource", + "path": "FromCrossAccountRuleStack/MyRule/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Rule", + "aws:cdk:cloudformation:props": { + "eventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "state": "ENABLED", + "targets": [ + { + "id": "SQS", + "arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:test-region:12345678:event-bus/default" + ] + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Rule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "ToCrossAccountRuleStack": { + "id": "ToCrossAccountRuleStack", + "path": "ToCrossAccountRuleStack", + "children": { + "Queue": { + "id": "Queue", + "path": "ToCrossAccountRuleStack/Queue", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "FromCrossAccountRuleStackMyRule68A189ED-SQS": { + "id": "FromCrossAccountRuleStackMyRule68A189ED-SQS", + "path": "ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS", + "children": { + "Resource": { + "id": "Resource", + "path": "ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Rule", + "aws:cdk:cloudformation:props": { + "eventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "state": "ENABLED", + "targets": [ + { + "id": "SQS", + "arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sqs:test-region:12345678:IntegTestCrossEnvRule" + ] + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Rule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "EventBusPolicy-987654321-test-region-12345678": { + "id": "EventBusPolicy-987654321-test-region-12345678", + "path": "EventBusPolicy-987654321-test-region-12345678", + "children": { + "GivePermToOtherAccount": { + "id": "GivePermToOtherAccount", + "path": "EventBusPolicy-987654321-test-region-12345678/GivePermToOtherAccount", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::EventBusPolicy", + "aws:cdk:cloudformation:props": { + "statementId": "Allow-account-987654321-FromCrossAccountRuleStackMyRule68A189ED", + "action": "events:PutEvents", + "principal": "987654321" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnEventBusPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "CrossAccountDeploy": { + "id": "CrossAccountDeploy", + "path": "CrossAccountDeploy", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "CrossAccountDeploy/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert", + "children": { + "AwsApiCallEventBridgedescribeEventBus": { + "id": "AwsApiCallEventBridgedescribeEventBus", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertEqualsEventBridgedescribeEventBus": { + "id": "AssertEqualsEventBridgedescribeEventBus", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus", + "children": { + "AssertionProvider": { + "id": "AssertionProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.EqualsAssertion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts b/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts new file mode 100644 index 0000000000000..c77813f90ed56 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts @@ -0,0 +1,99 @@ +/// !cdk-integ * +import { App, Arn, CfnResource, Stack } from '@aws-cdk/core'; +import { ExpectedResult, IntegTest, Match } from '@aws-cdk/integ-tests'; +import { Rule, IRuleTarget } from '../lib'; + +/** + * Basic idea for this test is to create an EventBridge that "connects" + * an SQS queue in one account to another account. Nothing is sent on the + * queue, it's just used to set up the condition where aws-events creates + * a support stack. + */ + +const app = new App(); + +const account = process.env.CDK_INTEG_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT; + +// As the integ-runner doesnt provide a default cross account, we make our own. +const crossAccount = process.env.CDK_INTEG_CROSS_ACCOUNT || '987654321'; +const region = process.env.CDK_INTEG_REGION || process.env.CDK_DEFAULT_REGION; + +const fromCrossAccountStack = new Stack(app, 'FromCrossAccountRuleStack', { + env: { + account: crossAccount, + region, + }, +}); + +/** + * To make this testable, we need to have the stack that stores the event bridge be in + * the same account that the IntegTest stack is deployed into. Otherwise, we have no + * access to the IAM policy that the EventBusPolicy-account-region support stack creates. + */ +const toCrossAccountStack = new Stack(app, 'ToCrossAccountRuleStack', { + env: { + account, + region, + }, +}); +const queueName = 'IntegTestCrossEnvRule'; + +const queue = new CfnResource(toCrossAccountStack, 'Queue', { + type: 'AWS::SQS::Queue', + properties: { + QueueName: queueName, + ReceiveMessageWaitTimeSeconds: 20, + }, +}); + +const target: IRuleTarget = { + bind: () => ({ + id: 'SQS', + arn: Arn.format({ + resource: queueName, + service: 'sqs', + }, toCrossAccountStack), + targetResource: queue, + }), +}; + +new Rule(fromCrossAccountStack, 'MyRule', { + eventPattern: { + detail: { + foo: ['bar'], + }, + detailType: ['cdk-integ-custom-rule'], + source: ['cdk-integ'], + }, + targets: [target], +}); + +toCrossAccountStack.addDependency(fromCrossAccountStack); + +const integ = new IntegTest(app, 'CrossAccountDeploy', { + testCases: [ + toCrossAccountStack, + ], +}); + +// We are using the default event bus, don't need to define any parameters for this call. +const eventVerification = integ.assertions.awsApiCall('EventBridge', 'describeEventBus'); + +integ.node.addDependency(toCrossAccountStack); + +eventVerification.provider.addPolicyStatementFromSdkCall('events', 'DescribeEventBus'); + +// IAM policy will be created by the support stack, assert that everything created as expected. +eventVerification.assertAtPath('Policy', ExpectedResult.objectLike({ + Statement: Match.arrayWith( + [Match.objectLike({ + Sid: Match.stringLikeRegexp(`Allow-account-${crossAccount}`), + Principal: { + AWS: `arn:aws:iam::${crossAccount}:root`, + }, + Resource: Match.stringLikeRegexp(`arn:aws:events:us-east-1:${account}`), + })], + ), +})); + +app.synth(); diff --git a/packages/@aws-cdk/aws-events/test/rule.test.ts b/packages/@aws-cdk/aws-events/test/rule.test.ts index a822f2d7c256b..b000e914d8312 100644 --- a/packages/@aws-cdk/aws-events/test/rule.test.ts +++ b/packages/@aws-cdk/aws-events/test/rule.test.ts @@ -933,7 +933,7 @@ describe('rule', () => { const app = new cdk.App(); const sourceAccount = '123456789012'; - const nodeAddr = 'c810f4680339b01edf1f157c4fd07da73469742773'; + const uniqueName = 'SourceStackRuleD6962A13'; const sourceStack = new cdk.Stack(app, 'SourceStack', { env: { account: sourceAccount, @@ -1015,7 +1015,7 @@ describe('rule', () => { const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; Template.fromStack(eventBusPolicyStack).hasResourceProperties('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', - 'StatementId': `Allow-account-${sourceAccount}-${nodeAddr}`, + 'StatementId': `Allow-account-${sourceAccount}-${uniqueName}`, 'Principal': sourceAccount, }); });