Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(stepfunctions): add support for EncryptionConfiguration (#30959)
### 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