This project provides a generic Custom::Resource
for CloudFormation. Almost anything that can be done through the AWS API can be achieved through this custom resource. If CloudFormation has again not kept up with latest service updates or is missing some crucial configuration options, this might be very useful.
Here are a few examples of what additional resources you will be able to manage through CloudFormation with this little helper.
run make test
. This will create a stack with some of the custom resources described below. If the stack was created successfully, the stack will be deleted.
The following snippet is required in each CloudFormation stack that wants to make use of this helper.
Download the customresource.template.json
file from the releases page and place it on an S3 bucket in your account.
CustomResource:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-templates/customresource/v1.3.2/customresource.template.json
CustomResourcePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Roles: [!GetAtt CustomResource.Outputs.Role]
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Resource: '*'
Action:
# add actions that the custom resource needs to have access to
- ses:VerifyDomainIdentity
Resources can be created in different accounts and regions. To create a resource in a different account, provide a RoleArn
property. The role must exist in the target account and must list the CustomResourceRole
ARN as a principal in the resource policy. To create resources in a different region, simply provide the Region
property.
This example registers a Cognito User Pool Client in a different account and region.
UserPoolClient:
Type: Custom::CognitoUserPoolClient
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: CognitoIdentityServiceProvider
RoleArn: arn:aws:iam::123412341234:role/CustomResourceDelegate
Region: us-east-2
Create:
Action: createUserPoolClient
PhysicalResourceIdQuery: UserPoolClient.ClientId
Parameters:
UserPoolId: !Ref UserPoolId
ClientName: !Sub ${AWS::AccountId}-${AWS::Region}-${AWS::StackName}
GenerateSecret: true
Delete:
Action: deleteUserPoolClient
Parameters:
ClientId: ${PhysicalResourceId}
UserPoolId: !Ref UserPoolId
Bucket:
Type: AWS::S3::Bucket
BucketInventory:
Type: Custom::BucketInventory
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: S3
PhysicalResourceId: !Sub ${AWS::StackName}-BucketInventory-${!Random}
Create:
Action: putBucketInventoryConfiguration
Parameters:
Bucket: !Ref Bucket
Id: ${PhysicalResourceId}
InventoryConfiguration:
Id: ${PhysicalResourceId}
IncludedObjectVersions: Current
OptionalFields: [Size, LastModifiedDate]
IsEnabled: true
Schedule:
Frequency: Daily
Filter:
Prefix: abcdef/
Destination:
S3BucketDestination:
Bucket: !GetAtt Bucket.Arn
Format: CSV
AccountId: !Ref AWS::AccountId
Prefix: Inventory
Delete:
Action: deleteBucketInventoryConfiguration
Parameters:
Bucket: !Ref Bucket
Id: ${PhysicalResourceId}
IgnoreErrors: true
CustomResourcePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: RDS
Roles: [!Ref CustomResourceRole]
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Resource: !GetAtt Role.Arn
Action: iam:PassRole
- Effect: Allow
Resource: !Sub arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster:${DBCluster}
Action:
- rds:ModifyDBCluster
- rds:AddRoleToDBCluster
- rds:RemoveRoleFromDBCluster
EnableIAM:
Type: Custom::RDSIAMAuthentication
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: RDS
Create:
Action: modifyDBCluster
Parameters:
DBClusterIdentifier: !Ref DBCluster
EnableIAMDatabaseAuthentication: true
ApplyImmediately: true
RoleAttachment:
Type: Custom::RDSRoleAttachment
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: RDS
Parameters:
DBClusterIdentifier: !Ref DBCluster
RoleArn: !GetAtt Role.Arn
Create:
Action: addRoleToDBCluster
Delete:
Action: removeRoleFromDBCluster
HmacSecret:
Type: Custom::KMSRandomBytes
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: KMS
Create:
Action: generateRandom
Parameters:
NumberOfBytes: 36
# access random base64-encoded bytes with !GetAtt HmacSecret.Plaintext
# Statement:
# Effect: Allow
# Action: apigateway:GET
# Resource: !Sub arn:aws:apigateway:${AWS::Region}:*:/domainnames/*
RegionalEndpoint:
Type: Custom::APIGatewayRegionalEndpoint
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: APIGateway
Create:
Action: getDomainName
PhysicalResourceIdQuery: regionalDomainName
Parameters:
domainName: !Ref DomainName
# !Ref RegionalEndpoint contains the regional endpoint's hostname
InvitationId:
Type: Custom::GuardDutyInvitationId
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: GuardDuty
PhysicalResourceIdQuery: Invitations[0].InvitationId
Create:
Action: listInvitations
# Access InvitationId via !Ref InvitationId
UserPoolDomain:
Type: Custom::CognitoUserPoolDomain
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: CognitoIdentityServiceProvider
PhysicalResourceId: !Ref DomainName
Parameters:
UserPoolId: !Ref UserPool
Domain: ${PhysicalResourceId}
Create:
Action: createUserPoolDomain
Update:
# Updates are not fully supported. This will delete the previous domain name.
# You will have to create the new domain name manually through the API or console.
Action: deleteUserPoolDomain
IgnoreErrors: true
Delete:
Action: deleteUserPoolDomain
IgnoreErrors: true
AWS::Cognito::UserPoolClient
doesn't allow defining some of the settings for a user pool, such as the callback URLs or the allowed OAuth flows and scopes. A regular AWS::Cognito::UserPoolClient
resource needs to exist already.
UserPoolClientSettings:
Type: Custom::CognitoUserPoolClientSettings
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: CognitoIdentityServiceProvider
Create:
Action: updateUserPoolClient
Parameters:
UserPoolId: !Ref UserPool
ClientId: !Ref UserPoolClient
AllowedOAuthFlows: [code, implicit]
AllowedOAuthScopes: [openid]
SupportedIdentityProviders: [COGNITO]
AllowedOAuthFlowsUserPoolClient: true
CallbackURLs:
- !Sub https://${HostedZone}/callback
Although AWS::Cognito::UserPoolClient
in theory returns a ClientSecret
attribute, it only return a string that states that retrieving client secrets through CloudFormation are not supported at this time.
UserPoolClientSecret:
Type: Custom::CognitoClientSecret
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: CognitoIdentityServiceProvider
Create:
Action: describeUserPoolClient
Attributes: UserPoolClient
Parameters:
UserPoolId: !Ref UserPool
ClientId: !Ref UserPoolClient
# The client secret will be accessible as !GetAtt UserPoolClientSecret.ClientSecret
UserPoolUICustomization:
Type: Custom::CognitoUICustomization
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: CognitoIdentityServiceProvider
PhysicalResourceId: ALL
Create:
Action: setUICustomization
Parameters:
UserPoolId: !Ref UserPool
ClientId: ALL
CSS: |
.logo-customizable { max-width: 50%; }
.banner-customizable { background-color: white; }
.socialButton-customizable { background-color: white; border: 1px solid #dddddd }
Delete:
Action: setUICustomization
IgnoreErrors: true
Parameters:
UserPoolId: !Ref UserPool
ClientId: ALL
CSS: ''
ImageFile: 'null'
DomainVerification:
Type: Custom::SESDomainVerification
DependsOn: CustomResourcePolicy # Important: Otherwise you will run into Access Denied exception
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: SES
Parameters:
Domain: !Ref Domain
Create:
Action: verifyDomainIdentity
DomainVerificationRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref HostedZone
Name: !Sub _amazonses.${Domain}
Type: TXT
TTL: 300
ResourceRecords:
- !Sub '"${DomainVerification.VerificationToken}"'
DomainRuleSet:
Type: Custom::SESReceiptRuleSet
DependsOn: CustomResourcePolicy
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: SES
Parameters:
RuleSetName: !Ref Domain
Create:
Action: createReceiptRuleSet
Delete:
Action: deleteReceiptRuleSet
ActivateDomainRuleSet:
Type: Custom::SESActivateDomainRuleSet
DependsOn: DomainRuleSet
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: SES
Create:
Action: setActiveReceiptRuleSet
Parameters:
RuleSetName: !Ref Domain
Delete:
Action: setActiveReceiptRuleSet
Parameters:
RuleSetName: null
DomainReceiptRule:
Type: Custom::SESReceiptRule
DependsOn: DomainRuleSet
Properties:
ServiceToken: !GetAtt CustomResource.Outputs.ServiceToken
Service: SES
Parameters:
RuleSetName: !Ref Domain
Rule:
Name: default
Enabled: true
TlsPolicy: Optional
ScanEnabled: true
Recipients: [!Ref Domain]
Actions:
- SNSAction:
TopicArn: !Ref Topic
Encoding: UTF-8
Create:
Action: createReceiptRule
Update:
Action: updateReceiptRule
Delete:
Action: deleteReceiptRule
Parameters:
RuleSetName: !Ref Domain
RuleName: default