From a1f8263673bc9ce08dbf973210d99dc70860d413 Mon Sep 17 00:00:00 2001 From: Chelsea Urquhart Date: Sun, 21 Apr 2024 07:07:07 -0700 Subject: [PATCH] fix(stepfunctions): distributed maps under branches distributed maps under branch states (i.e., Parallel) do not apply the necessary permissions to run the state. instead of only iterating states in the parent graph, iterate all children as well. if any of them are distributed maps, add the necessary policy. --- ...unctions-map-distributed-stack.assets.json | 19 + ...ctions-map-distributed-stack.template.json | 216 +++++++++ .../cdk.out | 1 + ...efaultTestDeployAssert63593303.assets.json | 19 + ...aultTestDeployAssert63593303.template.json | 36 ++ .../integ.json | 12 + .../manifest.json | 167 +++++++ .../tree.json | 429 ++++++++++++++++++ .../test/integ.map-distributed-iam.ts | 59 +++ .../aws-stepfunctions/lib/state-graph.ts | 10 +- .../test/state-graph.test.ts | 101 +++++ 11 files changed, 1068 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.ts create mode 100644 packages/aws-cdk-lib/aws-stepfunctions/test/state-graph.test.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.assets.json new file mode 100644 index 0000000000000..1f7bf86de2cc8 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "c583542f1a1a4d70c5b7930dda94ecccba23a2fcb848dad95e5812cc61d18d1f": { + "source": { + "path": "cdk-stepfunctions-map-distributed-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "c583542f1a1a4d70c5b7930dda94ecccba23a2fcb848dad95e5812cc61d18d1f.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-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.template.json new file mode 100644 index 0000000000000..6fad197e0eba2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk-stepfunctions-map-distributed-stack.template.json @@ -0,0 +1,216 @@ +{ + "Resources": { + "StateMachine1RoleDE82F282": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachine18AFC9B86": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": "{\"StartAt\":\"My-Map-State\",\"States\":{\"My-Map-State\":{\"Type\":\"Map\",\"End\":true,\"ItemsPath\":\"$.inputForMap\",\"ItemSelector\":{\"foo\":\"foo\",\"bar.$\":\"$.bar\"},\"ItemProcessor\":{\"ProcessorConfig\":{\"Mode\":\"DISTRIBUTED\",\"ExecutionType\":\"STANDARD\"},\"StartAt\":\"Pass State 1\",\"States\":{\"Pass State 1\":{\"Type\":\"Pass\",\"End\":true}}},\"MaxConcurrencyPath\":\"$.maxConcurrency\"}},\"TimeoutSeconds\":30}", + "RoleArn": { + "Fn::GetAtt": [ + "StateMachine1RoleDE82F282", + "Arn" + ] + } + }, + "DependsOn": [ + "StateMachine1RoleDE82F282" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "StateMachine1DistributedMapPolicyA6BF4F8F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "StateMachine18AFC9B86" + } + }, + { + "Action": [ + "states:DescribeExecution", + "states:StopExecution" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Ref": "StateMachine18AFC9B86" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachine1DistributedMapPolicyA6BF4F8F", + "Roles": [ + { + "Ref": "StateMachine1RoleDE82F282" + } + ] + } + }, + "StateMachine2Role6BE3CF0B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachine21CE8E3CE": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": "{\"StartAt\":\"My-Map-State\",\"States\":{\"My-Map-State\":{\"Type\":\"Map\",\"End\":true,\"ItemsPath\":\"$.inputForMap\",\"ItemSelector\":{\"foo\":\"foo\",\"bar.$\":\"$.bar\"},\"ItemProcessor\":{\"ProcessorConfig\":{\"Mode\":\"DISTRIBUTED\",\"ExecutionType\":\"STANDARD\"},\"StartAt\":\"Pass State 2\",\"States\":{\"Pass State 2\":{\"Type\":\"Pass\",\"End\":true}}},\"MaxConcurrencyPath\":\"$.maxConcurrency\"}},\"TimeoutSeconds\":30}", + "RoleArn": { + "Fn::GetAtt": [ + "StateMachine2Role6BE3CF0B", + "Arn" + ] + } + }, + "DependsOn": [ + "StateMachine2Role6BE3CF0B" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "StateMachine2DistributedMapPolicyECDEB23C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "StateMachine21CE8E3CE" + } + }, + { + "Action": [ + "states:DescribeExecution", + "states:StopExecution" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Ref": "StateMachine21CE8E3CE" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachine2DistributedMapPolicyECDEB23C", + "Roles": [ + { + "Ref": "StateMachine2Role6BE3CF0B" + } + ] + } + } + }, + "Outputs": { + "StateMachine1ARN": { + "Value": { + "Ref": "StateMachine18AFC9B86" + } + }, + "StateMachine1RoleARN": { + "Value": { + "Fn::GetAtt": [ + "StateMachine1RoleDE82F282", + "Arn" + ] + } + }, + "StateMachine2ARN": { + "Value": { + "Ref": "StateMachine21CE8E3CE" + } + }, + "StateMachine2RoleARN": { + "Value": { + "Fn::GetAtt": [ + "StateMachine2Role6BE3CF0B", + "Arn" + ] + } + } + }, + "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-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets.json new file mode 100644 index 0000000000000..ce8984756f21c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.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-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json @@ -0,0 +1,36 @@ +{ + "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-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/integ.json new file mode 100644 index 0000000000000..0615a23b2885d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest": { + "stacks": [ + "cdk-stepfunctions-map-distributed-stack" + ], + "assertionStack": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert", + "assertionStackName": "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/manifest.json new file mode 100644 index 0000000000000..92798db835ee8 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/manifest.json @@ -0,0 +1,167 @@ +{ + "version": "36.0.0", + "artifacts": { + "cdk-stepfunctions-map-distributed-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdk-stepfunctions-map-distributed-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdk-stepfunctions-map-distributed-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdk-stepfunctions-map-distributed-stack.template.json", + "terminationProtection": false, + "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}/c583542f1a1a4d70c5b7930dda94ecccba23a2fcb848dad95e5812cc61d18d1f.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdk-stepfunctions-map-distributed-stack.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": [ + "cdk-stepfunctions-map-distributed-stack.assets" + ], + "metadata": { + "/cdk-stepfunctions-map-distributed-stack/StateMachine1/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine1RoleDE82F282" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine1/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine18AFC9B86" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine1/DistributedMapPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine1DistributedMapPolicyA6BF4F8F" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine1ARN": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine1ARN" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine1RoleARN": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine1RoleARN" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine2/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2Role6BE3CF0B" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine21CE8E3CE" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine2/DistributedMapPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2DistributedMapPolicyECDEB23C" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine2ARN": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2ARN" + } + ], + "/cdk-stepfunctions-map-distributed-stack/StateMachine2RoleARN": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2RoleARN" + } + ], + "/cdk-stepfunctions-map-distributed-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-stepfunctions-map-distributed-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-stepfunctions-map-distributed-stack" + }, + "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.template.json", + "terminationProtection": false, + "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}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.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": [ + "cdkstepfunctionsmapdistributediamintegDefaultTestDeployAssert63593303.assets" + ], + "metadata": { + "/cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/tree.json new file mode 100644 index 0000000000000..66c52161519a0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.js.snapshot/tree.json @@ -0,0 +1,429 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "cdk-stepfunctions-map-distributed-stack": { + "id": "cdk-stepfunctions-map-distributed-stack", + "path": "cdk-stepfunctions-map-distributed-stack", + "children": { + "Map 1": { + "id": "Map 1", + "path": "cdk-stepfunctions-map-distributed-stack/Map 1", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.DistributedMap", + "version": "0.0.0" + } + }, + "Pass State 1": { + "id": "Pass State 1", + "path": "cdk-stepfunctions-map-distributed-stack/Pass State 1", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.Pass", + "version": "0.0.0" + } + }, + "StateMachine1": { + "id": "StateMachine1", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1", + "children": { + "Role": { + "id": "Role", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::StepFunctions::StateMachine", + "aws:cdk:cloudformation:props": { + "definitionString": "{\"StartAt\":\"My-Map-State\",\"States\":{\"My-Map-State\":{\"Type\":\"Map\",\"End\":true,\"ItemsPath\":\"$.inputForMap\",\"ItemSelector\":{\"foo\":\"foo\",\"bar.$\":\"$.bar\"},\"ItemProcessor\":{\"ProcessorConfig\":{\"Mode\":\"DISTRIBUTED\",\"ExecutionType\":\"STANDARD\"},\"StartAt\":\"Pass State 1\",\"States\":{\"Pass State 1\":{\"Type\":\"Pass\",\"End\":true}}},\"MaxConcurrencyPath\":\"$.maxConcurrency\"}},\"TimeoutSeconds\":30}", + "roleArn": { + "Fn::GetAtt": [ + "StateMachine1RoleDE82F282", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.CfnStateMachine", + "version": "0.0.0" + } + }, + "DistributedMapPolicy": { + "id": "DistributedMapPolicy", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/DistributedMapPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1/DistributedMapPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "StateMachine18AFC9B86" + } + }, + { + "Action": [ + "states:DescribeExecution", + "states:StopExecution" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Ref": "StateMachine18AFC9B86" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "StateMachine1DistributedMapPolicyA6BF4F8F", + "roles": [ + { + "Ref": "StateMachine1RoleDE82F282" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.StateMachine", + "version": "0.0.0" + } + }, + "StateMachine1ARN": { + "id": "StateMachine1ARN", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1ARN", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "StateMachine1RoleARN": { + "id": "StateMachine1RoleARN", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine1RoleARN", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "Map 2": { + "id": "Map 2", + "path": "cdk-stepfunctions-map-distributed-stack/Map 2", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.DistributedMap", + "version": "0.0.0" + } + }, + "Pass State 2": { + "id": "Pass State 2", + "path": "cdk-stepfunctions-map-distributed-stack/Pass State 2", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.Pass", + "version": "0.0.0" + } + }, + "StateMachine2": { + "id": "StateMachine2", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2", + "children": { + "Role": { + "id": "Role", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::StepFunctions::StateMachine", + "aws:cdk:cloudformation:props": { + "definitionString": "{\"StartAt\":\"My-Map-State\",\"States\":{\"My-Map-State\":{\"Type\":\"Map\",\"End\":true,\"ItemsPath\":\"$.inputForMap\",\"ItemSelector\":{\"foo\":\"foo\",\"bar.$\":\"$.bar\"},\"ItemProcessor\":{\"ProcessorConfig\":{\"Mode\":\"DISTRIBUTED\",\"ExecutionType\":\"STANDARD\"},\"StartAt\":\"Pass State 2\",\"States\":{\"Pass State 2\":{\"Type\":\"Pass\",\"End\":true}}},\"MaxConcurrencyPath\":\"$.maxConcurrency\"}},\"TimeoutSeconds\":30}", + "roleArn": { + "Fn::GetAtt": [ + "StateMachine2Role6BE3CF0B", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.CfnStateMachine", + "version": "0.0.0" + } + }, + "DistributedMapPolicy": { + "id": "DistributedMapPolicy", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/DistributedMapPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2/DistributedMapPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "StateMachine21CE8E3CE" + } + }, + { + "Action": [ + "states:DescribeExecution", + "states:StopExecution" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Ref": "StateMachine21CE8E3CE" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "StateMachine2DistributedMapPolicyECDEB23C", + "roles": [ + { + "Ref": "StateMachine2Role6BE3CF0B" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions.StateMachine", + "version": "0.0.0" + } + }, + "StateMachine2ARN": { + "id": "StateMachine2ARN", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2ARN", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "StateMachine2RoleARN": { + "id": "StateMachine2RoleARN", + "path": "cdk-stepfunctions-map-distributed-stack/StateMachine2RoleARN", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-stepfunctions-map-distributed-stack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-stepfunctions-map-distributed-stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "cdk-stepfunctions-map-distributed-iam-integ": { + "id": "cdk-stepfunctions-map-distributed-iam-integ", + "path": "cdk-stepfunctions-map-distributed-iam-integ", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-stepfunctions-map-distributed-iam-integ/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.ts new file mode 100644 index 0000000000000..488ced0ef4270 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.map-distributed-iam.ts @@ -0,0 +1,59 @@ +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import * as cdk from 'aws-cdk-lib'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; + +/** + * Stack verification steps: + * + * -- aws stepfunctions describe-state-machine --state-machine-arn has a status of `ACTIVE` + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'cdk-stepfunctions-map-distributed-stack'); +let index = 0; + +function createMap() { + index++; + + return new sfn.DistributedMap(stack, `Map ${index}`, { + stateName: 'My-Map-State', + maxConcurrencyPath: sfn.JsonPath.stringAt('$.maxConcurrency'), + itemsPath: sfn.JsonPath.stringAt('$.inputForMap'), + itemSelector: { + foo: 'foo', + bar: sfn.JsonPath.stringAt('$.bar'), + }, + }).itemProcessor(new sfn.Pass(stack, `Pass State ${index}`), { + mode: sfn.ProcessorMode.DISTRIBUTED, + executionType: sfn.ProcessorType.STANDARD, + }); +} + +const sm1 = new sfn.StateMachine(stack, 'StateMachine1', { + definitionBody: sfn.DefinitionBody.fromChainable(createMap()), + timeout: cdk.Duration.seconds(30), +}); + +new cdk.CfnOutput(stack, 'StateMachine1ARN', { + value: sm1.stateMachineArn, +}); +new cdk.CfnOutput(stack, 'StateMachine1RoleARN', { + value: sm1.role.roleArn, +}); + +const sm2 = new sfn.StateMachine(stack, 'StateMachine2', { + definitionBody: sfn.DefinitionBody.fromChainable(createMap()), + timeout: cdk.Duration.seconds(30), +}); + +new cdk.CfnOutput(stack, 'StateMachine2ARN', { + value: sm2.stateMachineArn, +}); +new cdk.CfnOutput(stack, 'StateMachine2RoleARN', { + value: sm2.role.roleArn, +}); + +new IntegTest(app, 'cdk-stepfunctions-map-distributed-iam-integ', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/aws-cdk-lib/aws-stepfunctions/lib/state-graph.ts b/packages/aws-cdk-lib/aws-stepfunctions/lib/state-graph.ts index 364b43064883a..94bf009b87dc0 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/lib/state-graph.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions/lib/state-graph.ts @@ -182,7 +182,15 @@ export class StateGraph { }), })); - break; + return; + } + } + + // recursively bind on any child graphs, taking care to not call it on this since that would of course create an + // infinite loop. + for (const stateGraph of this.allContainedStates.values()) { + if (stateGraph !== this) { + stateGraph.bind(stateMachine); } } } diff --git a/packages/aws-cdk-lib/aws-stepfunctions/test/state-graph.test.ts b/packages/aws-cdk-lib/aws-stepfunctions/test/state-graph.test.ts new file mode 100644 index 0000000000000..2055fd924a1c5 --- /dev/null +++ b/packages/aws-cdk-lib/aws-stepfunctions/test/state-graph.test.ts @@ -0,0 +1,101 @@ +import * as assertions from '../../assertions'; +import * as cdk from '../../core'; +import * as stepfunctions from '../lib'; + +describe('State Graph', () => { + test('bind adds execution permissions to state machine when distributed map is used within the primary graph', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const map = createMap(stack); + const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { + definitionBody: stepfunctions.DefinitionBody.fromChainable(map), + }); + const stateMachineLogicalId = stack.getLogicalId(stateMachine.node.defaultChild as stepfunctions.CfnStateMachine); + const template = assertions.Template.fromStack(stack); + + // THEN + template.hasResource('AWS::IAM::Policy', createPolicyProps(stateMachineLogicalId)); + }); + + test('bind adds execution permissions to state machine when distributed map is used within a child graph', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const map = createMap(stack); + const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { + definitionBody: stepfunctions.DefinitionBody.fromChainable(new stepfunctions.Parallel(stack, 'Parallel', { + resultPath: '$.result', + }).branch( + map, + )), + }); + const stateMachineLogicalId = stack.getLogicalId(stateMachine.node.defaultChild as stepfunctions.CfnStateMachine); + const template = assertions.Template.fromStack(stack); + + // THEN + template.hasResource('AWS::IAM::Policy', createPolicyProps(stateMachineLogicalId)); + }); + + test('looping child states do not create recursion', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const map = createMap(stack); + const start = new stepfunctions.Parallel(stack, 'Parallel', { + resultPath: '$.result', + }).branch( + map.next(map), + ); + const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { + definitionBody: stepfunctions.DefinitionBody.fromChainable(start.next(start)), + }); + const stateMachineLogicalId = stack.getLogicalId(stateMachine.node.defaultChild as stepfunctions.CfnStateMachine); + const template = assertions.Template.fromStack(stack); + + // THEN + // might as well check policy since this is around distributed map perms; however, the actual thing under test here + // is that our permissions adding doesn't create infinite recursion as we iterate through all nested graphs. + // a failure in this test case (for the thing it is meant to test) would present as a stack overflow ("maximum call + // stack size exceeded" error.) + template.hasResource('AWS::IAM::Policy', createPolicyProps(stateMachineLogicalId)); + }); +}); + +function createMap(stack: cdk.Stack) { + return new stepfunctions.DistributedMap(stack, 'Map', { + maxConcurrency: 1, + itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap'), + itemSelector: { + foo: 'foo', + bar: stepfunctions.JsonPath.stringAt('$.bar'), + }, + }).itemProcessor(new stepfunctions.Pass(this, 'Pass State')); +} + +function createPolicyProps(stateMachineLogicalId: string) { + return { + Properties: { + PolicyDocument: { + // ensure that self-starting permission is added which is necessary for distributed maps + Statement: [ + { + Action: 'states:StartExecution', + Resource: { + Ref: stateMachineLogicalId, + }, + }, + { + Action: ['states:DescribeExecution', 'states:StopExecution'], + Resource: { + 'Fn::Join': ['', [{ Ref: stateMachineLogicalId }, ':*']], + }, + }, + ], + }, + }, + }; +} \ No newline at end of file