Skip to content

Commit df163ec

Browse files
authored
feat(stepfunctions): support cross-account task invocations (#23012)
support configuring a role to be assumed for task invocations https://docs.aws.amazon.com/step-functions/latest/dg/concepts-access-cross-acct-resources.html closes #22994 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f80328c commit df163ec

18 files changed

+1579
-73
lines changed

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

+28
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,34 @@ const definition = sfn.Chain
577577
// ...
578578
```
579579

580+
## Task Credentials
581+
582+
Tasks are executed using the State Machine's execution role. In some cases, e.g. cross-account access, an IAM role can be assumed by the State Machine's execution role to provide access to the resource.
583+
This can be achieved by providing the optional `credentials` property which allows using a fixed role or a json expression to resolve the role at runtime from the task's inputs.
584+
585+
```ts
586+
import * as iam from '@aws-cdk/aws-iam';
587+
import * as lambda from '@aws-cdk/aws-lambda';
588+
589+
declare const submitLambda: lambda.Function;
590+
declare const iamRole: iam.Role;
591+
592+
// use a fixed role for all task invocations
593+
const role = sfn.TaskRole.fromRole(iamRole);
594+
// or use a json expression to resolve the role at runtime based on task inputs
595+
//const role = sfn.TaskRole.fromRoleArnJsonPath('$.RoleArn');
596+
597+
const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', {
598+
lambdaFunction: submitLambda,
599+
outputPath: '$.Payload',
600+
// use credentials
601+
credentials: { role },
602+
});
603+
```
604+
605+
See [the AWS documentation](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-access-cross-acct-resources.html)
606+
to learn more about AWS Step Functions support for accessing resources in other AWS accounts.
607+
580608
## State Machine Fragments
581609

582610
It is possible to define reusable (or abstracted) mini-state machines by

packages/@aws-cdk/aws-stepfunctions/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from './states/map';
2222
export * from './states/custom-state';
2323

2424
export * from './states/task-base';
25+
export * from './task-credentials';
2526

2627
// AWS::StepFunctions CloudFormation Resources:
2728
export * from './stepfunctions.generated';

packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import * as iam from '@aws-cdk/aws-iam';
33
import * as cdk from '@aws-cdk/core';
44
import { Construct } from 'constructs';
55
import { Chain } from '../chain';
6+
import { FieldUtils } from '../fields';
67
import { StateGraph } from '../state-graph';
8+
import { Credentials } from '../task-credentials';
79
import { CatchProps, IChainable, INextable, RetryProps } from '../types';
810
import { renderJsonPath, State } from './state';
911

@@ -91,6 +93,16 @@ export interface TaskStateBaseProps {
9193
*
9294
*/
9395
readonly integrationPattern?: IntegrationPattern;
96+
97+
/**
98+
* Credentials for an IAM Role that the State Machine assumes for executing the task.
99+
* This enables cross-account resource invocations.
100+
*
101+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-access-cross-acct-resources.html
102+
*
103+
* @default - None (Task is executed using the State Machine's execution role)
104+
*/
105+
readonly credentials?: Credentials;
94106
}
95107

96108
/**
@@ -112,12 +124,14 @@ export abstract class TaskStateBase extends State implements INextable {
112124

113125
private readonly timeout?: cdk.Duration;
114126
private readonly heartbeat?: cdk.Duration;
127+
private readonly credentials?: Credentials;
115128

116129
constructor(scope: Construct, id: string, props: TaskStateBaseProps) {
117130
super(scope, id, props);
118131
this.endStates = [this];
119132
this.timeout = props.timeout;
120133
this.heartbeat = props.heartbeat;
134+
this.credentials = props.credentials;
121135
}
122136

123137
/**
@@ -263,6 +277,13 @@ export abstract class TaskStateBase extends State implements INextable {
263277
for (const policyStatement of this.taskPolicies || []) {
264278
graph.registerPolicyStatement(policyStatement);
265279
}
280+
if (this.credentials) {
281+
graph.registerPolicyStatement(new iam.PolicyStatement({
282+
effect: iam.Effect.ALLOW,
283+
actions: ['sts:AssumeRole'],
284+
resources: [this.credentials.role.resource],
285+
}));
286+
}
266287
}
267288

268289
/**
@@ -277,6 +298,10 @@ export abstract class TaskStateBase extends State implements INextable {
277298
return this.metric(prefix + suffix, props);
278299
}
279300

301+
private renderCredentials() {
302+
return this.credentials ? FieldUtils.renderObject({ Credentials: { RoleArn: this.credentials.role.roleArn } }) : undefined;
303+
}
304+
280305
private renderTaskBase() {
281306
return {
282307
Type: 'Task',
@@ -287,6 +312,7 @@ export abstract class TaskStateBase extends State implements INextable {
287312
OutputPath: renderJsonPath(this.outputPath),
288313
ResultPath: renderJsonPath(this.resultPath),
289314
...this.renderResultSelector(),
315+
...this.renderCredentials(),
290316
};
291317
}
292318
}
@@ -347,4 +373,4 @@ export enum IntegrationPattern {
347373
* @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token
348374
*/
349375
WAIT_FOR_TASK_TOKEN = 'WAIT_FOR_TASK_TOKEN'
350-
}
376+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import { JsonPath } from './fields';
3+
4+
/**
5+
* Specifies a target role assumed by the State Machine's execution role for invoking the task's resource.
6+
*
7+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-access-cross-acct-resources.html
8+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-task-state.html#task-state-fields
9+
*/
10+
export interface Credentials {
11+
/**
12+
* The role to be assumed for executing the Task.
13+
*/
14+
readonly role: TaskRole;
15+
}
16+
17+
/**
18+
* Role to be assumed by the State Machine's execution role for invoking a task's resource.
19+
*
20+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-access-cross-acct-resources.html
21+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-task-state.html#task-state-fields
22+
*/
23+
export abstract class TaskRole {
24+
/**
25+
* Construct a task role retrieved from task inputs using a json expression
26+
*
27+
* @param expression json expression to roleArn
28+
*
29+
* @example
30+
*
31+
* TaskRole.fromRoleArnJsonPath('$.RoleArn');
32+
*/
33+
public static fromRoleArnJsonPath(expression: string): TaskRole {
34+
return new JsonExpressionTaskRole(expression);
35+
}
36+
37+
/**
38+
* Construct a task role based on the provided IAM Role
39+
*
40+
* @param role IAM Role
41+
*/
42+
public static fromRole(role: iam.IRole): TaskRole {
43+
return new IamRoleTaskRole(role);
44+
}
45+
46+
/**
47+
* Retrieves the roleArn for this TaskRole
48+
*/
49+
public abstract readonly roleArn: string;
50+
51+
/**
52+
* Retrieves the resource for use in IAM Policies for this TaskRole
53+
*/
54+
public abstract readonly resource: string;
55+
}
56+
57+
class JsonExpressionTaskRole extends TaskRole {
58+
public readonly resource: string;
59+
public readonly roleArn: string;
60+
61+
constructor(expression: string) {
62+
super();
63+
this.roleArn = JsonPath.stringAt(expression);
64+
this.resource = '*';
65+
}
66+
}
67+
68+
class IamRoleTaskRole extends TaskRole {
69+
public readonly resource: string;
70+
public readonly roleArn: string;
71+
72+
constructor(role: iam.IRole) {
73+
super();
74+
this.roleArn = role.roleArn;
75+
this.resource = role.roleArn;
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
5+
"source": {
6+
"path": "StateMachineCredentialsDefaultTestDeployAssert3F5E6D8D.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"Parameters": {
3+
"BootstrapVersion": {
4+
"Type": "AWS::SSM::Parameter::Value<String>",
5+
"Default": "/cdk-bootstrap/hnb659fds/version",
6+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
7+
}
8+
},
9+
"Rules": {
10+
"CheckBootstrapVersion": {
11+
"Assertions": [
12+
{
13+
"Assert": {
14+
"Fn::Not": [
15+
{
16+
"Fn::Contains": [
17+
[
18+
"1",
19+
"2",
20+
"3",
21+
"4",
22+
"5"
23+
],
24+
{
25+
"Ref": "BootstrapVersion"
26+
}
27+
]
28+
}
29+
]
30+
},
31+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
32+
}
33+
]
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"d775f19c6469457d54fcd62837c1d84ec75c1b8aea7b635bb10dc74dcc0e474d": {
5+
"source": {
6+
"path": "aws-stepfunctions-state-machine-credentials-integ.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "d775f19c6469457d54fcd62837c1d84ec75c1b8aea7b635bb10dc74dcc0e474d.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}

0 commit comments

Comments
 (0)