Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,21 @@ stepFunctions:
enabled: true
```

### KMS Key Permissions

If your state machine accesses KMS-encrypted resources (e.g. a DynamoDB table with a customer-managed KMS key), specify the key ARNs using `kmsKeyArns`. The plugin will add the required KMS permissions (`kms:Decrypt`, `kms:Encrypt`, `kms:ReEncrypt*`, `kms:GenerateDataKey*`, `kms:DescribeKey`) to the autogenerated IAM role.

```yaml
stepFunctions:
stateMachines:
hellostepfunc1:
kmsKeyArns:
- arn:aws:kms:us-east-1:123456789012:key/your-key-id
- !Ref MyKMSKey
definition:
...
```

## Current Gotcha

Please keep this gotcha in mind if you want to reference the `name` from the `resources` section. To generate Logical ID for CloudFormation, the plugin transforms the specified name in serverless.yml based on the following scheme.
Expand Down
7 changes: 7 additions & 0 deletions lib/deploy/stepFunctions/compileIamRole.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ module.exports = {
});
}

if (stateMachineObj.kmsKeyArns && stateMachineObj.kmsKeyArns.length > 0) {
iamPermissions.push({
action: 'kms:Decrypt,kms:Encrypt,kms:ReEncrypt*,kms:GenerateDataKey*,kms:DescribeKey',
resource: stateMachineObj.kmsKeyArns,
});
}

if (stateMachineObj.encryptionConfig && stateMachineObj.encryptionConfig.KmsKeyId) {
iamPermissions.push({
action: 'kms:Decrypt,kms:Encrypt',
Expand Down
72 changes: 72 additions & 0 deletions lib/deploy/stepFunctions/compileIamRole.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1688,4 +1688,76 @@ describe('#compileIamRole', () => {
expect(statements[0].Action[1]).to.equal('kms:Encrypt');
expect(statements[0].Resource[0]['Fn::Sub']).to.equal('arn:kms:....');
});

it('should add KMS data-key permissions for each kmsKeyArn', () => {
serverless.service.stepFunctions = {
stateMachines: {
myStateMachine1: {
id: 'StateMachine1',
kmsKeyArns: [
'arn:aws:kms:us-east-1:123456789012:key/key-1',
{ Ref: 'MyKMSKey' },
],
definition: {
StartAt: 'A',
States: {
A: {
Type: 'Task',
Resource: 'arn:aws:states:::dynamodb:getItem',
End: true,
},
},
},
},
},
};

serverlessStepFunctions.compileIamRole();
const statements = serverlessStepFunctions.serverless.service.provider
.compiledCloudFormationTemplate.Resources.StateMachine1Role.Properties.Policies[0]
.PolicyDocument.Statement;

const kmsStatements = statements.filter((s) => s.Action.includes('kms:Decrypt'));
expect(kmsStatements).to.have.lengthOf(1);
expect(kmsStatements[0].Effect).to.equal('Allow');
expect(kmsStatements[0].Action).to.deep.equal([
'kms:Decrypt',
'kms:Encrypt',
'kms:ReEncrypt*',
'kms:GenerateDataKey*',
'kms:DescribeKey',
]);
expect(kmsStatements[0].Resource).to.deep.equal([
'arn:aws:kms:us-east-1:123456789012:key/key-1',
{ Ref: 'MyKMSKey' },
]);
});

it('should not add kms permissions when kmsKeyArns is absent', () => {
serverless.service.stepFunctions = {
stateMachines: {
myStateMachine1: {
id: 'StateMachine1',
definition: {
StartAt: 'A',
States: {
A: {
Type: 'Task',
Resource: 'arn:aws:states:::dynamodb:getItem',
End: true,
},
},
},
},
},
};

serverlessStepFunctions.compileIamRole();
const statements = serverlessStepFunctions.serverless.service.provider
.compiledCloudFormationTemplate.Resources.StateMachine1Role.Properties.Policies[0]
.PolicyDocument.Statement;

const kmsStatements = statements.filter((s) => s.Action.includes('kms:Decrypt'));
expect(kmsStatements).to.have.lengthOf(0);
});
});
3 changes: 3 additions & 0 deletions lib/deploy/stepFunctions/compileStateMachines.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const encryptionConfig = Joi.object().keys({
Type: Joi.string().default('AWS_OWNED_KEY'),
});

const kmsKeyArns = Joi.array().items(arn);

const iamRoleStatements = Joi.array().items(
Joi.object({
Effect: Joi.string().valid('Allow', 'Deny'),
Expand Down Expand Up @@ -91,6 +93,7 @@ const schema = Joi.object().keys({
encryptionConfig,
inheritGlobalTags,
iamRoleStatements,
kmsKeyArns,
}).oxor('role', 'iamRoleStatements');

module.exports = schema;
Loading