- Overview
- Usage
- Configuration options
- Hook registry submission with StackSets
- Example templates
- Tests
- Hook development
This hook for AWS CloudFormation performs policy-as-code validation of AWS Key Management Service (AWS KMS) encryption-related, user-provided configuration settings for a number of AWS resources with which KMS integrates that are supported in this hook.
This hook also includes configuration options that you can choose to enable/use to perform additional validation operations (for example, determine if EBS Encryption by default is enabled for your account in the current Region, whether to validate the BlockDeviceMapping encryption settings of an Amazon Machine Image (AMI) ID you specify).
AWS resource types and properties supported in this hook include the following (see also the configuration options section shown next in this document):
- AWS::AutoScaling::LaunchConfiguration
- BlockDeviceMappings: validates that the Ebs property for BlockDevice list items are configured as following:
- Encrypted: validates that it is set to a boolean value of
true
. - For additional, opt-in configuration options, see also:
- Encrypted: validates that it is set to a boolean value of
- BlockDeviceMappings: validates that the Ebs property for BlockDevice list items are configured as following:
- AWS::CloudTrail::Trail
- KMSKeyId: validates that this property is set, and runs a regex pattern validation on its value.
- AWS::DynamoDB::GlobalTable
- SSESpecification properties configured as follows:
- SSEEnabled: validates that it is set to a boolean value of
true
. - SSEType: validates that it is set to
KMS
.
- SSEEnabled: validates that it is set to a boolean value of
- ReplicaSpecification properties underneath the Replicas property are configured as follows:
- KMSMasterKeyID: (property underneath SSESpecification) runs a regex pattern validation if this property has a value set.
- SSESpecification properties configured as follows:
- AWS::DynamoDB::Table
- SSESpecification properties configured as follows:
- SSEEnabled: validates that it is set to a boolean value of
true
. - SSEType: validates that it is set to
KMS
. - KMSMasterKeyID: runs a regex pattern validation if this property has a value set.
- SSEEnabled: validates that it is set to a boolean value of
- SSESpecification properties configured as follows:
- AWS::EC2::Instance
- BlockDeviceMappings: validates that the Ebs property for BlockDeviceMapping list items are configured as following:
- AWS::EC2::LaunchTemplate
- BlockDeviceMappings of LaunchTemplateData: validates that the Ebs property for BlockDeviceMapping list items are configured as following:
- AWS::EC2::Volume
- AWS::EFS::FileSystem
- AWS::Kinesis::Stream
- StreamEncryption items are configured as following:
- EncryptionType: validates that it is set to
KMS
. - KeyId: validates that this property is set, and runs a regex pattern validation on its value.
- EncryptionType: validates that it is set to
- StreamEncryption items are configured as following:
- AWS::Logs::LogGroup
- KmsKeyId: validates that this property is set, and runs a regex pattern validation on its value.
- AWS::RDS::DBCluster
- StorageEncrypted: validates that this property is set to a boolean value of
true
in the following cases:- when the following properties are not specified:
SnapshotIdentifier
SourceDBClusterIdentifier
- when the following properties are not specified:
- KmsKeyId: runs a regex pattern validation if this property has a value set.
- StorageEncrypted: validates that this property is set to a boolean value of
- AWS::RDS::DBInstance
- StorageEncrypted: validates that this property is set to a boolean value of
true
in the following cases:- when the
Engine
property value does not start with theaurora
occurrence: as per this documentation page, the encryption for Amazon Aurora DB instances is managed by the RDS DB cluster; - when the following properties are not specified:
DBClusterSnapshotIdentifier
SnapshotIdentifier
SourceDBInstanceIdentifier
- when the
- KmsKeyId: runs a regex pattern validation if this property has a value set.
- StorageEncrypted: validates that this property is set to a boolean value of
- AWS::RDS::GlobalCluster
- StorageEncrypted: validates that this property is set to a boolean value of
true
.
- StorageEncrypted: validates that this property is set to a boolean value of
- AWS::Redshift::Cluster
- AWS::S3::Bucket
- BucketEncryption: validates that the ServerSideEncryptionRule structure for ServerSideEncryptionByDefault is configured as following:
- SSEAlgorithm: validates that it is set to
aws:kms
. - KMSMasterKeyID: runs a regex pattern validation if this property has a value set.
- For additional, opt-in configuration options, see also:
- SSEAlgorithm: validates that it is set to
- BucketEncryption: validates that the ServerSideEncryptionRule structure for ServerSideEncryptionByDefault is configured as following:
- AWS::SNS::Topic
- KmsMasterKeyId: validates that this property is set, and runs a regex pattern validation on its value.
- AWS::SQS::Queue
- SqsManagedSseEnabled: validates that this property is either set to a boolean
false
, or that is not set: you can either enable SSE-KMS (that is the intent of this hook), or SSE-SQS; - KmsMasterKeyId: validates that this property is set, and runs a regex pattern validation on its value.
- SqsManagedSseEnabled: validates that this property is either set to a boolean
To get started, follow the steps shown next:
- Install Apache Maven, that you'll need to build this hook that uses Java. You'll also need to install the JDK 8 or JDK 11; for more information, see Prerequisites for developing hooks.
- Install the CloudFormation CLI.
- Run the following commands to build and submit it as a private extension to the CloudFormation registry in the AWS region(s) of your choice. The following example uses the
us-east-1
region; you will need to make a separatecfn submit
registry submission for each region you want to use (alternatively, see Hook registry submission with StackSets to submit the hook as a private extension to the registry in multiple regions with one operation):
cfn generate && mvn clean verify && cfn submit --set-default --region us-east-1
- Configure this hook: create a
type_config.json
file as shown next (for more information onProperties
defined for this hook, see Configuration options):
cat <<EOF > type_config.json
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
"UseGetEbsEncryptionByDefaultAsFallback": "no",
"ValidateAmiBlockDeviceMappingEncryptionSettings": "no",
"ValidateBucketKeyEnabled": "no"
}
}
}
}
EOF
- By default, all the supported resource type targets in this hook are included. You can, optionally, choose to only include the supported targets you require (thus, excluding the others) by adding the
TargetFilters
section to the type configuration that you will apply (as shown later on in this file). For example, if you only want to trigger the hook forAWS::S3::Bucket
andAWS::SQS::Queue
resource types on pre-create and pre-update operations, add theTargetFilters
section as follows (for more information on configuring hooks, see the Hooks structure page):
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
[...omitted for brevity...]
},
"TargetFilters":{
"TargetNames": [
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"Actions": [
"CREATE",
"UPDATE"
],
"InvocationPoints": [
"PRE_PROVISION"
]
}
}
}
}
- Get the Amazon Resource Name (ARN) for this hook:
aws cloudformation list-types \
--type HOOK \
--filters TypeNamePrefix=AwsCommunity::KMS::EncryptionSettings \
--query 'TypeSummaries[?TypeName==`AwsCommunity::KMS::EncryptionSettings`].TypeArn' \
--output text
- Find the ARN in the output of the command above, and use it with this command you'll run next:
aws cloudformation set-type-configuration \
--configuration file://type_config.json \
--type-arn 'YOUR_HOOK_ARN'
Whether or not to instruct this hook to call the GetEbsEncryptionByDefault
API to determine if EBS Encryption by default is enabled for your account in the current Region. If EBS encryption by default is enabled for your account in the current Region, this hook does not perform additional policy-as-code validation checks for a number of resource type properties, except for certain regular expression pattern checks (such as for this KmsKeyId
property values) or for certain missing mandatory property checks (such as for LaunchTemplateData
property values).
For more information on which resource type properties use this configuration option, see Supported resource types and properties in this hook. If you wish to activate this policy-as-code validation as a fallback strategy, choose yes
; otherwise, choose no
(default).
When you specify an Amazon Machine Image (AMI) ID for the ImageId
property of resource types such as AWS::AutoScaling::LaunchConfiguration
, AWS::EC2::Instance
, and AWS::EC2::LaunchTemplate
, or when this hook determines the AMI ID via the instance ID property value by calling e.g., the DescribeInstances
EC2 API (for example, the InstanceId
property), whether to validate the BlockDeviceMapping
encryption settings of the AMI. If you wish to activate this policy-as-code validation check, choose yes
; otherwise, choose no
(default).
Whether to validate if the BucketKeyEnabled
property for the Amazon S3 bucket resource type is set to true
. If you wish to activate this policy-as-code validation check, choose yes
; otherwise, choose no
(default).
This section will guide you through the process of submitting the hook to the registry as a private extension using AWS CloudFormation StackSets, to multiple regions with one operation.
To get started, use the command below to build and package the hook in an awscommunity-kms-encryptionsettings.zip
ZIP archive:
mvn clean verify && cfn submit --dry-run
Upload the ZIP archive to a bucket you own; later on, you'll need to reference the object URL of this ZIP file you uploaded, so make a note of it.
Following steps assume you'll choose to use the self-managed permissions to operate with StackSets. To get started, prepare your account by following Prerequisites for stack set operations: follow steps in Set up basic permissions for stack set operations, and create both the AWSCloudFormationStackSetAdministrationRole
and AWSCloudFormationStackSetExecutionRole
resources in your account.
Once ready, you'll use the AWS CloudFormation console to Create a stack set, for which you'll choose to use self-managed permissions:
- In the CloudFormation console, choose StackSets.
- Choose Create StackSet.
- In the Choose a template page, choose Self-service permissions.
- Specify the admin IAM role name you created earlier:
AWSCloudFormationStackSetAdministrationRole
. - Specify the IAM execution role name you created earlier:
AWSCloudFormationStackSetExecutionRole
. - Go to the Prerequisite - Prepare template section.
- Choose Template is ready.
- Specify the template to use in the Specify template section: choose to upload the
templates/private-registry-submit.yaml
template; alternatively, first upload the template to a bucket you own, and then provide the Amazon S3 template URL. Note that by using this template you'll also create a KMS key for each region you'll choose, and use each key with Amazon CloudWatch Logs log group resources that the template describes: whilst you are not required to use KMS with log group resources, the template describes a key for such resources so that theAwsCommunity::KMS::EncryptionSettings
hook will find them to be compliant (this hook encompasses log groups) as you update the stack set in the future. - Choose Next.
- In Specify StackSet details, specify the StackSet name and description.
- In Parameters, specify values you need. In
SchemaHandlerPackage
parameter, specify the object URL of the ZIP archive you uploaded earlier. Choose Next. - In Execution configuration, choose the Active managed execution. Choose Next.
- In Accounts, specify your AWS account ID in Account numbers.
- In the Regions section, specify the regions you need.
- In Deployment options, choose Parallel for Region Concurrency. Choose Next.
- In the Review page, review your choices, and select I acknowledge that AWS CloudFormation might create IAM resources.
- Choose Submit to start the StackSet creation process.
Templates in this section are marked as:
- Non-compliant: this hook will find the given template to be non-compliant, and
- Compliant: the template will be found to be compliant, and should deploy successfully.
Notes:
- example templates shown next:
- assume this hook is configured with the default Configuration options values;
- might include additional resource types on which a target resource would depend on, and that are added here to show additional context;
- there are also templates called
integ-succeed.yml
andinteg-fail.yml
in thetest
directory, that can be used to create a stack for testing: resources described in these templates are expected to be created either successfully or not, respectively.
Non-compliant (Encrypted
in BlockDeviceMappings
is set to false
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
LaunchConfigurationInstanceType:
Description: Amazon EC2 instance type to use for the LaunchConfiguration resource.
Type: String
AllowedValues:
- a1.large
Default: a1.large
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'LaunchConfigurationInstanceType'
Compliant (Encrypted
in BlockDeviceMappings
is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
LaunchConfigurationInstanceType:
Description: Amazon EC2 instance type to use for the LaunchConfiguration resource.
Type: String
AllowedValues:
- a1.large
Default: a1.large
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'LaunchConfigurationInstanceType'
Note that in the example shown next the IsLogging
property is set to false
to turn off logging for this test-only use case; independently of the setting of this property, CloudTrail will create an S3 object whose prefix starts with AWSLogs/YOUR_ACCOUNT_ID/CloudTrail/
.
Non-compliant (the AWS::CloudTrail::Trail
resource type is not set up to use KMSKeyId
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
CloudTrailTrail:
Type: AWS::CloudTrail::Trail
Properties:
EventSelectors:
- IncludeManagementEvents: true
ReadWriteType: All
IsLogging: false
S3BucketName: !Ref 'CloudTrailTrailS3Bucket'
TrailName: !Sub '${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
CloudTrailTrailS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
CloudTrailTrailS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref 'CloudTrailTrailS3Bucket'
PolicyDocument:
Statement:
- Action: s3:GetBucketAcl
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: !GetAtt CloudTrailTrailS3Bucket.Arn
Sid: AWSCloudTrailAclCheck20150319
- Action: s3:PutObject
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
s3:x-amz-acl: bucket-owner-full-control
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: !Sub '${CloudTrailTrailS3Bucket.Arn}/AWSLogs/${AWS::AccountId}/*'
Sid: AWSCloudTrailWrite20150319
Version: "2012-10-17"
Note that in the example shown next:
- the
IsLogging
property is set tofalse
to turn off logging for this test-only use case; independently of the setting of this property, CloudTrail will create an S3 object whose prefix starts withAWSLogs/YOUR_ACCOUNT_ID/CloudTrail/
; - an example KMS key policy is shown; for more information, see Configure AWS KMS key policies for CloudTrail.
Compliant (the AWS::CloudTrail::Trail
resource type is set up to use KMSKeyId
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
CloudTrailTrailKmsKey:
Type: AWS::KMS::Key
Properties:
EnableKeyRotation: true
KeyPolicy:
Id: key-default-1
Statement:
- Action: kms:*
Effect: Allow
Principal:
AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root'
Resource: '*'
Sid: Enable IAM User Permissions
- Action: kms:GenerateDataKey*
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
StringLike:
kms:EncryptionContext:aws:cloudtrail:arn: !Sub 'arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/*'
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: '*'
Sid: Allow CloudTrail to encrypt logs
- Action: kms:DescribeKey
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: '*'
Sid: Allow CloudTrail to describe key
- Action:
- kms:Decrypt
- kms:ReEncryptFrom
Condition:
StringEquals:
kms:CallerAccount: !Ref 'AWS::AccountId'
StringLike:
kms:EncryptionContext:aws:cloudtrail:arn: !Sub 'arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/*'
Effect: Allow
Principal:
AWS: '*'
Resource: '*'
Sid: Allow principals in the account to decrypt log files
Version: "2012-10-17"
PendingWindowInDays: 7
CloudTrailTrail:
Type: AWS::CloudTrail::Trail
Properties:
EventSelectors:
- IncludeManagementEvents: true
ReadWriteType: All
IsLogging: false
KMSKeyId: !Ref 'CloudTrailTrailKmsKey'
S3BucketName: !Ref 'CloudTrailTrailS3Bucket'
TrailName: !Sub '${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
CloudTrailTrailS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
CloudTrailTrailS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref 'CloudTrailTrailS3Bucket'
PolicyDocument:
Statement:
- Action: s3:GetBucketAcl
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: !GetAtt CloudTrailTrailS3Bucket.Arn
Sid: AWSCloudTrailAclCheck20150319
- Action: s3:PutObject
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${AWS::Partition}-${AWS::AccountId}-${AWS::Region}-test-only-trail'
s3:x-amz-acl: bucket-owner-full-control
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource: !Sub '${CloudTrailTrailS3Bucket.Arn}/AWSLogs/${AWS::AccountId}/*'
Sid: AWSCloudTrailWrite20150319
Version: "2012-10-17"
Non-compliant (SSESpecification
properties are missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
DynamoDbGlobalTable:
Type: AWS::DynamoDB::GlobalTable
Properties:
AttributeDefinitions:
- AttributeName: Book
AttributeType: S
- AttributeName: Author
AttributeType: S
BillingMode: PAY_PER_REQUEST
KeySchema:
- AttributeName: Book
KeyType: HASH
- AttributeName: Author
KeyType: RANGE
Replicas:
- Region: !Ref 'AWS::Region'
TableClass: STANDARD
Compliant (SSESpecification
properties are present, and are set up to use KMS encryption):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
DynamoDbGlobalTable:
Type: AWS::DynamoDB::GlobalTable
Properties:
AttributeDefinitions:
- AttributeName: Book
AttributeType: S
- AttributeName: Author
AttributeType: S
BillingMode: PAY_PER_REQUEST
KeySchema:
- AttributeName: Book
KeyType: HASH
- AttributeName: Author
KeyType: RANGE
Replicas:
- Region: !Ref 'AWS::Region'
TableClass: STANDARD
SSESpecification:
SSEEnabled: true
SSEType: KMS
Non-compliant (SSESpecification
properties are missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
DynamoDbTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: Book
AttributeType: S
- AttributeName: Author
AttributeType: S
KeySchema:
- AttributeName: Book
KeyType: HASH
- AttributeName: Author
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
Compliant (SSESpecification
properties are present, and are set up to use KMS encryption):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
DynamoDbTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: Book
AttributeType: S
- AttributeName: Author
AttributeType: S
KeySchema:
- AttributeName: Book
KeyType: HASH
- AttributeName: Author
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
SSESpecification:
SSEEnabled: true
SSEType: KMS
Non-compliant (Encrypted
in BlockDeviceMappings
is set to false
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
InstanceType:
Description: Amazon EC2 instance type to use.
Type: String
AllowedValues:
- t2.micro
- t2.small
Default: t2.micro
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'InstanceType'
Compliant (Encrypted
in BlockDeviceMappings
is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
InstanceType:
Description: Amazon EC2 instance type to use.
Type: String
AllowedValues:
- t2.micro
- t2.small
Default: t2.micro
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'InstanceType'
Non-compliant (Encrypted
in BlockDeviceMappings
is set to false
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
InstanceType:
Description: Amazon EC2 instance type to use.
Type: String
AllowedValues:
- t2.micro
- t2.small
Default: t2.micro
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: false
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'InstanceType'
Compliant (Encrypted
in BlockDeviceMappings
is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Parameters:
InstanceType:
Description: Amazon EC2 instance type to use.
Type: String
AllowedValues:
- t2.micro
- t2.small
Default: t2.micro
LatestAmiId:
Description: Region-specific image to use.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
- DeviceName: /dev/sdk
Ebs:
Encrypted: true
VolumeSize: 1
VolumeType: gp3
ImageId: !Ref 'LatestAmiId'
InstanceType: !Ref 'InstanceType'
Non-compliant (Encrypted
is set to false
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
Volume:
Type: AWS::EC2::Volume
Properties:
AvailabilityZone: !Sub '${AWS::Region}a'
Encrypted: false
Size: 1
Compliant (Encrypted
is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
Volume:
Type: AWS::EC2::Volume
Properties:
AvailabilityZone: !Sub '${AWS::Region}a'
Encrypted: true
Size: 1
Non-compliant (Encrypted
is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
EfsFileSystem:
Type: AWS::EFS::FileSystem
Compliant (Encrypted
is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
EfsFileSystem:
Type: AWS::EFS::FileSystem
Properties:
Encrypted: true
Non-compliant (StreamEncryption
properties are missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
KinesisStream:
Type: AWS::Kinesis::Stream
Properties:
ShardCount: 1
Compliant (StreamEncryption
properties are present, and are set up to use KMS encryption):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
KinesisStream:
Type: AWS::Kinesis::Stream
Properties:
ShardCount: 1
StreamEncryption:
EncryptionType: KMS
KeyId: alias/aws/kinesis
Non-compliant (the AWS::Logs::LogGroup
resource type is not set up to use KmsKeyId
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
LogsLogGroup:
Type: AWS::Logs::LogGroup
Compliant (the AWS::Logs::LogGroup
resource type is set up to use KmSKeyId
. Note that the example below uses a sample configuration for the KMS key; for more information on configuration options you can use, see Encrypt log data in CloudWatch Logs using AWS Key Management Service and AWS::KMS::Key):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
LogsLogGroup:
Type: AWS::Logs::LogGroup
Properties:
KmsKeyId: !GetAtt LogsLogGroupKmsKey.Arn
RetentionInDays: 365
LogsLogGroupKmsKey:
Type: AWS::KMS::Key
Properties:
EnableKeyRotation: true
KeyPolicy:
Id: key-default-1
Statement:
- Action: kms:*
Effect: Allow
Principal:
AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root'
Resource: '*'
Sid: Enable IAM User Permissions
- Action:
- kms:Decrypt*
- kms:Describe*
- kms:Encrypt*
- kms:GenerateDataKey*
- kms:ReEncrypt*
Condition:
ArnLike:
kms:EncryptionContext:aws:logs:arn: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*'
Effect: Allow
Principal:
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Resource: '*'
Version: "2012-10-17"
PendingWindowInDays: 7
Non-compliant (StorageEncrypted
for the AWS::RDS::DBCluster
resource type is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsDbCluster:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RdsDbClusterSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RdsDbClusterSecretsManagerSecret}::username}}'
RdsDbClusterSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"@/'
GenerateStringKey: password
PasswordLength: 30
SecretStringTemplate: '{"username": "admin"}'
RdsDbClusterSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RdsDbClusterSecretsManagerSecret'
TargetId: !Ref 'RdsDbCluster'
TargetType: AWS::RDS::DBCluster
Compliant (StorageEncrypted
for the AWS::RDS::DBCluster
resource type is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsDbCluster:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RdsDbClusterSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RdsDbClusterSecretsManagerSecret}::username}}'
StorageEncrypted: true
RdsDbClusterSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"@/'
GenerateStringKey: password
PasswordLength: 30
SecretStringTemplate: '{"username": "admin"}'
RdsDbClusterSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RdsDbClusterSecretsManagerSecret'
TargetId: !Ref 'RdsDbCluster'
TargetType: AWS::RDS::DBCluster
Non-compliant (StorageEncrypted
for the AWS::RDS::DBInstance
resource type is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsDbInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t3.micro
DBName: testdbinstance
Engine: MySQL
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RdsDbInstanceSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RdsDbInstanceSecretsManagerSecret}::username}}'
PubliclyAccessible: false
RdsDbInstanceSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"@/'
GenerateStringKey: password
PasswordLength: 30
SecretStringTemplate: '{"username": "admin"}'
RdsDbInstanceSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RdsDbInstanceSecretsManagerSecret'
TargetId: !Ref 'RdsDbInstance'
TargetType: AWS::RDS::DBInstance
Compliant (StorageEncrypted
for the AWS::RDS::DBInstance
resource type is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsDbInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t3.micro
DBName: testdbinstance
Engine: MySQL
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RdsDbInstanceSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RdsDbInstanceSecretsManagerSecret}::username}}'
PubliclyAccessible: false
StorageEncrypted: true
RdsDbInstanceSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"@/'
GenerateStringKey: password
PasswordLength: 30
SecretStringTemplate: '{"username": "admin"}'
RdsDbInstanceSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RdsDbInstanceSecretsManagerSecret'
TargetId: !Ref 'RdsDbInstance'
TargetType: AWS::RDS::DBInstance
Non-compliant (StorageEncrypted
for the AWS::RDS::GlobalCluster
resource type is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsGlobalCluster:
Type: AWS::RDS::GlobalCluster
Properties:
Engine: aurora
Compliant (StorageEncrypted
for the AWS::RDS::GlobalCluster
resource type is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RdsGlobalCluster:
Type: AWS::RDS::GlobalCluster
Properties:
Engine: aurora
StorageEncrypted: true
Non-compliant (Encrypted
for the AWS::Redshift::Cluster
resource type is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RedshiftCluster:
Type: AWS::Redshift::Cluster
Properties:
ClusterType: single-node
DBName: testdb
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RedshiftClusterSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RedshiftClusterSecretsManagerSecret}::username}}'
NodeType: dc2.large
PubliclyAccessible: false
RedshiftClusterSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"''@/\'
GenerateStringKey: password
PasswordLength: 64
SecretStringTemplate: '{"username": "admin"}'
RedshiftClusterSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RedshiftClusterSecretsManagerSecret'
TargetId: !Ref 'RedshiftCluster'
TargetType: AWS::Redshift::Cluster
Compliant (Encrypted
for the AWS::Redshift::Cluster
resource type is set to true
):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
RedshiftCluster:
Type: AWS::Redshift::Cluster
Properties:
ClusterType: single-node
DBName: testdb
Encrypted: true
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RedshiftClusterSecretsManagerSecret}::password}}'
MasterUsername: !Sub '{{resolve:secretsmanager:${RedshiftClusterSecretsManagerSecret}::username}}'
NodeType: dc2.large
PubliclyAccessible: false
RedshiftClusterSecretsManagerSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
ExcludeCharacters: '"''@/\'
GenerateStringKey: password
PasswordLength: 64
SecretStringTemplate: '{"username": "admin"}'
RedshiftClusterSecretsManagerSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref 'RedshiftClusterSecretsManagerSecret'
TargetId: !Ref 'RedshiftCluster'
TargetType: AWS::Redshift::Cluster
Non-compliant (ServerSideEncryptionConfiguration
properties are missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Compliant (ServerSideEncryptionConfiguration
properties are are present, and are set up to use KMS encryption):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
Non-compliant (KmsMasterKeyId
is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
SnsTopic:
Type: AWS::SNS::Topic
Compliant (KmsMasterKeyId
is present):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
SnsTopic:
Type: AWS::SNS::Topic
Properties:
KmsMasterKeyId: alias/aws/sns
Non-compliant (KmsMasterKeyId
is missing):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
SqsQueue:
Type: AWS::SQS::Queue
Properties:
SqsManagedSseEnabled: false
Compliant (KmsMasterKeyId
is present):
AWSTemplateFormatVersion: "2010-09-09"
Description: Example AWS CloudFormation template.
Resources:
SqsQueue:
Type: AWS::SQS::Queue
Properties:
KmsMasterKeyId: alias/aws/sqs
SqsManagedSseEnabled: false
To run unit tests, and verify code coverage, use the following command:
mvn clean verify
Contract tests help you validate hooks you develop work as expected, and must pass before you publish a hook you write, should you elect to publish your hook as a public extension. Your hook does not have to pass contract tests for being registered as a private extension, but it is highly recommended you implement contract test inputs and strive to pass them anyway.
For more information, see Testing registered hooks.
To run contract tests, start with setting up a retry configuration in your ~/.aws/config
global AWS configuration file, should it be needed. For more information, see AWS CLI retries. Assuming you use a default
profile, add this content to your file:
[default]
retry_mode = standard
max_attempts = 15
Next, open a new terminal window, and run:
sam local start-lambda
For more information, see Testing resource types locally using AWS SAM.
Open a another terminal window, and create a ~/.cfn-cli/
directory in your home directory:
mkdir ~/.cfn-cli/
Next, set up the configuration file for this hook, by copying the existing type_config.json
file you created in the usage section above into a new ~/.cfn-cli/typeConfiguration.json
file
cp type_config.json ~/.cfn-cli/typeConfiguration.json
Next, build this hook, and run contract tests:
cfn generate && mvn clean verify && cfn test -v --enforce-timeout 90
For more information on developing CloudFormation Hooks, see Developing hooks.
To extend this hook with additional resource types whose KMS-related, user-specified configuration settings you want to validate (recommendations shown below to follow the alphabetical order, where indicated, as well as other style notes, are meant to help with code maintenance):
- add the resource type name (e.g.,
AWS::SQS::Queue
) to theawscommunity-kms-encryptionsettings.json
schema, for both thepreCreate
andpreUpdate
handlers:- add the resource by following the alphabetical order;
- if you plan to make API calls from the hook to the relevant AWS service, add the needed IAM permissions in
permissions
for the handlers as well, by following the alphabetical order;
- if you plan to make API calls from the hook to the relevant AWS service: update the
pom.xml
file, and add the dependency for the relevant AWS service in the<dependencies>
section (such as, the dependency forec2
that is already in the POM file). Follow the alphabetical order of the<artifactId>
values to add the dependency; - run
cfn generate
to generate the hook input model, so to include the schema changes above; - update the supported resource types in this hook section on this file accordingly;
- if you plan to add hook configuration options:
- update the usage and configuration options sections on this file accordingly, and follow the alphabetical order for entries in configuration options;
- do the same for the
type_config.json
file, that you can find at the root level of the project;
- update the contents of the templates called
integ-succeed.yml
andinteg-fail.yml
in thetest
directory, by adding the new resource: follow the alphabetical order of the relevant resource type: assign the logical ID of the resource a name that includes the second and the third segment of the resource type name, without the::
delimiters and in pascal case (e.g.,S3Bucket
forAWS::S3::Bucket
). Resources described in these templates are expected to be created either successfully or not, respectively: reflect these intents in both templates; - update the example templates section on this file accordingly:
- follow the alphabetical order of the relevant resource type;
- add both use cases for both Non-compliant and Compliant cases, and add a brief description on why resources will be found to be Non-compliant or Compliant;
- add the class where you will implement the support for the resource type:
- as needed, create a new directory in
main/java/com/awscommunity/kms/encryptionsettings/services
, and add the relevantpackage-info.java
file to it: see thepackage-info.java
file for an existing service directory on this hook, and set the content for the new file accordingly; - add the new class file to the designated directory: mimic the name convention and method sorting that other relevant class files in other directories are using;
- make sure the class implements the
AwsKmsIntegratedService
interface; - add the implementation of the class;
- if needed, consider adding helper methods such as the existing ones in the classes inside the
helpers
package, and service-specific helpers in classes inside packages likeebs
,ec2
,rds
;
- as needed, create a new directory in
- wire up the class to the
factory
in theAwsKmsIntegratedServiceFactoryImpl
class, by following the alphabetical order; - implement unit tests:
- create a test model in the
Mocks
class for the resource type to cover: follow the existing methods whose names end inMockTargetModel
; - consume the test model in unit tests you'll implement in the
PreCreatePreUpdateHookHandlerCommonTests
class; - add unit tests by following the alphabetical order of the relevant resource type;
- wire up unit tests:
- add the wire-up methods to both the
PreCreateHookHandlerTest
andPreUpdateHookHandlerTest
classes; - when you add the wire-up methods, follow the alphabetical order of the relevant resource type;
- add the wire-up methods to both the
- create a test model in the
- run
mvn clean verify
to verify unit test coverage for this hook is met; - update contract tests inputs in the
inputs
directory: follow the alphabetical order of the relevant resource type; - make sure all contract tests pass;
- submit the hook to the private registry in your account and for a given region, and test the changes.
The RPDK will automatically generate the correct hook input model from the schema whenever the project is built via Maven. You can also do this manually with the following command: cfn generate
.
Please don't modify files under
target/generated-sources/rpdk
, as they will be automatically overwritten.
The code uses Lombok, and you may have to install IDE integrations to enable auto-complete for Lombok-annotated classes.