Cloudformation Hooks
Cloudformation Hooks
Copyright © 2024 Amazon Web Services, Inc. and/or its affiliates. All rights reserved.
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Amazon's trademarks and trade dress may not be used in connection with any product or service
that is not Amazon's, in any manner that is likely to cause confusion among customers, or in any
manner that disparages or discredits Amazon. All other trademarks not owned by Amazon are
the property of their respective owners, who may or may not be affiliated with, connected to, or
sponsored by Amazon.
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Table of Contents
What is AWS CloudFormation Hooks? ........................................................................................... 1
Getting started ................................................................................................................................ 2
Step 1: Find a Hook in the public registry .............................................................................................. 2
Step 2: Enable the Hook ............................................................................................................................ 4
Step 3: Set the Execution role ARN ......................................................................................................... 4
Step 4: (Optional) Enable logging ............................................................................................................ 5
Step 5: Activate the Hook .......................................................................................................................... 6
Step 6: Configure the Hook ....................................................................................................................... 6
Step 7: Test the Hook ................................................................................................................................. 6
Step 8: Deactivate the Hook ...................................................................................................................... 8
Conclusion ...................................................................................................................................................... 8
Terms and concepts ......................................................................................................................... 9
Hook ................................................................................................................................................................ 9
Hook target ................................................................................................................................................. 10
target invocation point ............................................................................................................................. 10
target action ................................................................................................................................................ 10
Hook handler .............................................................................................................................................. 10
Schema ........................................................................................................................................... 12
Hook schema properties ........................................................................................................................... 12
Example Hooks schema ............................................................................................................................ 17
Configuration schema ................................................................................................................... 19
Hook configuration schema properties ................................................................................................. 19
Specifying target names, action, and invocation points .................................................................... 22
Using wildcards ........................................................................................................................................... 24
Stack level filtering ....................................................................................................................... 27
Stack level filtering components ............................................................................................................ 27
FilteringCriteria ......................................................................................................................... 27
StackNames .......................................................................................................................................... 28
StackRoles .......................................................................................................................................... 29
Include and Exclude ....................................................................................................................... 30
Stack level filtering examples ................................................................................................................. 30
Developing Hooks .......................................................................................................................... 34
Prerequisites ................................................................................................................................................ 35
Permissions for developing Hooks .................................................................................................... 36
iii
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
iv
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
You can use Hooks to enforce a variety of requirements and guidelines. For example, a security-
related Hook can verify security groups for the appropriate inbound and outbound traffic rules for
your Amazon Virtual Private Cloud (Amazon VPC). A cost-related Hook can restrict development
environments to only use smaller Amazon Elastic Compute Cloud (Amazon EC2) instance types. A
Hook designed for data availability can enforce automatic backups for Amazon Relational Database
Service (Amazon RDS) .
CloudFormation Hooks is a supported extension type in the AWS CloudFormation registry. The
registry makes it easy to distribute and activate Hooks both publicly and privately. Versioning,
and resource and module extension types are also supported by the registry. You can use pre-built
Hooks, or build your own Hooks using the CloudFormation CLI.
This guide provides an overview of the structure of AWS CloudFormation Hooks, and guides for
developing, registering, testing, managing, and publishing your own Hooks.
1
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
2. In the CloudFormation menu, in the Registry group, choose Public extensions to display the
Registry: Public extensions dashboard.
3. In the Filter panel, from the Extension type list, select Hooks.
4. In the Filter panel, from the Publisher list, select Third party. A list of third-party Hooks is
displayed. You can scroll the list to browse the available Hooks. Each Hook displays publisher
and update information, a summary, and activation status.
- Effect: Allow
Action: '*'
Resource: '*'
The Target resource types tab lists the resource types the Hook targets, and the handlers
available in the Hook.
Schema
Configuration
The Configuration tab displays the Hook's configuration schema, and the configuration for
the active Hook. You can also edit the configuration for the active Hook in this tab.
7. Click the View documentation link to view the README.md for the Hook. The documentation
provides example CloudFormation templates, including one that causes the Hook to fail, and
an example configuration.
8. Copy the example template that causes the Hook to fail, and save it to a YAML file so that you
can test the Hook in a later step.
9. Copy the configuration example to a file so that you can configure the Hook in a later step.
10. Return to the CloudFormation console.
• Choose Activate so that you can configure the Hook and enable it for your CloudFormation
account.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"resources.cloudformation.amazonaws.com",
"hooks.cloudformation.amazonaws.com"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "account"
},
"StringLike": {
"aws:SourceArn": [
"arn:aws:cloudformation:region:account:type/hook/AWSSamples-
IamPolicyDoesNotGiveAdmin-Hook/*"
]
}
}
}
]
}
{
"logRoleArn": "arn:aws:iam::account:role/rolename",
"logGroupName": "log-group-name"
}
The logging role requires the custom trust policy in Step 3: Set the Execution role ARN and the
following permissions:
• cloudwatch:ListMetrics
• cloudwatch:PutMetricData
• logs:CreateLogStream
• logs:DescribeLogGroups
• logs:DescribeLogStreams
• logs:CreateLogGroup
• logs:PutLogEvents
To configure a Hook:
2. Use the following code for the Hook's configuration JSON. This configuration targets all stacks
and fails when invoked for a stack that has a policy definition that gives the equivalent of the
AdminAccess managed policy.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks":"ALL",
"FailureMode":"FAIL",
"Properties": {}
}
}
}
5. Use Choose file to upload the failure example CloudFormation template that you saved in
Step 1: Find a Hook in the public registry.
7. In Specify stack details, provide a name for your test stack, such as TestHookFailure.
9. In Configure stack options, the default settings are sufficient for testing the Hook. Choose
Next to continue.
CloudFormation attempts to create the stack but fails when the Hook is invoked. The operation is
rolled back, and the stack is deleted. You can verify this in the stack's Events list.
Conclusion
Having completed the preceding steps, AWSSamples::IamPolicyDoesNotGiveAdmin::Hook
runs when you create a stack with an applicable Hook target. For additional information about
testing and managing Hooks, refer the chapter Developing Hooks.
Concepts
• Hook
• Hook target
• target invocation point
• target action
• Hook handler
Hook
A Hook contains code that is invoked immediately before CloudFormation creates, updates,
or deletes specific resources. Hooks can inspect the resources that CloudFormation is about
to provision. If any resources don’t comply with the organizational guidelines defined in your
Hook logic, then you may choose to either WARN users or FAIL, preventing CloudFormation from
provisioning the resource.
• Proactive validation – Reduces risk, operational overhead, and cost by identifying noncompliant
resources before they're created, updated, or deleted.
• Automatic enforcement – Provides enforcement in your AWS account to prevent noncompliant
resources from being provisioned by CloudFormation.
Your Hook logic can return success or failure. A success response will allow the operation to
continue. A failure for non-compliant resources can result in the following:
You can register your Hooks as private or third-party public extensions in the CloudFormation
registry. For more information, see Using the AWS CloudFormation registry.
Hook 9
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Hook target
Hook targets are the CloudFormation resources that you want to run a Hook against. They can
be general resources that CloudFormation supports, or third-party resources in the registry.
You specify targets while authoring a Hook. For example, you can author a Hook targeting the
AWS::S3::Bucket resource. A Hook can support multiple targets, and there is no limit on the
number of resource targets.
target action
Target action is the type of operation that triggers a Hook. The action can be CREATE, UPDATE, or
DELETE.
Hook handler
The code that handles evaluation. It is associated with a target invocation point and a target action
that mark an exact point where a Hook runs. You write handlers that host logic for these specific
points. For example, a PRE target invocation point with CREATE target action makes a preCreate
Hook handler. Code within the Hook handler runs when a matching target invocation point and
service are performing an associated target action.
Important
Stack operations that result in the status of UpdateCleanup do not invoke a Hook.
For example, during the following two scenarios, the Hook's preDelete handler is not
invoked:
Hook target 10
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
• the stack is updated after removing one resource from the template.
• a resource with the update type of replacement is deleted.
Hook handler 11
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Hook schema
A Hook includes a Hook specification represented by a JSON schema and Hook handlers. The
first step in creating a Hook is modeling a schema that defines the Hook, its properties, and their
attributes. When you initialize a Hook project using the CloudFormation CLI init command, a
Hook schema file is created for you. Use this schema file as a starting point for defining the shape
and semantics of your Hook.
{
"typeName": "string",
"description": "string",
"sourceUrl": "string",
"documentationUrl": "string",
"definitions": {
"definitionName": {
. . .
}
},
"typeConfiguration": {
"properties": {
"propertyName": {
"description": "string",
"type": "string",
. . .
},
},
"required": [
"propertyName"
. . .
],
"additionalProperties": false
},
"handlers": {
"preCreate": {
"targetNames": [
],
"permissions": [
]
},
"preUpdate": {
"targetNames": [
],
"permissions": [
]
},
"preDelete": {
"targetNames": [
],
"permissions": [
]
}
},
"additionalProperties": false
}
typeName
The unique name for your Hook. Specifies a three-part namespace for your Hook, with a
recommended pattern of Organization::Service::Hook.
Note
The following organization namespaces are reserved and can't be used in your Hook
type names:
• Alexa
• AMZN
• Amazon
• ASK
• AWS
• Custom
• Dev
Required: Yes
Pattern: ^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$
Minimum: 10
Maximum: 196
description
Required: Yes
sourceUrl
Required: No
Maximum: 4096
documentationUrl
Required: Yes
Pattern: ^https\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])(\:[0-9]*)*([\?/#].*)?$
Maximum: 4096
Note
Although the Hook schema should include complete and accurate property descriptions,
you can use the documentationURL property to provide users with more details,
including examples, use cases, and other detailed information.
definitions
It's considered a best practice to use the definitions section to define schema elements that
can be used at multiple points in your Hook type schema. You can then use a JSON pointer to
reference that element at the appropriate places in your Hook type schema.
Required: No
typeConfiguration
Required: Yes
properties
The properties of the Hook. All properties of a Hook must be expressed in the schema. Align the
Hook schema properties with the Hook type configuration properties.
Note
Nested properties aren't allowed. Instead, define any nested properties in the
definitions element, and use a $ref pointer to reference them in the desired
property.
additionalProperties
Required: Yes
handlers
Handlers specify the operations which can initiate the Hook defined in the schema, such as
Hook invocation points. For example, a preUpdate handler is invoked before the update
operations for all specified targets in the handler.
Note
At least one value must be specified for the handler.
Important
Stack operations that result in the status of UpdateCleanup do not invoke a Hook.
For example, during the following two scenarios, the Hook's preDelete handler is not
invoked:
• the stack is updated after removing one resource from the template.
• a resource with the update type of replacement is deleted.
targetNames
A string array of type names that Hook targets. For example, if a preCreate handler has an
AWS::S3::Bucket target, the Hook runs for Amazon S3 buckets during the preprovisioning
phase.
• TargetName
Pattern: ^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$
Minimum: 1
Required: Yes
Warning
SSM SecureString and Secrets Manager dynamic references are not resolved before
they are passed to Hooks.
permissions
A string array that specifies the AWS permissions needed to invoke the handler.
Required: Yes
additionalProperties
Required: Yes
{
"typeName":"MyCompany::Testing::MyTestHook",
"description":"Verifies S3 bucket and SQS queues properties before create and
update",
"sourceUrl":"https://mycorp.com/my-repo.git",
"documentationUrl":"https://mycorp.com/documentation",
"typeConfiguration":{
"properties":{
"minBuckets":{
"description":"Minimum number of compliant buckets",
"type":"string"
},
"minQueues":{
"description":"Minimum number of compliant queues",
"type":"string"
},
"encryptionAlgorithm":{
"description":"Encryption algorithm for SSE",
"default":"AES256",
"type":"string"
}
},
"required":[
],
"additionalProperties":false
},
"handlers":{
"preCreate":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
]
},
"preUpdate":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
]
},
"preDelete":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
"s3:ListBucket",
"s3:ListAllMyBuckets",
"s3:GetEncryptionConfiguration",
"sqs:ListQueues",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
]
}
},
"additionalProperties":false
}
Topics
• Hook configuration schema properties
• Specifying target names, action, and invocation points for Hook targets
• Using wildcards with Hook target names
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
...
}
"TargetFilters": {
...
}
}
}
}
HookConfiguration
Hook configuration supports activating or deactivating Hooks at stack level, failure modes, and
Hook properties values.
If the mode is set to ALL, the Hook applies to all stacks in your account during a CREATE,
UPDATE, or DELETE resource operation.
If the mode is set to NONE, the Hook won't apply to stacks in your account.
Specifies Hook runtime properties. These should match the shape of the properties
supported by Hooks schema.
TargetFilters
Specifies the target filters for the Hook. You can use target filters to invoke the Hook only
when specified target from the targets that the Hook is registered for in the Hook schema.
For example, if a Hook targets AWS::S3::Bucket and AWS::DynamoDB::Table in the
Hook schema, then target filters in the Hook configuration will apply only to those targets.
An object array that specifies the list of targets to use for target filtering.
Actions
Important
The Hook configuration schema doesn't support using the Targets object
array in conjunction with the following TargetNames, Actions, and
InvocationPoints list arrays. For more information, see the section called
“Specifying target names, action, and invocation points”.
TargetNames
Target names support concrete target names and full wildcard matching.
Pattern: ^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$
Maximum: 50
Actions
A string array that specifies the target actions for the targets that you list in
TargetNames.
A string array that specifies the invocation points for the targets that you list in
TargetNames.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {},
"TargetFilters":{
"Targets": [
{
"TargetName": "AWS::S3::Bucket",
"Action": "CREATE",
"InvocationPoint": "PRE_PROVISION"
},
{
"TargetName": "AWS::S3::Bucket",
"Action": "UPDATE",
"InvocationPoint": "PRE_PROVISION"
},
{
"TargetName": "AWS::DynamoDB::Table",
"Action": "CREATE",
"InvocationPoint": "PRE_PROVISION"
},
{
"TargetName": "AWS::DynamoDB::Table",
"Action": "UPDATE",
"InvocationPoint": "PRE_PROVISION"
}
]
}
}
}
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {},
"TargetFilters":{
"TargetNames": [
"AWS::S3::Bucket",
"AWS::DynamoDB::Table"
],
"Actions": [
"CREATE",
"UPDATE"
],
"InvocationPoints": [
"PRE_PROVISION"
]
}
}
}
}
In these examples, the configuration that uses the list arrays is more concise, but both
configurations set the Hook to be invoked only before a CREATE or UPDATE operation on
AWS::S3::Bucket or AWS::DynamoDB::Table.
However, if you want to set the Hook to be invoked before CREATE operations only on
AWS::S3::Bucket, and before UPDATE operations on AWS::DynamoDB::Table, you must use
the Targets object array for more precise control over your Hook configuration. For example:
Example 3: Using Targets object array with different Actions for each TargetName
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {},
"TargetFilters":{
"Targets": [
{
"TargetName": "AWS::S3::Bucket",
"Action": "CREATE",
"InvocationPoint": "PRE_PROVISION"
},
{
"TargetName": "AWS::DynamoDB::Table",
"Action": "UPDATE",
"InvocationPoint": "PRE_PROVISION"
}
]
}
}
}
}
The following example targets all resource types supported by Amazon S3.
{
...
"handlers": {
"preCreate": {
"targetNames": [
"AWS::S3::*"
],
"permissions": []
}
}
...
}
Using wildcards 24
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
The following example matches all resource types that have "Bucket" in the name.
{
...
"handlers": {
"preCreate": {
"targetNames": [
"AWS::*::Bucket*"
],
"permissions": []
}
}
...
}
The AWS::*::Bucket* might resolve to any of the following concrete resource types:
• AWS::Lightsail::Bucket
• AWS::S3::Bucket
• AWS::S3::BucketPolicy
• AWS::S3Outpost::Bucket
• AWS::S3Outpost::BucketPolicy
The following example configuration invokes the Hook for CREATE operations on all Amazon
S3 resource types, and for UPDATE operations on all named table resource types, such as
AWS::DynamobDB::Table or AWS::Glue::Table.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {},
"TargetFilters":{
"Targets": [
{
"TargetName": "AWS::S3::*",
"Action": "CREATE",
Using wildcards 25
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
"InvocationPoint": "PRE_PROVISION"
},
{
"TargetName": "AWS::*::Table",
"Action": "UPDATE",
"InvocationPoint": "PRE_PROVISION"
}
]
}
}
}
}
The following example configuration invokes the Hook for CREATE and UPDATE operations on
all Amazon S3 resource types, and also for CREATE and UPDATE operations on all named table
resource types, such as AWS::DynamobDB::Table or AWS::Glue::Table.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {},
"TargetFilters":{
"TargetNames": [
"AWS::S3::*",
"AWS::*::Table"
],
"Actions": [
"CREATE",
"UPDATE"
],
"InvocationPoints": [
"PRE_PROVISION"
]
}
}
}
}
Using wildcards 26
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL"
}
}
}
To use stack level filtering, you need to add a StackFilters key under HookConfiguration.
The StackFilters key has one required member and has two optional members.
FilteringCriteria
FilteringCriteria is required. It can be ALL or ANY.
...
# required parameter
"FilteringCriteria": {
"description": "Attribute to specify the filtering behavior. ANY makes the Hook
invokable if one filter matches. ALL makes the Hook invokable if all filters match",
"type": "string",
"default": "ALL",
"enum": [
"ALL",
"ANY"
]
}
...
StackNames
With the StackNames key, you can specify one or more stack names as a filter.
...
"StackNames": {
"description": "List of stack names as filters",
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"Include": {
"description": "List of stack names that the hook is going to target",
"type": "array",
"maxItems": 50,
"minItems": 1,
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/StackName"
}
},
"Exclude": {
"description": "List of stack names that the hook is going to be excluded from",
"type": "array",
"maxItems": 50,
"minItems": 1,
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/StackName"
}
}
}
}
...
StackNames 28
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
...
# with StackName defined below
"StackName": {
"description": "CloudFormation Stack name",
"type": "string",
"pattern": "^[a-zA-Z][-a-zA-Z0-9]*$",
"maxLength": 128
}
...
StackRoles
If service roles are defined for the stacks, the StackRoles key can be used to filter the stacks.
...
"StackRoles": {
"description": "List of stack roles that are performing the stack operations.",
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"Include": {
"description": "List of stack roles that the hook is going to target",
"type": "array",
"maxItems": 50,
"minItems": 1,
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/StackRole"
}
},
"Exclude": {
"description": "List of stack roles that the hook is going to be excluded from",
"type": "array",
"maxItems": 50,
"minItems": 1,
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/StackRole"
}
StackRoles 29
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
}
}
}
...
...
# with StackRole defined below
"StackRole": {
"description": "The Amazon Resource Name (ARN) of the IAM execution role to use to
perform stack operations",
"type": "string",
"pattern": "arn:.+:iam::[0-9]{12}:role/.+",
"maxLength": 256
}
...
Each filter has an Include list and Exclude list. Using StackNames as an example, the Hook
is only invoked on the stacks that are specified in Include list. If stack names are only specified
in the Exclude list, the hook is only invoked on stacks that are not in the Exclude list. If both
Include and Exclude are specified, the Hook targets what's in the Include list and not what's in
the Exclude list.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL",
"StackFilters": {
"FilteringCriteria": "ALL",
"StackNames": {
"Include": [
"stack-test-1",
"stack-test-2",
"stack-test-3"
]
}
}
}
}
}
If the stack names are instead added to the Exclude list, the Hook is invoked on any stack that is
not named stack-test-1, stack-test-2 or stack-test-3.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL",
"StackFilters": {
"FilteringCriteria": "ALL",
"StackNames": {
"Exclude": [
"stack-test-1",
"stack-test-2",
"stack-test-3"
]
}
}
}
}
}
If Include and Exclude lists aren't specified, the Hook is only invoked on the stacks in the
Include that aren't in the Exclude list. In the following example, the Hook is only invoked on
stack-test-3.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL",
"StackFilters": {
"FilteringCriteria": "ALL",
"StackNames": {
"Include": [
"stack-test-1",
"stack-test-2",
"stack-test-3"
],
"Exclude": [
"stack-test-1",
"stack-test-2"
]
}
}
}
}
}
The following Hook includes three StackNames, and one StackRole. Because the
FilteringCriteria is specified as ALL, the Hook is only invoked for stack that have both a
matching stack name and the matching stack role.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL",
"StackFilters": {
"FilteringCriteria": "ALL",
"StackNames": {
"Include": [
"stack-test-1",
"stack-test-2",
"stack-test-3"
]
},
"StackRoles": {
"Include": ["arn:aws:iam::123456789012:role/hook-role"]
}
}
}
}
}
The following Hook includes three StackNames, and one StackRole. Because the
FilteringCriteria is specified as ANY, the Hook is invoked for stack that have either a matching
stack name or the matching stack role.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"Properties": {},
"FailureMode": "FAIL",
"StackFilters": {
"FilteringCriteria": "ANY",
"StackNames": {
"Include": [
"stack-test-1",
"stack-test-2",
"stack-test-3"
]
},
"StackRoles": {
"Include": ["arn:aws:iam::123456789012:role/hook-role"]
}
}
}
}
}
1. Initiate
To initiate a Hook's project and its required files, use the CloudFormation CLI init command
and specify that you want to create a Hook. For more information, see Initiating an AWS
CloudFormation Hooks project.
2. Model
To model, author, and validate your Hook schema, define the Hook, its properties, and their
attributes.
The CloudFormation CLI creates empty handler functions which correspond to a specific Hook
invocation point. Add your own logic to these handlers to control what happens during your
Hook invocation at each stage of its target lifecycle. For more information, see Modeling AWS
CloudFormation Hooks.
3. Register
To register a Hook, submit your Hook to be registered either as a private or a public third-party
extension. Register your Hook with the submit operation. For more information, see Registering
private AWS CloudFormation Hooks.
b. Configure – Hooks are configured when the type configuration invokes against stacks.
• TargetStacks set to ALL turns the Hook on for all stack operations.
• TargetStacks set to NONE turns the Hook off, so it doesn't apply to stack operations.
• FailureMode set to WARN allows the operation to continue and sends a warning.
34
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Note
Hooks time out after 30 seconds.
The topics in this chapter guide you through the process of developing, registering, and publishing
your own Hooks with Python or Java.
Topics
Java prerequisites
• Apache Maven
• JDK 17
Note
If you intend to use the CloudFormation Command Line Interface (CLI) to initiate a
Hooks project for Java, you must install Python 3.8 or later as well. The Java plugin for
Prerequisites 35
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
the CloudFormation CLI can be installed through pip (Python's package manager), which
is distrubted with Python.
To implement Hook handlers for your Java Hooks project, you can download the Java Hook handler
example files.
Python prerequisites
To implement Hook handlers for your Python Hooks project, you can download the Python Hook
handler example files.
In addition to the CloudFormation Create, Update, and Delete stack permissions, you'll need
access to the following AWS CloudFormation operations. Access to these operations is managed
through your IAM role's CloudFormation policy.
• register-type
• list-types
• deregister-type
• set-type-configuration
To develop Hooks, you should be familiar with AWS CloudFormation templates, and either Python
or Java.
1. Install the the CloudFormation CLI with pip, the Python package manager.
2. Install either the Python or Java plugin for the CloudFormation CLI.
Python
Java
To upgrade the CloudFormation CLI and the plugin, you can use the upgrade option.
Python
Java
The init command launches a wizard that walks you through setting up the project, including a
Hooks schema file. Use this schema file as a starting point for defining the shape and semantics of
your Hooks. For more information about Hook schemas, see Schema.
mkdir ~/mycompany-testing-mytesthook
cd ~/mycompany-testing-mytesthook
cfn init
4. The init command launches a wizard that walks you through setting up the project. When
prompted, enter h to specify a Hooks project.
MyCompany::Testing::MyTestHook
6. If only one language plugin is installed, it is selected by default. If more than one language
plugin is installed, you can choose your desired language. Enter a number selection for the
language of your choice.
Python
(Optional) Choose Docker for platform-independent packaging. While Docker isn't required,
it's highly recommended to make packaging easier.
This is highly recommended unless you are experienced with cross-platform Python
packaging.
Java
Set Java package name and choose a codegen model. You can use the default package
name, or create a new one.
Results: You have successfully initiated the project and have generated the files needed to develop
a Hook. The following is an example of the directories and files that make up a Hooks project for
Python 3.8.
mycompany-testing-mytesthook.json
rpdk.log
README.md
requirements.txt
hook-role.yaml
template.yml
docs
README.md
src
__init__.py
handlers.py
models.py
target_models
aws_s3_bucket.py
Note
The files in the src directory are created based on your language selection. There are some
useful comments and examples in the generated files. Some files, such as models.py,
are automatically updated in a later step when you run the generate command to add
runtime code for your handlers.
Target invocation points and target actions specify the exact point where the Hook is invoked. Hook
handlers host executable custom logic for these points. For example, a target action of the CREATE
operation uses a preCreate handler. Your code written in the handler will invoke when Hook
targets and services perform a matching action. Hook targets are the destination where hooks are
invoked. You can specify targets such as, AWS CloudFormation public resources, private resources,
or custom resources. Hooks support an unlimited number of Hook targets.
The schema contains permissions required for the Hook. Authoring the Hook requires you to
specify permissions for each Hook handler. CloudFormation encourages authors to write policies
that follow the standard security advice of granting least privilege, or granting only the permissions
required to perform a task. Determine what users (and roles) need to do, and then craft policies
that allow them to perform only those tasks for Hook operations. CloudFormation uses these
permissions to scope-down Hook users provided permissions. These permissions are passed down
to the Hook. Hook handlers use these permissions to access AWS resources.
You can use the following schema file as a starting point to define your Hook. Use the Hook
schema to specify which handlers you want to implement. If you choose not to implement a
specific handler, remove it from the handlers' section of the Hook schema.For more details on the
schema, see Schema.
{
"typeName":"MyCompany::Testing::MyTestHook",
"description":"Verifies S3 bucket and SQS queues properties before create and
update",
"sourceUrl":"https://mycorp.com/my-repo.git",
"documentationUrl":"https://mycorp.com/documentation",
"typeConfiguration":{
"properties":{
"minBuckets":{
"description":"Minimum number of compliant buckets",
"type":"string"
},
"minQueues":{
"description":"Minimum number of compliant queues",
Modeling Hooks 40
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
"type":"string"
},
"encryptionAlgorithm":{
"description":"Encryption algorithm for SSE",
"default":"AES256",
"type":"string"
}
},
"required":[
],
"additionalProperties":false
},
"handlers":{
"preCreate":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
]
},
"preUpdate":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
]
},
"preDelete":{
"targetNames":[
"AWS::S3::Bucket",
"AWS::SQS::Queue"
],
"permissions":[
"s3:ListBucket",
"s3:ListAllMyBuckets",
"s3:GetEncryptionConfiguration",
"sqs:ListQueues",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
Modeling Hooks 41
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
]
}
},
"additionalProperties":false
}
Topics
• Modeling AWS CloudFormation Hooks using Java
• Modeling AWS CloudFormation Hooks using Python
Java based Hooks projects rely on Maven's pom.xml file as a dependency. Expand the following
section and copy the source code into the pom.xml file in the root of the project.
<groupId>com.mycompany.testing.mytesthook</groupId>
<artifactId>mycompany-testing-mytesthook-handler</artifactId>
<name>mycompany-testing-mytesthook-handler</name>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
Using Java 42
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<aws.java.sdk.version>2.16.1</aws.java.sdk.version>
<checkstyle.version>8.36.2</checkstyle.version>
<commons-io.version>2.8.0</commons-io.version>
<jackson.version>2.11.3</jackson.version>
<maven-checkstyle-plugin.version>3.1.1</maven-checkstyle-plugin.version>
<mockito.version>3.6.0</mockito.version>
<spotbugs.version>4.1.4</spotbugs.version>
<spotless.version>2.5.0</spotless.version>
<maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<cfn.generate.args/>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.16.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-
cloudformation-rpdk-java-plugin -->
<dependency>
<groupId>software.amazon.cloudformation</groupId>
<artifactId>aws-cloudformation-rpdk-java-plugin</artifactId>
<version>[2.0.0,3.0.0)</version>
</dependency>
Using Java 43
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>utils</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
Using Java 44
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-
cloudformation -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-cloudformation</artifactId>
<version>1.11.555</version>
<scope>test</scope>
</dependency>
Using Java 45
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<artifactId>jackson-dataformat-cbor</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-
modules-java8 -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-modules-java8</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>runtime</scope>
</dependency>
Using Java 46
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
</dependency>
Using Java 47
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.0-M1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-Xlint:all,-options,-processing</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
Using Java 48
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<artifact>*:*</artifact>
<excludes>
<exclude>**/Log4j2Plugins.dat</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>generate</id>
<phase>generate-sources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>cfn</executable>
<commandlineArgs>generate ${cfn.generate.args}</
commandlineArgs>
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
Using Java 49
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.basedir}/target/generated-sources/
rpdk</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<configuration>
<excludes>
<exclude>**/BaseHookConfiguration*</exclude>
<exclude>**/BaseHookHandler*</exclude>
<exclude>**/HookHandlerWrapper*</exclude>
<exclude>**/ResourceModel*</exclude>
<exclude>**/TypeConfigurationModel*</exclude>
<exclude>**/model/**/*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
Using Java 50
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>${project.basedir}</directory>
<includes>
<include>mycompany-testing-mytesthook.json</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/target/loaded-target-schemas</directory>
<includes>
<include>**/*.json</include>
</includes>
Using Java 51
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
</resource>
</resources>
</build>
</project>
Generate your hook project package. The CloudFormation CLI creates empty handler functions that
correspond to specific Hook actions in the target lifecycle as defined in the Hook specification.
cfn generate
Note
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For
more information, see Updating Lambda runtimes for resource types and hooks.
Add your own hook handler runtime code to the handlers that you choose to implement. For
example, you can add the following code for logging.
The CloudFormation CLI generates a Plain Old Java Objects (Java POJO). The following are output
examples generated from AWS::S3::Bucket.
Example AwsS3BucketTargetModel.java
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;
import...
Using Java 52
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE)
public class AwsS3BucketTargetModel extends ResourceHookTargetModel<AwsS3Bucket> {
@JsonIgnore
private static final TypeReference<AwsS3Bucket> TARGET_REFERENCE =
new TypeReference<AwsS3Bucket>() {};
@JsonIgnore
private static final TypeReference<AwsS3BucketTargetModel> MODEL_REFERENCE =
new TypeReference<AwsS3BucketTargetModel>() {};
@JsonIgnore
public static final String TARGET_TYPE_NAME = "AWS::S3::Bucket";
@JsonIgnore
public TypeReference<AwsS3Bucket> getHookTargetTypeReference() {
return TARGET_REFERENCE;
}
@JsonIgnore
public TypeReference<AwsS3BucketTargetModel> getTargetModelTypeReference() {
return MODEL_REFERENCE;
}
}
Example AwsS3Bucket.java
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;
import ...
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
Using Java 53
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE)
public class AwsS3Bucket extends ResourceHookTarget {
@JsonIgnore
public static final String TYPE_NAME = "AWS::S3::Bucket";
@JsonIgnore
public static final String IDENTIFIER_KEY_ID = "/properties/Id";
@JsonProperty("InventoryConfigurations")
private List<InventoryConfiguration> inventoryConfigurations;
@JsonProperty("WebsiteConfiguration")
private WebsiteConfiguration websiteConfiguration;
@JsonProperty("DualStackDomainName")
private String dualStackDomainName;
@JsonProperty("AccessControl")
private String accessControl;
@JsonProperty("AnalyticsConfigurations")
private List<AnalyticsConfiguration> analyticsConfigurations;
@JsonProperty("AccelerateConfiguration")
private AccelerateConfiguration accelerateConfiguration;
@JsonProperty("PublicAccessBlockConfiguration")
private PublicAccessBlockConfiguration publicAccessBlockConfiguration;
@JsonProperty("BucketName")
private String bucketName;
@JsonProperty("RegionalDomainName")
private String regionalDomainName;
@JsonProperty("OwnershipControls")
private OwnershipControls ownershipControls;
@JsonProperty("ObjectLockConfiguration")
private ObjectLockConfiguration objectLockConfiguration;
Using Java 54
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@JsonProperty("ObjectLockEnabled")
private Boolean objectLockEnabled;
@JsonProperty("LoggingConfiguration")
private LoggingConfiguration loggingConfiguration;
@JsonProperty("ReplicationConfiguration")
private ReplicationConfiguration replicationConfiguration;
@JsonProperty("Tags")
private List<Tag> tags;
@JsonProperty("DomainName")
private String domainName;
@JsonProperty("BucketEncryption")
private BucketEncryption bucketEncryption;
@JsonProperty("WebsiteURL")
private String websiteURL;
@JsonProperty("NotificationConfiguration")
private NotificationConfiguration notificationConfiguration;
@JsonProperty("LifecycleConfiguration")
private LifecycleConfiguration lifecycleConfiguration;
@JsonProperty("VersioningConfiguration")
private VersioningConfiguration versioningConfiguration;
@JsonProperty("MetricsConfigurations")
private List<MetricsConfiguration> metricsConfigurations;
@JsonProperty("IntelligentTieringConfigurations")
private List<IntelligentTieringConfiguration> intelligentTieringConfigurations;
@JsonProperty("CorsConfiguration")
private CorsConfiguration corsConfiguration;
@JsonProperty("Id")
private String id;
@JsonProperty("Arn")
private String arn;
Using Java 55
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@JsonIgnore
public JSONObject getPrimaryIdentifier() {
final JSONObject identifier = new JSONObject();
if (this.getId() != null) {
identifier.put(IDENTIFIER_KEY_ID, this.getId());
}
// only return the identifier if it can be used, i.e. if all components are
present
return identifier.length() == 1 ? identifier : null;
}
@JsonIgnore
public List<JSONObject> getAdditionalIdentifiers() {
final List<JSONObject> identifiers = new ArrayList<JSONObject>();
// only return the identifiers if any can be used
return identifiers.isEmpty() ? null : identifiers;
}
}
Example BucketEncryption.java
package software.amazon.testing.mytesthook.model.aws.s3.bucket;
import ...
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE)
public class BucketEncryption {
@JsonProperty("ServerSideEncryptionConfiguration")
private List<ServerSideEncryptionRule> serverSideEncryptionConfiguration;
Example ServerSideEncryptionRule.java
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;
Using Java 56
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import ...
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE)
public class ServerSideEncryptionRule {
@JsonProperty("BucketKeyEnabled")
private Boolean bucketKeyEnabled;
@JsonProperty("ServerSideEncryptionByDefault")
private ServerSideEncryptionByDefault serverSideEncryptionByDefault;
Example ServerSideEncryptionByDefault.java
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;
import ...
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE)
public class ServerSideEncryptionByDefault {
@JsonProperty("SSEAlgorithm")
private String sSEAlgorithm;
@JsonProperty("KMSMasterKeyID")
private String kMSMasterKeyID;
Using Java 57
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
With the POJOs generated, you can now write the handlers that actually implement the hook’s
functionality. For this example, implement the preCreate and preUpdate invocation point for
the handlers.
Topics
• Coding the API client builder
• Coding the API request maker
• Implementing the helper code
• Implementing the base handler
• Implementing the preCreate handler
• Coding the preCreate handler
• Updating the preCreate test
• Implementing the preUpdate handler
• Coding the preUpdate handler
• Updating the preUpdate test
• Implementing the preDelete handler
• Coding the preDelete handler
• Updating the preDelete handler
Example ClientBuilder.java
package com.awscommunity.kms.encryptionsettings;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.cloudformation.HookLambdaWrapper;
/**
* Describes static HTTP clients (to consume less memory) for API calls that
Using Java 58
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
private ClientBuilder() {
}
/**
* Create an HTTP client for Amazon EC2.
*
* @return Ec2Client An {@link Ec2Client} object.
*/
public static Ec2Client getEc2Client() {
return
Ec2Client.builder().httpClient(HookLambdaWrapper.HTTP_CLIENT).build();
}
}
Example Translator.java
package com.mycompany.testing.mytesthook;
import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest;
import software.amazon.awssdk.services.s3.model.ListBucketsRequest;
import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
/**
* This class is a centralized placeholder for
* - api request construction
* - object translation to/from aws sdk
*/
Using Java 59
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Example Translator.java
package com.mycompany.testing.mytesthook;
import com.google.common.collect.ImmutableMap;
import org.mockito.Mockito;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
Using Java 60
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import software.amazon.awssdk.awscore.AwsRequest;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.SdkClient;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Credentials;
import software.amazon.cloudformation.proxy.LoggerProxy;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
@lombok.Getter
public class AbstractTestBase {
protected final AwsSessionCredentials awsSessionCredential;
protected final AwsCredentialsProvider v2CredentialsProvider;
protected final AwsRequestOverrideConfiguration configuration;
protected final LoggerProxy loggerProxy;
protected final Supplier<Long> awsLambdaRuntime = () ->
Duration.ofMinutes(15).toMillis();
protected final AmazonWebServicesClientProxy proxy;
protected final Credentials mockCredentials =
new Credentials("mockAccessId", "mockSecretKey", "mockSessionToken");
@lombok.Setter
private SdkClient serviceClient;
protected AbstractTestBase() {
loggerProxy = Mockito.mock(LoggerProxy.class);
awsSessionCredential =
AwsSessionCredentials.create(mockCredentials.getAccessKeyId(),
mockCredentials.getSecretAccessKey(),
mockCredentials.getSessionToken());
v2CredentialsProvider =
StaticCredentialsProvider.create(awsSessionCredential);
Using Java 61
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
configuration = AwsRequestOverrideConfiguration.builder()
.credentialsProvider(v2CredentialsProvider)
.build();
proxy = new AmazonWebServicesClientProxy(
loggerProxy,
mockCredentials,
awsLambdaRuntime
) {
@Override
public <ClientT> ProxyClient<ClientT> newProxy(@Nonnull
Supplier<ClientT> client) {
return new ProxyClient<ClientT>() {
@Override
public <RequestT extends AwsRequest, ResponseT extends
AwsResponse>
ResponseT injectCredentialsAndInvokeV2(RequestT request,
Function<RequestT,
ResponseT> requestFunction) {
return proxy.injectCredentialsAndInvokeV2(request,
requestFunction);
}
@Override
public <RequestT extends AwsRequest, ResponseT extends
AwsResponse> CompletableFuture<ResponseT>
injectCredentialsAndInvokeV2Async(RequestT request,
Function<RequestT, CompletableFuture<ResponseT>> requestFunction) {
return proxy.injectCredentialsAndInvokeV2Async(request,
requestFunction);
}
@Override
public <RequestT extends AwsRequest, ResponseT extends
AwsResponse, IterableT extends SdkIterable<ResponseT>>
IterableT
injectCredentialsAndInvokeIterableV2(RequestT request,
Function<RequestT, IterableT> requestFunction) {
return proxy.injectCredentialsAndInvokeIterableV2(request,
requestFunction);
}
@SuppressWarnings("unchecked")
@Override
public ClientT client() {
Using Java 62
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
2. Replace the entire contents of the BaseHookHandlerStd.java file with the following code.
Using Java 63
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Example Translator.java
package com.mycompany.testing.mytesthook;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
@Override
public ProgressEvent<HookTargetModel, CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final HookHandlerRequest request,
final CallbackContext callbackContext,
final Logger logger,
final TypeConfigurationModel typeConfiguration
) {
this.logger = logger;
Using Java 64
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
typeConfiguration
);
} else if (AwsSqsQueue.TYPE_NAME.equals(targetName)) {
result = handleSqsQueueRequest(
proxy,
request,
callbackContext != null ? callbackContext : new
CallbackContext(),
proxy.newProxy(ClientBuilder::createSqsClient),
typeConfiguration
);
} else {
throw new UnsupportedTargetException(targetName);
}
log(
String.format(
"Result for [%s] invocation for target [%s] returned status [%s]
with message [%s]",
request.getHookContext().getInvocationPoint(),
targetName,
result.getStatus(),
result.getMessage()
)
);
return result;
}
Using Java 65
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
The preCreate handler verifies the server-side encryption settings for either an
AWS::S3::Bucket or AWS::SQS::Queue resource.
• For an AWS::S3::Bucket resource, the hook will only pass if the following is true:
• The encryption algorithm set for the Amazon S3 bucket is the correct algorithm required.
• For an AWS::SQS::Queue resource, the hook will only pass if the following is true:
2. Replace the entire contents of the PreCreateHookHandler.java file with the following
code.
package com.mycompany.testing.mytesthook;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
Using Java 66
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueueTargetModel;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.hook.HookStatus;
import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import
software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
import java.util.List;
@Override
public HookProgressEvent<CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final HookHandlerRequest request,
final CallbackContext callbackContext,
final Logger logger,
final TypeConfigurationModel typeConfiguration) {
} else if ("AWS::SQS::Queue".equals(targetName)) {
Using Java 67
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
if (bucket != null) {
final BucketEncryption bucketEncryption = bucket.getBucketEncryption();
if (bucketEncryption != null) {
final List<ServerSideEncryptionRule> serverSideEncryptionRules =
bucketEncryption.getServerSideEncryptionConfiguration();
if (CollectionUtils.isNotEmpty(serverSideEncryptionRules)) {
for (final ServerSideEncryptionRule rule :
serverSideEncryptionRules) {
final Boolean bucketKeyEnabled =
rule.getBucketKeyEnabled();
if (bucketKeyEnabled) {
final ServerSideEncryptionByDefault
serverSideEncryptionByDefault = rule.getServerSideEncryptionByDefault();
if (!StringUtils.equals(encryptionAlgorithm,
requiredEncryptionAlgorithm) && StringUtils.isBlank(kmsKeyId)) {
resultStatus = HookStatus.FAILED;
resultMessage = "KMS Key ID not set
and SSE Encryption Algorithm is incorrect for bucket with name: " +
bucket.getBucketName();
} else if (!StringUtils.equals(encryptionAlgorithm,
requiredEncryptionAlgorithm)) {
resultStatus = HookStatus.FAILED;
Using Java 68
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
if (resultStatus == HookStatus.FAILED) {
break;
}
}
} else {
resultStatus = HookStatus.FAILED;
resultMessage = "No SSE Encryption configurations for bucket
with name: " + bucket.getBucketName();
}
} else {
resultStatus = HookStatus.FAILED;
resultMessage = "Bucket Encryption not enabled for bucket with
name: " + bucket.getBucketName();
}
} else {
resultStatus = HookStatus.FAILED;
resultMessage = "Resource properties for S3 Bucket target model are
empty";
}
return HookProgressEvent.<CallbackContext>builder()
.status(resultStatus)
.message(resultMessage)
.errorCode(resultStatus == HookStatus.FAILED ?
HandlerErrorCode.ResourceConflict : null)
.build();
}
Using Java 69
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
return HookProgressEvent.<CallbackContext>builder()
.status(HookStatus.SUCCESS)
.message("Successfully invoked PreCreateHookHandler for target:
AWS::SQS::Queue")
.build();
}
}
package com.mycompany.testing.mytesthook;
import com.google.common.collect.ImmutableMap;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
Using Java 70
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.hook.HookContext;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
import software.amazon.cloudformation.proxy.hook.HookStatus;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
import java.util.Collections;
import java.util.Map;
@ExtendWith(MockitoExtension.class)
public class PreCreateHookHandlerTest {
@Mock
private AmazonWebServicesClientProxy proxy;
@Mock
private Logger logger;
@BeforeEach
public void setup() {
proxy = mock(AmazonWebServicesClientProxy.class);
logger = mock(Logger.class);
}
@Test
public void handleRequest_awsSqsQueueSuccess() {
Using Java 71
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.hookContext(HookContext.builder().targetName("AWS::SQS::Queue").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsS3BucketSuccess() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsS3BucketFail_bucketKeyNotEnabled() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
Using Java 72
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsS3BucketFail_incorrectSSEEncryptionAlgorithm() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsS3BucketFail_kmsKeyIdNotSet() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
Using Java 73
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsSqsQueueFail_serverSideEncryptionOff() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::SQS::Queue").targetModel(targetModel).
.build();
@Test
public void handleRequest_unsupportedTarget() {
final PreCreateHookHandler handler = new PreCreateHookHandler();
Using Java 74
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.hookContext(HookContext.builder().targetName("AWS::Unsupported::Target").targetModel(targ
.build();
assertThatExceptionOfType(UnsupportedTargetException.class)
.isThrownBy(() -> handler.handleRequest(proxy, request, null,
logger, typeConfiguration))
.withMessageContaining("Unsupported target")
.withMessageContaining("AWS::Unsupported::Target")
.satisfies(e ->
assertThat(e.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest));
}
@SuppressWarnings("SameParameterValue")
private AwsSqsQueue buildSqsQueue(final String queueName, final String
kmsKeyId) {
return AwsSqsQueue.builder()
.queueName(queueName)
.kmsMasterKeyId(kmsKeyId) // "KmsMasterKeyId" is name of the
property for an AWS::SQS::Queue
.build();
}
@SuppressWarnings("SameParameterValue")
Using Java 75
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Implement a preUpdate handler, which initiates before the update operations for all specified
targets in the handler. The preUpdate handler accomplishes the following:
• For an AWS::S3::Bucket resource, the hook will only pass if the following is true:
• The bucket encryption algorithm for an Amazon S3 bucket hasn't been modified.
Using Java 76
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
2. Replace the entire contents of the PreUpdateHookHandler.java file with the following
code.
package com.mycompany.testing.mytesthook;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
import org.apache.commons.lang3.StringUtils;
import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.hook.HookStatus;
import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import
software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
import java.util.List;
@Override
public HookProgressEvent<CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final HookHandlerRequest request,
final CallbackContext callbackContext,
final Logger logger,
final TypeConfigurationModel typeConfiguration) {
Using Java 77
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
return validateBucketEncryptionRulesNotUpdated(bucketProperties,
previousBucketProperties);
} else {
throw new UnsupportedTargetException(targetName);
}
}
private HookProgressEvent<CallbackContext>
validateBucketEncryptionRulesNotUpdated(final AwsS3Bucket resourceProperties,
final AwsS3Bucket previousResourceProperties) {
final List<ServerSideEncryptionRule> bucketEncryptionConfigs =
resourceProperties.getBucketEncryption().getServerSideEncryptionConfiguration();
final List<ServerSideEncryptionRule> previousBucketEncryptionConfigs =
previousResourceProperties.getBucketEncryption().getServerSideEncryptionConfiguration();
if (bucketEncryptionConfigs.size() !=
previousBucketEncryptionConfigs.size()) {
return HookProgressEvent.<CallbackContext>builder()
.status(HookStatus.FAILED)
.errorCode(HandlerErrorCode.NotUpdatable)
.message(
String.format(
"Current number of bucket encryption configs does not
match previous. Current has %d configs while previously there were %d configs",
bucketEncryptionConfigs.size(),
previousBucketEncryptionConfigs.size()
)
).build();
}
if (!StringUtils.equals(currentEncryptionAlgorithm,
previousEncryptionAlgorithm)) {
return HookProgressEvent.<CallbackContext>builder()
.status(HookStatus.FAILED)
.errorCode(HandlerErrorCode.NotUpdatable)
.message(
String.format(
Using Java 78
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
return HookProgressEvent.<CallbackContext>builder()
.status(HookStatus.SUCCESS)
.message("Successfully invoked PreUpdateHookHandler for target:
AWS::SQS::Queue")
.build();
}
}
2. Replace the entire contents of the PreUpdateHandlerTest.java file with the following
code.
package com.mycompany.testing.mytesthook;
import com.google.common.collect.ImmutableMap;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
import
com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
Using Java 79
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.hook.HookContext;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
import software.amazon.cloudformation.proxy.hook.HookStatus;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
import java.util.Arrays;
import java.util.stream.Stream;
@ExtendWith(MockitoExtension.class)
public class PreUpdateHookHandlerTest {
@Mock
private AmazonWebServicesClientProxy proxy;
@Mock
private Logger logger;
@BeforeEach
public void setup() {
proxy = mock(AmazonWebServicesClientProxy.class);
logger = mock(Logger.class);
}
@Test
public void handleRequest_awsS3BucketSuccess() {
final PreUpdateHookHandler handler = new PreUpdateHookHandler();
Using Java 80
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_awsS3BucketFail_bucketEncryptionConfigsDontMatch() {
final PreUpdateHookHandler handler = new PreUpdateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
Using Java 81
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
public void
handleRequest_awsS3BucketFail_bucketEncryptionAlgorithmDoesNotMatch() {
final PreUpdateHookHandler handler = new PreUpdateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).
.build();
@Test
public void handleRequest_unsupportedTarget() {
final PreUpdateHookHandler handler = new PreUpdateHookHandler();
.hookContext(HookContext.builder().targetName("AWS::Unsupported::Target").targetModel(targ
.build();
assertThatExceptionOfType(UnsupportedTargetException.class)
Using Java 82
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@SuppressWarnings("SameParameterValue")
private AwsS3Bucket buildAwsS3Bucket(
final String bucketName,
final ServerSideEncryptionRule ...serverSideEncryptionRules
) {
return AwsS3Bucket.builder()
.bucketName(bucketName)
.bucketEncryption(
BucketEncryption.builder()
.serverSideEncryptionConfiguration(
Arrays.asList(serverSideEncryptionRules)
).build()
).build();
}
Using Java 83
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Implement a preDelete handler, which initiates before the delete operations for all specified
targets in the handler. The preDelete handler accomplishes the following:
• For an AWS::S3::Bucket resource, the hook will only pass if the following is true:
• Verifies that the minimum required complaint resources will exist in the account after delete
the resource.
• The minimum required complaint resources amount is set in the hook’s type configuration.
2. Replace the entire contents of the PreDeleteHookHandler.java file with the following
code.
package com.mycompany.testing.mytesthook;
import com.google.common.annotations.VisibleForTesting;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueueTargetModel;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import software.amazon.awssdk.services.cloudformation.CloudFormationClient;
Using Java 84
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import
software.amazon.awssdk.services.cloudformation.model.CloudFormationException;
import
software.amazon.awssdk.services.cloudformation.model.DescribeStackResourceRequest;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.Bucket;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlRequest;
import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
import software.amazon.awssdk.services.sqs.model.ListQueuesResponse;
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
import software.amazon.awssdk.services.sqs.model.SqsException;
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.hook.HookContext;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
import
software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Override
protected ProgressEvent<HookTargetModel, CallbackContext>
handleS3BucketRequest(
final AmazonWebServicesClientProxy proxy,
final HookHandlerRequest request,
final CallbackContext callbackContext,
Using Java 85
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Using Java 86
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@Override
protected ProgressEvent<HookTargetModel, CallbackContext>
handleSqsQueueRequest(
final AmazonWebServicesClientProxy proxy,
final HookHandlerRequest request,
final CallbackContext callbackContext,
final ProxyClient<SqsClient> proxyClient,
final TypeConfigurationModel typeConfiguration
) {
final HookContext hookContext = request.getHookContext();
final String targetName = hookContext.getTargetName();
if (!AwsSqsQueue.TYPE_NAME.equals(targetName)) {
throw new RuntimeException(String.format("Request target type [%s] is
not 'AWS::SQS::Queue'", targetName));
}
this.sqsClient = proxyClient;
final int minQueues = NumberUtils.toInt(typeConfiguration.getMinQueues());
Using Java 87
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
targetQueueUrl = cfnClient.injectCredentialsAndInvokeV2(
DescribeStackResourceRequest.builder()
.stackName(hookContext.getTargetLogicalId())
.logicalResourceId(hookContext.getTargetLogicalId())
.build(),
cfnClient.client()::describeStackResource
).stackResourceDetail().physicalResourceId();
} catch (CloudFormationException e) {
log(String.format("Error while calling DescribeStackResource API
for queue name: %s", e.getMessage()));
}
}
Using Java 88
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@VisibleForTesting
Collection<String> getBucketSSEAlgorithm(final String bucket) {
try {
return
s3Client.injectCredentialsAndInvokeV2(Translator.createGetBucketEncryptionRequest(bucket),
s3Client.client()::getBucketEncryption)
.serverSideEncryptionConfiguration()
.rules()
.stream()
.filter(r ->
Objects.nonNull(r.applyServerSideEncryptionByDefault()))
.map(r ->
r.applyServerSideEncryptionByDefault().sseAlgorithmAsString())
.collect(Collectors.toSet());
} catch (S3Exception e) {
return new HashSet<>();
}
}
Using Java 89
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@VisibleForTesting
boolean isQueueEncrypted(final String queueUrl) {
try {
final GetQueueAttributesRequest request =
GetQueueAttributesRequest.builder()
.queueUrl(queueUrl)
.attributeNames(QueueAttributeName.KMS_MASTER_KEY_ID)
.build();
final String kmsKeyId = sqsClient.injectCredentialsAndInvokeV2(request,
sqsClient.client()::getQueueAttributes)
.attributes()
.get(QueueAttributeName.KMS_MASTER_KEY_ID);
return StringUtils.isNotBlank(kmsKeyId);
} catch (SqsException e) {
throw new CfnGeneralServiceException("Error while calling SQS
GetQueueAttributes API", e);
}
}
}
2. Replace the entire contents of the PreDeleteHookHandler.java file with the following
code.
package com.mycompany.testing.mytesthook;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.Bucket;
Using Java 90
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest;
import software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse;
import software.amazon.awssdk.services.s3.model.ListBucketsRequest;
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.ServerSideEncryptionByDefault;
import software.amazon.awssdk.services.s3.model.ServerSideEncryptionConfiguration;
import software.amazon.awssdk.services.s3.model.ServerSideEncryptionRule;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlResponse;
import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
import software.amazon.awssdk.services.sqs.model.ListQueuesResponse;
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.hook.HookContext;
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@ExtendWith(MockitoExtension.class)
public class PreDeleteHookHandlerTest extends AbstractTestBase {
@BeforeEach
Using Java 91
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
@Test
public void handleRequest_awsS3BucketSuccess() {
final PreDeleteHookHandler handler = Mockito.spy(new
PreDeleteHookHandler());
when(s3Client.listBuckets(any(ListBucketsRequest.class))).thenReturn(mockResponse);
when(s3Client.getBucketEncryption(any(GetBucketEncryptionRequest.class)))
.thenReturn(buildGetBucketEncryptionResponse("AES256"))
.thenReturn(buildGetBucketEncryptionResponse("AES256", "aws:kms"))
.thenThrow(S3Exception.builder().message("No Encrypt").build())
.thenReturn(buildGetBucketEncryptionResponse("aws:kms"))
.thenReturn(buildGetBucketEncryptionResponse("AES256"));
setServiceClient(s3Client);
Using Java 92
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.bucketName("toBeDeletedBucket")
.build()
)
)
.build())
.build();
verify(s3Client,
times(5)).getBucketEncryption(any(GetBucketEncryptionRequest.class));
verify(handler, never()).getBucketSSEAlgorithm("toBeDeletedBucket");
@Test
public void handleRequest_awsSqsQueueSuccess() {
final PreDeleteHookHandler handler = Mockito.spy(new
PreDeleteHookHandler());
when(sqsClient.getQueueUrl(any(GetQueueUrlRequest.class)))
.thenReturn(GetQueueUrlResponse.builder().queueUrl("https://
toBeDeletedQueue.queue").build());
when(sqsClient.listQueues(any(ListQueuesRequest.class)))
.thenReturn(ListQueuesResponse.builder().queueUrls(queueUrls).build());
when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class)))
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build())
Using Java 93
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
.thenReturn(GetQueueAttributesResponse.builder().attributes(new
HashMap<>()).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(new
HashMap<>()).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build());
setServiceClient(sqsClient);
verify(sqsClient,
times(5)).getQueueAttributes(any(GetQueueAttributesRequest.class));
verify(handler, never()).isQueueEncrypted("toBeDeletedQueue");
@Test
public void handleRequest_awsS3BucketFailed() {
final PreDeleteHookHandler handler = Mockito.spy(new
PreDeleteHookHandler());
Using Java 94
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
when(s3Client.listBuckets(any(ListBucketsRequest.class))).thenReturn(mockResponse);
when(s3Client.getBucketEncryption(any(GetBucketEncryptionRequest.class)))
.thenReturn(buildGetBucketEncryptionResponse("AES256"))
.thenReturn(buildGetBucketEncryptionResponse("AES256", "aws:kms"))
.thenThrow(S3Exception.builder().message("No Encrypt").build())
.thenReturn(buildGetBucketEncryptionResponse("aws:kms"))
.thenReturn(buildGetBucketEncryptionResponse("AES256"));
setServiceClient(s3Client);
Using Java 95
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
verify(s3Client,
times(5)).getBucketEncryption(any(GetBucketEncryptionRequest.class));
verify(handler, never()).getBucketSSEAlgorithm("toBeDeletedBucket");
@Test
public void handleRequest_awsSqsQueueFailed() {
final PreDeleteHookHandler handler = Mockito.spy(new
PreDeleteHookHandler());
when(sqsClient.getQueueUrl(any(GetQueueUrlRequest.class)))
.thenReturn(GetQueueUrlResponse.builder().queueUrl("https://
toBeDeletedQueue.queue").build());
when(sqsClient.listQueues(any(ListQueuesRequest.class)))
.thenReturn(ListQueuesResponse.builder().queueUrls(queueUrls).build());
when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class)))
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(new
HashMap<>()).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(new
HashMap<>()).build())
.thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttribute
"kmsKeyId")).build());
setServiceClient(sqsClient);
Using Java 96
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
verify(sqsClient,
times(5)).getQueueAttributes(any(GetQueueAttributesRequest.class));
verify(handler, never()).isQueueEncrypted("toBeDeletedQueue");
Using Java 97
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
Generate your hook project package. The CloudFormation CLI creates empty handler functions that
correspond to specific Hook actions in the target lifecycle as defined in the Hook specification.
cfn generate
Note
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For
more information, see Updating Lambda runtimes for resource types and hooks.
Add your own hook handler runtime code to the handlers that you choose to implement. For
example, you can add the following code for logging.
LOG.setLevel(logging.INFO)
Using Python 98
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
The CloudFormation CLI generates the src/models.py file from the Configuration schema.
Example models.py
import sys
from dataclasses import dataclass
from inspect import getmembers, isclass
from typing import (
AbstractSet,
Any,
Generic,
Mapping,
MutableMapping,
Optional,
Sequence,
Type,
TypeVar,
)
T = TypeVar("T")
@dataclass
class HookHandlerRequest(BaseHookHandlerRequest):
pass
@dataclass
Using Python 99
AWS CloudFormation Hooks AWS CloudFormation Hooks User Guide
class TypeConfigurationModel(BaseModel):
limitSize: Optional[str]
cidr: Optional[str]
encryptionAlgorithm: Optional[str]
@classmethod
def _deserialize(
cls: Type["_TypeConfigurationModel"],
json_data: Optional[Mapping[str, Any]],
) -> Optional["_TypeConfigurationModel"]:
if not json_data:
return None
return cls(
limitSize=json_data.get("limitSize"),
cidr=json_data.get("cidr"),
encryptionAlgorithm=json_data.get("encryptionAlgorithm"),
)
_TypeConfigurationModel = TypeConfigurationModel
With the Python data classes generated, you can write the handlers that actually implement
the Hook’s functionality. In this example, you’ll implement the preCreate, preUpdate, and
preDelete invocation points for the handlers.
Topics
• Implement the preCreate handler
• Implement the preUpdate handler
• Implement the preDelete handler
• Implement a Hook handler
The preCreate handler verifies the server-side encryption settings for either an
AWS::S3::Bucket or AWS::SQS::Queue resource.
• For an AWS::S3::Bucket resource, the Hook will only pass if the following is true.
• The Amazon S3 bucket encryption is set.
Implement a preUpdate handler, which initiates before the update operations for all specified
targets in the handler. The preUpdate handler accomplishes the following:
• For an AWS::S3::Bucket resource, the Hook will only pass if the following is true:
• The bucket encryption algorithm for an Amazon S3 bucket hasn't been modified.
Implement a preDelete handler, which initiates before the delete operations for all specified
targets in the handler. The preDelete handler accomplishes the following:
• For an AWS::S3::Bucket resource, the Hook will only pass if the following is true:
• Verifies that the minimum required compliant resources will exist in the account after delete
the resource.
• The minimum required compliant resources amount is set in the Hook’s configuration.
1. In your IDE, open the handlers.py file, located in the src folder.
2. Replace the entire contents of the handlers.py file with the following code.
Example handlers.py
import logging
from typing import Any, MutableMapping, Optional
import botocore
HandlerErrorCode,
Hook,
HookInvocationPoint,
OperationStatus,
ProgressEvent,
SessionProxy,
exceptions,
)
LOG.setLevel(logging.INFO)
def _validate_s3_bucket_encryption(
bucket: MutableMapping[str, Any], required_encryption_algorithm: str
) -> ProgressEvent:
status = None
message = ""
error_code = None
if bucket:
bucket_name = bucket.get("BucketName")
bucket_encryption = bucket.get("BucketEncryption")
if bucket_encryption:
server_side_encryption_rules = bucket_encryption.get(
"ServerSideEncryptionConfiguration"
)
if server_side_encryption_rules:
for rule in server_side_encryption_rules:
bucket_key_enabled = rule.get("BucketKeyEnabled")
if bucket_key_enabled:
server_side_encryption_by_default = rule.get(
"ServerSideEncryptionByDefault"
)
encryption_algorithm =
server_side_encryption_by_default.get(
"SSEAlgorithm"
)
kms_key_id = server_side_encryption_by_default.get(
"KMSMasterKeyID"
) # "KMSMasterKeyID" is name of the property for an
AWS::S3::Bucket
if encryption_algorithm == required_encryption_algorithm:
if encryption_algorithm == "aws:kms" and not
kms_key_id:
status = OperationStatus.FAILED
message = f"KMS Key ID not set for bucket with
name: f{bucket_name}"
else:
status = OperationStatus.SUCCESS
message = f"Successfully invoked
PreCreateHookHandler for AWS::S3::Bucket with name: {bucket_name}"
else:
status = OperationStatus.FAILED
message = f"SSE Encryption Algorithm is incorrect for
bucket with name: {bucket_name}"
else:
status = OperationStatus.FAILED
message = f"Bucket key not enabled for bucket with name:
{bucket_name}"
if status == OperationStatus.FAILED:
break
else:
status = OperationStatus.FAILED
message = f"No SSE Encryption configurations for bucket with name:
{bucket_name}"
else:
status = OperationStatus.FAILED
message = (
f"Bucket Encryption not enabled for bucket with name:
{bucket_name}"
)
else:
status = OperationStatus.FAILED
message = "Resource properties for S3 Bucket target model are empty"
if status == OperationStatus.FAILED:
error_code = HandlerErrorCode.NonCompliant
kms_key_id = queue.get(
"KmsMasterKeyId"
) # "KmsMasterKeyId" is name of the property for an AWS::SQS::Queue
if not kms_key_id:
return ProgressEvent(
status=OperationStatus.FAILED,
message=f"Server side encryption turned off for queue with name:
{queue_name}",
errorCode=HandlerErrorCode.NonCompliant,
)
return ProgressEvent(
status=OperationStatus.SUCCESS,
message=f"Successfully invoked PreCreateHookHandler for
targetAWS::SQS::Queue with name: {queue_name}",
)
@hook.handler(HookInvocationPoint.CREATE_PRE_PROVISION)
def pre_create_handler(
session: Optional[SessionProxy],
request: HookHandlerRequest,
callback_context: MutableMapping[str, Any],
type_configuration: TypeConfigurationModel,
) -> ProgressEvent:
target_name = request.hookContext.targetName
if "AWS::S3::Bucket" == target_name:
return _validate_s3_bucket_encryption(
request.hookContext.targetModel.get("resourceProperties"),
type_configuration.encryptionAlgorithm,
)
elif "AWS::SQS::Queue" == target_name:
return _validate_sqs_queue_encryption(
request.hookContext.targetModel.get("resourceProperties")
)
else:
raise exceptions.InvalidRequest(f"Unknown target type: {target_name}")
def _validate_bucket_encryption_rules_not_updated(
resource_properties, previous_resource_properties
) -> ProgressEvent:
bucket_encryption_configs = resource_properties.get("BucketEncryption",
{}).get(
"ServerSideEncryptionConfiguration", []
)
previous_bucket_encryption_configs = previous_resource_properties.get(
"BucketEncryption", {}
).get("ServerSideEncryptionConfiguration", [])
if len(bucket_encryption_configs) != len(previous_bucket_encryption_configs):
return ProgressEvent(
status=OperationStatus.FAILED,
message=f"Current number of bucket encryption configs does not
match previous. Current has {str(len(bucket_encryption_configs))} configs while
previously there were {str(len(previous_bucket_encryption_configs))} configs",
errorCode=HandlerErrorCode.NonCompliant,
)
for i in range(len(bucket_encryption_configs)):
current_encryption_algorithm = (
bucket_encryption_configs[i]
.get("ServerSideEncryptionByDefault", {})
.get("SSEAlgorithm")
)
previous_encryption_algorithm = (
previous_bucket_encryption_configs[i]
.get("ServerSideEncryptionByDefault", {})
.get("SSEAlgorithm")
)
if current_encryption_algorithm != previous_encryption_algorithm:
return ProgressEvent(
status=OperationStatus.FAILED,
message=f"Bucket Encryption algorithm can not be changed once
set. The encryption algorithm was changed to {current_encryption_algorithm} from
{previous_encryption_algorithm}.",
errorCode=HandlerErrorCode.NonCompliant,
)
return ProgressEvent(
status=OperationStatus.SUCCESS,
message="Successfully invoked PreUpdateHookHandler for target:
AWS::SQS::Queue",
)
def _validate_queue_encryption_not_disabled(
resource_properties, previous_resource_properties
) -> ProgressEvent:
if previous_resource_properties.get(
"KmsMasterKeyId"
) and not resource_properties.get("KmsMasterKeyId"):
return ProgressEvent(
status=OperationStatus.FAILED,
errorCode=HandlerErrorCode.NonCompliant,
message="Queue encryption can not be disable",
)
else:
return ProgressEvent(status=OperationStatus.SUCCESS)
@hook.handler(HookInvocationPoint.UPDATE_PRE_PROVISION)
def pre_update_handler(
session: Optional[SessionProxy],
request: BaseHookHandlerRequest,
callback_context: MutableMapping[str, Any],
type_configuration: MutableMapping[str, Any],
) -> ProgressEvent:
target_name = request.hookContext.targetName
if "AWS::S3::Bucket" == target_name:
resource_properties =
request.hookContext.targetModel.get("resourceProperties")
previous_resource_properties = request.hookContext.targetModel.get(
"previousResourceProperties"
)
return _validate_bucket_encryption_rules_not_updated(
resource_properties, previous_resource_properties
)
elif "AWS::SQS::Queue" == target_name:
resource_properties =
request.hookContext.targetModel.get("resourceProperties")
previous_resource_properties = request.hookContext.targetModel.get(
"previousResourceProperties"
)
return _validate_queue_encryption_not_disabled(
resource_properties, previous_resource_properties
)
else:
raise exceptions.InvalidRequest(f"Unknown target type: {target_name}")
In the directory of your Hook project, run the following command to build your Hook, run
unit tests, and package your project as a JAR file that you can use to submit your Hook to the
CloudFormation registry.
Register a Hook
To register a Hook
1. (Optional) Configure your default AWS Region name to us-west-2, by submitting the
configure operation.
$ aws configure
AWS Access Key ID [None]: <Your Access Key ID>
AWS Secret Access Key [None]: <Your Secret Key>
Default region name [None]: us-west-2
Default output format [None]: json
2. (Optional) The following command builds and packages your Hook project without registering
it.
{‘ProgressStatus’: ‘COMPLETE’}
1. To verify your Hook, use the list-types command to list your newly registered Hook and
return a summary description of it.
The command returns the following output and will also show you publicly available Hooks
you can activate in your AWS account and Regions.
{
"TypeSummaries": [
{
"Type": "HOOK",
"TypeName": "MyCompany::Testing::MyTestHook",
"DefaultVersionId": "00000001",
"TypeArn": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID/type/hook/
MyCompany-Testing-MyTestHook",
"LastUpdated": "2021-08-04T23:00:03.058000+00:00",
"Description": "Verifies S3 bucket and SQS queues properties before
creating or updating"
}
]
}
2. Retrieve the TypeArn from the list-type output for your Hook and save it.
export HOOK_TYPE_ARN=arn:aws:cloudformation:us-west-2:ACCOUNT_ID/type/hook/
MyCompany-Testing-MyTestHook
To learn how to publish Hooks for public use, see Publishing Hooks for public use.
Configure Hooks
After you've developed and registered your Hook, you can configure your Hook in your AWS
account by publishing it to the registry.
Note
By enabling Hooks in your account, you are authorizing a Hook to use defined
permissions from your AWS account. CloudFormation removes non-required
permissions before passing your permissions to the Hook. CloudFormation
recommends customers or Hook users to review the Hook permissions and be aware of
what permissions the Hooks are allowed to before enabling Hooks in your account.
Specify the configuration data for your registered Hook extension in the same account and
AWS Region.
--configuration '{"CloudFormationConfiguration":{"HookConfiguration":
{"TargetStacks":"ALL","FailureMode":"FAIL","Properties":{"minBuckets":
"1","minQueues": "1", "encryptionAlgorithm": "aws:kms"}}}}'
--type-arn $HOOK_TYPE_ARN
Important
To enable your Hook to proactively inspect the configuration of your stack, you must
set the TargetStacks to ALL in the HookConfiguration section, after the Hook
has been registered and activated in your account.
For more information, see Accessing AWS APIs from a resource type.
hook-role.yaml template
Note
If you choose to create your own execution role, we highly
recommend practicing the principle of least privilege by allow
listing only hooks.cloudformation.amazonaws.com and
resources.cloudformation.amazonaws.com.
The following template uses the IAM, Amazon S3, and Amazon SQS permissions.
AWSTemplateFormatVersion: 2010-09-09
Description: >
This CloudFormation template creates a role assumed by CloudFormation during
Hook operations on behalf of the customer.
Resources:
ExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- resources.cloudformation.amazonaws.com
- hooks.cloudformation.amazonaws.com
Action: 'sts:AssumeRole'
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
StringLike:
aws:SourceArn: !Sub arn:${AWS::Partition}:cloudformation:
${AWS::Region}:${AWS::AccountId}:type/hook/MyCompany-Testing-MyTestHook/*
Path: /
Policies:
- PolicyName: HookTypePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 's3:GetEncryptionConfiguration'
- 's3:ListBucket'
- 's3:ListAllMyBuckets'
- 'sqs:GetQueueAttributes'
- 'sqs:GetQueueUrl'
- 'sqs:ListQueues'
Resource: '*'
Outputs:
ExecutionRoleArn:
Value: !GetAtt
- ExecutionRole
- Arn
The Hook failure mode is set to FAIL if the CloudFormation template didn't provision an S3 bucket
with the following:
AWSTemplateFormatVersion: 2010-09-09
Resources:
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties: {}
2. Create a stack, and specify your template in the AWS Command Line Interface (AWS CLI). In
the following example, specify the stack name as my-hook-stack and the template name as
my-failed-bucket-stack.yml.
3. (Optional) View your stack progress by specifying your stack name. In the following example,
specify the stack name my-hook-stack.
--stack-name my-hook-stack
Use the describe-stack-events operation to see the Hook failure while creating the
bucket. The following is an example output of the command.
{
"StackEvents": [
...
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-hook-
stack/2c693970-f57e-11eb-a0fb-061a2a83f0b9",
"EventId": "S3Bucket-CREATE_FAILED-2021-08-04T23:47:03.305Z",
"StackName": "my-hook-stack",
"LogicalResourceId": "S3Bucket",
"PhysicalResourceId": "",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:47:03.305000+00:00",
"ResourceStatus": "CREATE_FAILED",
"ResourceStatusReason": "The following hook(s) failed:
[MyCompany::Testing::MyTestHook]",
"ResourceProperties": "{}",
"ClientRequestToken": "Console-CreateStack-abe71ac2-ade4-
a762-0499-8d34d91d6a92"
},
...
]
}
Results: The Hook invocation failed the stack configuration and stopped the resource from
provisioning.
1. To create a stack and pass the Hook validation, update the template so that your resource
uses an encrypted S3 bucket. This example uses the template my-encrypted-bucket-
stack.yml.
AWSTemplateFormatVersion: 2010-09-09
Description: |
This CloudFormation template provisions an encrypted S3 Bucket
Resources:
EncryptedS3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub 'encryptedbucket-${AWS::Region}-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !Ref EncryptionKey
BucketKeyEnabled: true
EncryptionKey:
Type: 'AWS::KMS::Key'
DeletionPolicy: Retain
Properties:
Description: KMS key used to encrypt the resource type artifacts
EnableKeyRotation: true
KeyPolicy:
Version: 2012-10-17
Statement:
- Sid: Enable full access for owning account
Effect: Allow
Principal:
AWS: !Ref 'AWS::AccountId'
Action: 'kms:*'
Resource: '*'
Outputs:
EncryptedBucketName:
Value: !Ref EncryptedS3Bucket
Note
Hooks won't be invoked for skipped resources.
2. Create a stack and specify your template. In this example, the stack name is my-encrypted-
bucket-stack.
{
"StackEvents": [
...
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-
CREATE_COMPLETE-2021-08-04T23:23:20.973Z",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "encryptedbucket-us-west-2-ACCOUNT_ID",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:23:20.973000+00:00",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-
west-2-071617338693\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":
[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm
\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-
CREATE_IN_PROGRESS-2021-08-04T23:22:59.410Z",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "encryptedbucket-us-west-2-ACCOUNT_ID",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:22:59.410000+00:00",
"ResourceStatus": "CREATE_IN_PROGRESS",
"ResourceStatusReason": "Resource creation Initiated",
"ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-
west-2-071617338693\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":
[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm
\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-6516081f-c1f2-4bfe-a0f0-cefa28679994",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:22:58.349000+00:00",
"ResourceStatus": "CREATE_IN_PROGRESS",
"ResourceStatusReason": "Hook invocations complete. Resource creation
initiated",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
...
]
}
Results: CloudFormation successfully created the stack. The Hook's logic verified that the
AWS::S3::Bucket resource contained server-side encryption before provisioning the
resource.
AWSTemplateFormatVersion: 2010-09-09
Description: |
This CloudFormation template provisions an encrypted S3 Bucket
Resources:
EncryptedS3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub 'encryptedbucket-${AWS::Region}-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketKeyEnabled: true
Outputs:
EncryptedBucketName:
Value: !Ref EncryptedS3Bucket
2. Create a stack, and specify your template in the AWS CLI. In the following example, specify the
stack name as my-hook-stack and the template name as aes256-bucket.yml.
3. (Optional) View your stack progress by specifying your stack name. In the following example,
specify the stack name my-hook-stack.
Use the describe-stack-events operation to see the Hook failure while creating the
bucket. The following is an example output of the command.
{
"StackEvents": [
...
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-hook-
stack/2c693970-f57e-11eb-a0fb-061a2a83f0b9",
"EventId": "S3Bucket-CREATE_FAILED-2021-08-04T23:47:03.305Z",
"StackName": "my-hook-stack",
"LogicalResourceId": "S3Bucket",
"PhysicalResourceId": "",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:47:03.305000+00:00",
"ResourceStatus": "CREATE_FAILED",
"ResourceStatusReason": "The following hook(s) failed:
[MyCompany::Testing::MyTestHook]",
"ResourceProperties": "{}",
"ClientRequestToken": "Console-CreateStack-abe71ac2-ade4-
a762-0499-8d34d91d6a92"
},
...
]
}
Results: The Hook invocation failed the stack configuration and stopped the resource from
provisioning. The stack failed due to the S3 bucket encryption configured incorrectly. The Hook
type configuration requires aws:kms while this bucket uses AES256.
1. To create a stack and pass the Hook validation, update the template so that your resource uses
an encrypted S3 bucket. This example uses the template kms-bucket-and-queue.yml.
AWSTemplateFormatVersion: 2010-09-09
Description: |
This CloudFormation template provisions an encrypted S3 Bucket
Resources:
EncryptedS3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub 'encryptedbucket-${AWS::Region}-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !Ref EncryptionKey
BucketKeyEnabled: true
EncryptedQueue:
Type: 'AWS::SQS::Queue'
Properties:
QueueName: 'encryptedqueue-${AWS::Region}-${AWS::AccountId}'
KmsMasterKeyId: !Ref EncryptionKey
EncryptionKey:
Type: 'AWS::KMS::Key'
DeletionPolicy: Retain
Properties:
Description: KMS key used to encrypt the resource type artifacts
EnableKeyRotation: true
KeyPolicy:
Version: 2012-10-17
Statement:
- Sid: Enable full access for owning account
Effect: Allow
Principal:
AWS: !Ref 'AWS::AccountId'
Action: 'kms:*'
Resource: '*'
Outputs:
EncryptedBucketName:
Value: !Ref EncryptedS3Bucket
EncryptedQueueName:
Value: !Ref EncryptedQueue
Note
Hooks won't be invoked for skipped resources.
2. Create a stack and specify your template. In this example, the stack name is my-encrypted-
bucket-stack.
{
"StackEvents": [
...
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-
CREATE_COMPLETE-2021-08-04T23:23:20.973Z",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "encryptedbucket-us-west-2-ACCOUNT_ID",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:23:20.973000+00:00",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-
west-2-071617338693\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":
[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm
\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-
CREATE_IN_PROGRESS-2021-08-04T23:22:59.410Z",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "encryptedbucket-us-west-2-ACCOUNT_ID",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:22:59.410000+00:00",
"ResourceStatus": "CREATE_IN_PROGRESS",
"ResourceStatusReason": "Resource creation Initiated",
"ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-
west-2-071617338693\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":
[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm
\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
{
"StackId": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID:stack/my-
encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
"EventId": "EncryptedS3Bucket-6516081f-c1f2-4bfe-a0f0-cefa28679994",
"StackName": "my-encrypted-bucket-stack",
"LogicalResourceId": "EncryptedS3Bucket",
"PhysicalResourceId": "",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2021-08-04T23:22:58.349000+00:00",
"ResourceStatus": "CREATE_IN_PROGRESS",
"ResourceStatusReason": "Hook invocations complete. Resource creation
initiated",
"ClientRequestToken": "Console-CreateStack-39df35ac-ca00-
b7f6-5661-4e917478d075"
},
...
]
}
Results: CloudFormation successfully created the stack. The Hook's logic verified that the
AWS::S3::Bucket resource contained server-side encryption before provisioning the
resource.
To update a Hook, submit your revisions to the CloudFormation registry through the
CloudFormation CLI submit operation.
$ cfn submit
To specify the default version of your Hook in your account, use the set-type-default-
version command and specify the type, type name, and version ID.
TargetStacks set to NONE turns the Hook off in your account, so it doesn't apply to stack
operations. Use the set-type-configuration operation and specify TargetStacks as NONE to
deactivate a Hook.
The following example specifies the AWS Region and the Amazon Resource Name (ARN) of the
Hook that's being deactivated.
Note
Before deregistering the Hook, you must individually deregister all previous active versions
of that extension. For more information, see DeregisterType.
To deregister a Hook, use the deregister-type operation and specify your Hook ARN.
2. Test your Hook to make sure it meets all necessary requirements for being published in the
CloudFormation registry.
3. Publish your Hook to the CloudFormation registry.
Note
Before you publish any extension in a given Region, you must first register as an
extension publisher in that Region. To do this in multiple Regions simultaneously, see
Publishing your extension in multiple Regions using AWS CloudFormation StackSets.
After you've developed and registered your Hook, you can make it publicly available to general
CloudFormation users by publishing it to the CloudFormation registry, as a third-party public
extension.
Public third-party Hooks enable you to offer CloudFormation users to proactively inspect the
configuration of AWS resources before provisioning. As with private Hooks, public Hooks are
treated the same as any Hook published by AWS within CloudFormation.
Hooks published to the registry are visible by all CloudFormation users in the AWS Regions in
which they're published. Users can then activate your extension in their account, which makes
it available for use in their templates. For more information, see Using public extensions in
CloudFormation in the CloudFormation User Guide.
Each handler and target is tested twice. Once for SUCCESS and once for FAILED.
CloudFormation offers two ways for you to specify the input data for it to use when performing
contract tests:
• Overrides file
Using an overrides file provides a light-weight way of specifying input data for certain specific
properties for the CloudFormation to use during preCreate, preUpdate and preDelete
operations testing.
• Input files
You can also use multiple input files to specify contract test input data if:
• You want or need to specify different input data for create, update, and delete operations, or
invalid data with which to test.
• You want to specify multiple different input data sets.
The following is an example of Amazon S3 Hook's input data using the overrides file.
{
"CREATE_PRE_PROVISION": {
"AWS::S3::Bucket": {
"resourceProperties": {
"/BucketName": "encryptedbucket-us-west-2-contractor",
"/BucketEncryption/ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
}
},
"AWS::SQS::Queue": {
"resourceProperties": {
"/QueueName": "MyQueueContract",
"/KmsMasterKeyId": "hellocontract"
}
}
},
"UPDATE_PRE_PROVISION": {
"AWS::S3::Bucket": {
"resourceProperties": {
"/BucketName": "encryptedbucket-us-west-2-contractor",
"/BucketEncryption/ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
},
"previousResourceProperties": {
"/BucketName": "encryptedbucket-us-west-2-contractor",
"/BucketEncryption/ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
}
}
},
"INVALID_UPDATE_PRE_PROVISION": {
"AWS::S3::Bucket": {
"resourceProperties": {
"/BucketName": "encryptedbucket-us-west-2-contractor",
"/BucketEncryption/ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "AES256"
}
}
]
},
"previousResourceProperties": {
"/BucketName": "encryptedbucket-us-west-2-contractor",
"/BucketEncryption/ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
}
}
},
"INVALID": {
"AWS::SQS::Queue": {
"resourceProperties": {
"/QueueName": "MyQueueContract",
"/KmsMasterKeyId": "KMS-KEY-ARN"
}
}
}
}
Use input files to specify different kinds of input data for the CloudFormation to use: preCreate
input, preUpdate input, and invalid input. Each kind of data is specified in a separate file. You can
also specify multiple sets of input data for contract tests.
To specify input files for the CloudFormation to use in contract testing, add an inputs folder to
the root directory of your Hooks project. Then add your input files.
Specify which kind of input data a file contains by using the following naming conventions, where
n is an integer:
• inputs_n_pre_create.json: Use files with preCreate handlers for specifying inputs for
creating the resource.
• inputs_n_pre_update.json: Use files with preUpdate handlers for specifying inputs for
updating the resource.
• inputs_n_pre_delete.json: Use files with preDelete handlers for specifying inputs for
deleting the resource.
• inputs_n_invalid.json: For specifying invalid inputs to test.
To specify multiple sets of input data for contract tests, increment the integer in the
file names to order your input data sets. For example, your first set of input files should
be named inputs_1_pre_create.json, inputs_1_pre_update.json, and
inputs_1_pre_invalid.json. Your next set would be named inputs_2_pre_create.json,
inputs_2_pre_update.json, and inputs_2_pre_invalid.json, and so on.
Each input file is a JSON file containing only the resource properties to be used in testing.
The following is an example directory for inputs for Amazon S3 specifying input data using input
files.
inputs_1_pre_create.json
{
"AWS::S3::Bucket": {
"resourceProperties": {
"AccessControl": "BucketOwnerFullControl",
"AnalyticsConfigurations": [],
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
}
},
"AWS::SQS::Queue": {
"resourceProperties": {
"QueueName": "MyQueue",
"KmsMasterKeyId": "KMS-KEY-ARN"
}
}
}
inputs_1_pre_update.json
{
"AWS::S3::Bucket": {
"resourceProperties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
},
"previousResourceProperties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
}
}
}
inputs_1_invalid.json
{
"AWS::S3::Bucket": {
"resourceProperties": {
"AccessControl": "BucketOwnerFullControl",
"AnalyticsConfigurations": [],
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
}
},
"AWS::SQS::Queue": {
"resourceProperties": {
"NotValid": "The property of this resource is not valid."
}
}
}
inputs_1_invalid_pre_update.json
{
"AWS::S3::Bucket": {
"resourceProperties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "AES256"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
},
"previousResourceProperties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"BucketKeyEnabled": true,
"ServerSideEncryptionByDefault": {
"KMSMasterKeyID": "KMS-KEY-ARN",
"SSEAlgorithm": "aws:kms"
}
}
]
},
"BucketName": "encryptedbucket-us-west-2"
}
}
}
For more information, see Testing your public extension before publishing.
131