Skip to content

Commit

Permalink
feat(stepfunctions): add support for EncryptionConfiguration (#30959)
Browse files Browse the repository at this point in the history
### Reason for this change

Allow customers to specify a customer managed KMS key and data key reuse period to encrypt state machine definition and execution history and activity inputs. The underlying `AWS::StepFunctions::StateMachine` and `AWS::StepFunctions::Activity` resources currently expose this through an optional `EncryptionConfiguration` property.

### Description of changes

Activity and StateMachine accept a new field called encryptionConfiguration of type `EncryptionConfiguration` in their respective props. We have two separate classes which inherit the base class: 1. `CustomerManagedEncryptionConfiguration` 2. `AwsOwnedEncryptionConfiguration`

`CustomerManagedEncryptionConfiguration`:

`kmsKey`
 - Type: `IKey`
 - Description: Symmetric customer managed KMS key for server-side encryption of the state machine definition and execution history (when provided in StateMachine Props), and Activity Inputs (when provided in Activity Props)
 - Default: `undefined`

`kmsDataKeyReusePeriodSeconds`:
 - Type: `Duration`
 - Description: Maximum duration that Step Functions will reuse customer managed data keys. When the period expires, Step Functions will call `GenerateDataKey`. Must be a value between 60 and 900 seconds.
 - Default: 300 sec


`AwsOwnedEncryptionConfiguration`
- Doesn't accept any fields


### Permission Changes

#### Activity:
 - When the customer provides `kmsKey?` the key policy will be updated with the following policy statement:
  ```
{
            "Effect": "Allow",
            "Principal": {
                "Service": "states.amazonaws.com"
            },
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:EncryptionContext:aws:states:activityArn": "arn:aws:states:<region><account_id>:activity:<activity_name>"
                }
            }
}
  ```

#### StateMachine:
 - When the customer provides `kmsKey?` the key policy will be updated with the following policy statement:
  ```
{
            "Effect": "Allow",
            "Principal": {
                "Service": "states.amazonaws.com"
            },
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:EncryptionContext:aws:states:stateMachineArn": "arn:aws:states:<region><account_id>:stateMachine:<statemachine_name>"
                }
            }
}
```

- If the state machine contains an activity which uses KMS encryption, the state machine execution role will be updated with the following permissions
```
{
            "Effect": "Allow",
            "Principal": {
                "Service": "states.amazonaws.com"
            },
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "<activity_kmskey_arn>",
}
```

- Customers have the option to encrypt data sent to CloudWatch Logs. To support this, if the customer provides both the `kmsKey?` and `logs?` prop, the following key policy statement will be added to the key used by the StateMachine:

```
{
            "Effect": "Allow",
            "Principal": {
                "Service": "delivery.logs.amazonaws.com"
            },
            "Action": [
                "kms:Decrypt",
            ]
}
```

In addition the execution role will be updated to include a separate policy that includes kms actions and encryption context for logging (otherwise customer will not see logs)
```
{
            "Effect": "Allow",
            "Action": [
                "kms:GenerateDataKey"
            ],
            "Resource": "<state_machine_kms_key_arn>",
            "Condition": {
                "StringEquals": {
                    "kms:EncryptionContext:SourceArn": "arn:aws:logs:<region><account_id>:*"
                }
            }
}
```

### Description of how you validated changes

### Unit Test (scenarios):
- Activity 
   - Creating an Activity with a KMS Key and without specifying `kmsDataKeyReusePeriodSeconds` defaults to 300 secs
   - Creating an Activity with a KMS Key results in the correct KMS key policy being generated which allows only the Activity to perform `'kms:Decrypt'`, `'kms:GenerateDataKey'` actions on the associated KMS key.
   - Creating an Activity with invalid `kmsDataKeyReusePeriodSeconds` throws an error
   - Creating an Activity with `AwsOwnedEncryptionConfiguration` uses `AWS_OWNED_KEY` encryption type.
- StateMachine 
  - Creating a State Machine with a KMS Key allows only StateMachine execution role to perform `'kms:Decrypt'`, `'kms:GenerateDataKey'` actions on the key.
  - Creating a State Machine with `logs?` and `kmsKey?`:
    -  Separate IAM policy statement (using encryption context for logging) being generated for the State Machine execution role
    -  KMS key policy statement which enables log service delivery for integrations
  - Creating a State Machine which invokes an Activity using KMS encryption results in a IAM policy generated which allows the execution role to perform `'kms:Decrypt'`, `'kms:GenerateDataKey'` actions on the Activity KMS key
  - Creating a StateMachine with a KMS Key and without specifying `kmsDataKeyReusePeriodSeconds` defaults to 300 secs
  - Creating a State Machine with a KMS key and *invalid* `kmsDataKeyReusePeriodSeconds` throws a validation error
  - Creating a StateMachine with `AwsOwnedEncryptionConfiguration` uses `AWS_OWNED_KEY` encryption type.

 
### Integration tests
 - Create a State Machine and Activity which both have encryption enabled and assert the activity input is correct when calling `getActivityTask` API
 - Create a State Machine with encryption and logging enabled. Ensure _decrypted_ logs are being pushed to the log group. 
 

### Code samples
 - #### Creating an Activity with Encryption using a Customer Managed Key
```typescript
const kmsKey = new kms.Key(this, 'Key');
const activity = new sfn.Activity(this, 'ActivityWithCMKEncryptionConfiguration', {
  activityName: 'ActivityWithCMKEncryptionConfiguration',
  encryptionConfiguration: new sfn.CustomerManagedEncryptionConfiguration(kmsKey, cdk.Duration.seconds(75))
});
```

 - #### Creating a StateMachine with Encryption using a Customer Managed Key
```typescript
const kmsKey = new kms.Key(this, 'Key');
const stateMachine = new sfn.StateMachine(this, 'StateMachineWithCMKEncryptionConfiguration', {
  stateMachineName: 'StateMachineWithCMKEncryptionConfiguration',
  definitionBody: sfn.DefinitionBody.fromChainable(sfn.Chain.start(new sfn.Pass(this, 'Pass'))),
  stateMachineType: sfn.StateMachineType.STANDARD,
  encryptionConfiguration: new sfn.CustomerManagedEncryptionConfiguration(kmsKey, cdk.Duration.seconds(60)),
});
```

- #### Creating a StateMachine with CWL Encryption using a Customer Managed Key
``` typescript
const stateMachineKmsKey = new kms.Key(this, 'StateMachine Key');
const logGroupKey = new kms.Key(this, 'LogGroup Key');

// Required KMS key policy to enrypt the CloudWatch log group
logGroupKey.addToResourcePolicy(new cdk.aws_iam.PolicyStatement({
  resources: ['*'],
  actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'],
  principals: [new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.amazonaws.com`)],
  conditions: {
    ArnEquals: {
      'kms:EncryptionContext:aws:logs:arn': cdk.Stack.of(this).formatArn({
        service: 'logs',
        resource: 'log-group',
        sep: ':',
        resourceName: '/aws/vendedlogs/states/MyLogGroup',
      }),
    },
  },
}));

const logGroup = new logs.LogGroup(this, 'MyLogGroup', {
  logGroupName: '/aws/vendedlogs/states/MyLogGroup',
  encryptionKey: logGroupKey,
});

const stateMachine = new sfn.StateMachine(this, 'StateMachineWithCMKWithCWLEncryption', {
  stateMachineName: 'StateMachineWithCMKWithCWLEncryption',
  definitionBody: sfn.DefinitionBody.fromChainable(sfn.Chain.start(new sfn.Pass(this, 'PassState', {
    result: sfn.Result.fromString('Hello World'),
  }))),
  stateMachineType: sfn.StateMachineType.STANDARD,
  encryptionConfiguration: new sfn.CustomerManagedEncryptionConfiguration(stateMachineKmsKey)
  logs: {
    destination: logGroup,
    level: sfn.LogLevel.ALL,
    includeExecutionData: true,
  },
});
```

### Checklist
- [X] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
VaidSaraswat authored Sep 20, 2024
1 parent 4a7f423 commit b49032b
Show file tree
Hide file tree
Showing 33 changed files with 68,019 additions and 10 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b49032b

Please sign in to comment.