Skip to content

Commit

Permalink
updated grant APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaizen Conroy committed Jun 17, 2020
1 parent 2de64e3 commit 037fc09
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 105 deletions.
97 changes: 58 additions & 39 deletions packages/@aws-cdk/aws-stepfunctions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,22 @@ const activity = new stepfunctions.Activity(this, 'Activity');
new cdk.CfnOutput(this, 'ActivityArn', { value: activity.activityArn });
```

### Activity-Level Permissions

Granting IAM permissions to an activity can be achieved by calling the `grant(principal, actions)` API:

```ts
const activity = new stepfunctions.Activity(this, 'Activity');

const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

activity.grant(role, ['states:SendTaskSuccess']);
```

This will grant the IAM principal the specified actions onto the activity.

## Metrics

`Task` object expose various metrics on the execution of that particular task. For example,
Expand Down Expand Up @@ -514,96 +530,99 @@ IAM roles, users, or groups which need to be able to work with a State Machine s

Any object that implements the `IGrantable` interface (has an associated principal) can be granted permissions by calling:

- `stateMachine.grantStartExecution(principal)` - grants the principal the ability to start an execution
- `stateMachine.grantStartExecution(principal)` - grants the principal the ability to execute the state machine
- `stateMachine.grantRead(principal)` - grants the principal read access
- `stateMachine.grant(principal, actions, resourceArn)` - grants the principal the specific IAM action specified
- `stateMachine.grantTaskResponse(principal)` - grants the principal the ability to send task tokens to the state machine
- `stateMachine.grantExecution(principal, actions)` - grants the principal execution-level permissions for the IAM actions specified
- `stateMachine.grant(principal, actions)` - grants the principal state-machine-level permissions for the IAM actions specified

### Read Permissions
### Start Execution Permission

Grant `read` access to a state machine by calling the `grantRead()` API.
Grant permission to start an execution of a state machine by calling the `grantStartExecution()` API.

```ts
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
definition,
});

//give role read access to state machine
stateMachine.grantRead(role);
// Give role permission to start execution of state machine
stateMachine.grantStartExecution(role);
```

The following read permissions are provided to a service principal by the `grantRead()` API:
The following permission is provided to a service principal by the `grantStartExecution()` API:

- `states:ListExecutions` - to state machine
- `states:ListStateMachines` - to state machine
- `states:DescribeExecution` - to executions
- `states:DescribeStateMachineForExecution` - to executions
- `states:GetExecutionHistory` - to executions
- `states:ListActivities` - to `*`
- `states:DescribeStateMachine` - to `*`
- `states:DescribeActivity` - to `*`
- `states:StartExecution` - to state machine

### Start Execution Permission
### Read Permissions

Grant permission to start an execution of a state machine by calling the `grantStartExecution()` API.
Grant `read` access to a state machine by calling the `grantRead()` API.

```ts
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
definition,
});

//give role permission to start execution of state machine
stateMachine.grantStartExecution(role);
// Give role read access to state machine
stateMachine.grantRead(role);
```

The following permission is provided to a service principal by the `grantStartExecution()` API:
The following read permissions are provided to a service principal by the `grantRead()` API:

- `states:StartExecution` - to state machine
- `states:ListExecutions` - to state machine
- `states:ListStateMachines` - to state machine
- `states:DescribeExecution` - to executions
- `states:DescribeStateMachineForExecution` - to executions
- `states:GetExecutionHistory` - to executions
- `states:ListActivities` - to `*`
- `states:DescribeStateMachine` - to `*`
- `states:DescribeActivity` - to `*`

### Task Response Permissions

Grant permission to allow task responses to a state machine by calling the `grantTaskResponse()` API.

Task responses can be scoped down either to the state machine that holds a task that will use the callback pattern,
or an activity task with a separate `ARN`.

Grant permission to the state machine:
Grant permission to allow task responses to a state machine by calling the `grantTaskResponse()` API:

```ts
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
definition,
});

//default arn is the state machine arn
// Give role task response permissions to the state machine
stateMachine.grantTaskResponse(role);
```

Grant permission to an activity task:
The following read permissions are provided to a service principal by the `grantRead()` API:

- `states:SendTaskSuccess` - to state machine
- `states:SendTaskFailure` - to state machine
- `states:SendTaskHeartbeat` - to state machine

### Execution-level Permissions

Grant execution-level permissions to a state machine by calling the `grantExecution()` API:

```ts
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

const activityArn = 'arn:aws:states:*:*:activity:ActivityPrefix*';

const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
definition,
});

//specify the activity-level ARN for the task response
stateMachine.grantTaskResponse(role, activityArn);
// Give role permission to get execution history of ALL executions for the state machine
stateMachine.grantExecution(role, ['states:GetExecutionHistory']);
```

### Custom Permissions
Expand All @@ -613,12 +632,12 @@ You can add any set of permissions to a state machine by calling the `grant()` A
```ts
const user = new iam.User(stack, 'MyUser');

const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', {
definition,
});

//give user permission to send task success to the state machine
stateMachine.grant(user, ['states:SendTaskSuccess'], stateMachine.stateMachineArn);
stateMachine.grant(user, ['states:SendTaskSuccess']);
```

## Future work
Expand Down
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-stepfunctions/lib/activity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core';
import { CfnActivity } from './stepfunctions.generated';

Expand Down Expand Up @@ -73,6 +74,20 @@ export class Activity extends Resource implements IActivity {
this.activityName = this.getResourceNameAttribute(resource.attrName);
}

/**
* Grant the given identity permissions on this Activity
*
* @param identity The principal
* @param actions The list of desired actions
*/
public grant(identity: iam.IGrantable, actions: string[]) {
return iam.Grant.addToPrincipal({
grantee: identity,
actions,
resourceArns: [this.activityArn],
});
}

/**
* Return the given named metric for this Activity
*
Expand Down
45 changes: 26 additions & 19 deletions packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,38 +183,39 @@ abstract class StateMachineBase extends Resource implements IStateMachine {
}

/**
* Grant the given identity task response permissions for a resource
*
* @default stateMachineArn
* Grant the given identity task response permissions on a state machine
*/
public grantTaskResponse(identity: iam.IGrantable, activityArn?: string): iam.Grant {
// Validate activityArn
if (activityArn && Arn.parse(activityArn, ':').resource !== 'activity') {
throw new Error('activityArn must be a valid activity ARN');
}

// ActivityArn is valid if it exists
const arn = activityArn || this.stateMachineArn;

public grantTaskResponse(identity: iam.IGrantable): iam.Grant {
return iam.Grant.addToPrincipal({
grantee: identity,
actions: [
'states:SendTaskSuccess',
'states:SendTaskFailure',
'states:SendTaskHeartbeat',
],
resourceArns: [arn],
resourceArns: [this.stateMachineArn],
});
}

/**
* Grant the given identity permissions on all executions of the state machine
*/
public grantExecution(identity: iam.IGrantable, actions: string[]) {
return iam.Grant.addToPrincipal({
grantee: identity,
actions,
resourceArns: [`${this.executionArn()}:*`],
});
}

/**
* Grant the given identity custom permissions
*/
public grant(identity: iam.IGrantable, actions: string[], resourceArn: string): iam.Grant {
public grant(identity: iam.IGrantable, actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({
grantee: identity,
actions,
resourceArns: [resourceArn],
resourceArns: [this.stateMachineArn],
});
}

Expand Down Expand Up @@ -434,16 +435,22 @@ export interface IStateMachine extends IResource {
* Grant the given identity read permissions for this state machine
*
* @param identity The principal
* @param resourceArn The ARN of the resource
*/
grantTaskResponse(identity: iam.IGrantable, resourceArn?: string): iam.Grant;
grantTaskResponse(identity: iam.IGrantable): iam.Grant;

/**
* Grant the given identity permissions for all executions of a state machine
*
* @param identity The principal
* @param actions The list of desired actions
*/
grantExecution(identity: iam.IGrantable, actions: string[]): iam.Grant;

/**
* Grant the given identity custom permissions
*
* @param identity The principal
* @param actions The list of desired actions
* @param resourceArn The ARN of the resource
*/
grant(identity: iam.IGrantable, actions: string[], resourceArn: string): iam.Grant;
grant(identity: iam.IGrantable, actions: string[]): iam.Grant;
}
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { arrayWith, objectLike } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import * as stepfunctions from '../lib';

Expand Down Expand Up @@ -41,4 +43,32 @@ describe('Activity', () => {
statistic: 'Sum',
});
});

test('Activity can grant permissions to a role', () => {
// GIVEN
const stack = new cdk.Stack();

const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

const activity = new stepfunctions.Activity(stack, 'Activity');

// WHEN
activity.grant(role, ['states:SendTaskSuccess']);

// THEN
expect(stack).toHaveResourceLike('AWS::IAM::Policy', {
PolicyDocument: {
Statement: arrayWith(objectLike({
Action: 'states:SendTaskSuccess',
Effect: 'Allow',
Resource: {
Ref: 'Activity04690B0A',
},
})),
},
});

});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
"StateMachine2E01A3A5": {
"Type": "AWS::StepFunctions::StateMachine",
"Properties": {
"DefinitionString": "{\"StartAt\":\"my custom task\",\"States\":{\"my custom task\":{\"Next\":\"final step\",\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::dynamodb:putItem\",\"Parameters\":{\"TableName\":\"my-cool-table\",\"Item\":{\"id\":{\"S\":\"my-entry\"}}},\"ResultPath\":null},\"final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}",
"RoleArn": {
"Fn::GetAtt": [
"StateMachineRoleB840431D",
"Arn"
]
}
},
"DefinitionString": "{\"StartAt\":\"my custom task\",\"States\":{\"my custom task\":{\"Next\":\"final step\",\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::dynamodb:putItem\",\"Parameters\":{\"TableName\":\"my-cool-table\",\"Item\":{\"id\":{\"S\":\"my-entry\"}}},\"ResultPath\":null},\"final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}"
},
"DependsOn": [
"StateMachineRoleB840431D"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ const stateMachine = new sfn.StateMachine(stack, 'StateMachine', {
});

stateMachine.grantRead(role);
stateMachine.grant(role, ['states:SendTaskSuccess'], stateMachine.stateMachineArn);
stateMachine.grant(role, ['states:SendTaskSuccess']);

app.synth();
Loading

0 comments on commit 037fc09

Please sign in to comment.