A curated drawer of AWS reference implementations and patterns I keep coming back to, grouped by service.
ACM (Certificates)
DNS-validated certificate automation via CloudFormation custom resources.
API Gateway
Service integrations that skip Lambda entirely.
Lambda
Auth / Cognito
WebSockets
S3
DynamoDB
Elasticsearch / OpenSearch
Indexing DynamoDB streams into a search cluster.
IoT
Conditional DependsOn via WaitConditionHandle
A pattern for conditionally depending on a resource (here, only create a Route 53 hosted zone if no hostedZoneId is supplied) using a WaitCondition switched by Fn::If. Bundles a DNS-validated ACM cert custom resource.
service: ga-proxy
plugins:
- serverless-pseudo-parameters
- serverless-manifest-plugin
custom:
# default stage 'prod'
stage: ${opt:stage, 'prod'}
# default region 'us-west-1'
region: ${opt:region, 'us-west-1'}
# Primary host domain
baseDomain: example.com
domainsByStage:
prod: api.${self:custom.baseDomain}
staging: api-staging.${self:custom.baseDomain}
dev: api-dev.${self:custom.baseDomain}
# Resolved domain settings
apiDomain: ${self:custom.domainsByStage.${self:custom.stage}}
# Base mapping. domain.com/${apiBasePath}/
apiBasePath: 'mypath'
# Resolved full service url
apiServiceUrl: 'https://${self:custom.apiDomain}/${self:custom.apiBasePath}'
# Route53 hosted zone (leave empty to have the stack create one)
hostedZoneId: ''
provider:
name: aws
stage: ${self:custom.stage}
region: ${self:custom.region}
runtime: nodejs12.x
httpApi:
cors: true
functions:
proxy:
handler: handler.proxy
events:
- httpApi:
method: GET
path: /r/collect
- httpApi:
method: GET
path: /collect
resources:
Conditions:
## Create HostedZone if hostedZoneId empty
CreateZone: { "Fn::Equals" : ["${self:custom.hostedZoneId, ''}", ""] }
Resources:
## Hack for conditional depends clause
WaitOnHostedZone:
Condition: CreateZone
DependsOn: HostedZone
Type: "AWS::CloudFormation::WaitConditionHandle"
EmptyWait:
Type: "AWS::CloudFormation::WaitConditionHandle"
CustomWaitCondition:
Type: "AWS::CloudFormation::WaitCondition"
Properties:
Handle: { "Fn::If": [CreateZone, { Ref: WaitOnHostedZone }, { Ref: EmptyWait }] }
Timeout: "1"
Count: 0
HostedZone:
Type: 'AWS::Route53::HostedZone'
Condition: CreateZone
Properties:
HostedZoneConfig:
Comment: 'Hosted zone for ${self:custom.baseDomain}'
Name: '${self:custom.baseDomain}'
Domain:
Type: 'AWS::ApiGatewayV2::DomainName'
DependsOn:
- SSLCertificate
Properties:
DomainName: ${self:custom.apiDomain}
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: { Ref: SSLCertificate }
ApiMapping:
Type: 'AWS::ApiGatewayV2::ApiMapping'
DependsOn:
- HttpApi
- HttpApiStage
- Domain
Properties:
DomainName: ${self:custom.apiDomain}
ApiId: { Ref: HttpApi }
ApiMappingKey: ${self:custom.apiBasePath}
Stage: { Ref: HttpApiStage }
DnsRecord:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: '${self:custom.baseDomain}.'
RecordSets:
- Name: ${self:custom.apiDomain}
Type: A
AliasTarget:
HostedZoneId: { Fn::GetAtt: [ Domain, RegionalHostedZoneId ] }
DNSName: { Fn::GetAtt: [ Domain, RegionalDomainName ] }
SSLCertificate:
Type: 'Custom::DNSCertificate'
DependsOn:
- CustomWaitCondition
- CustomAcmCertificateLambdaExecutionRole
- CustomAcmCertificateLambda
Properties:
DomainName: '${self:custom.baseDomain}'
SubjectAlternativeNames:
- '*.${self:custom.baseDomain}'
ValidationMethod: DNS
Region: ${self:custom.region}
DomainValidationOptions:
- DomainName: '${self:custom.baseDomain}'
HostedZoneId: '${self:custom.hostedZoneId}'
ServiceToken: { Fn::GetAtt: [ CustomAcmCertificateLambda, Arn ] }
CustomAcmCertificateLambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
Policies:
- PolicyDocument:
Statement:
- Action:
- acm:AddTagsToCertificate
- acm:DeleteCertificate
- acm:DescribeCertificate
- acm:RemoveTagsFromCertificate
Effect: Allow
Resource:
- 'arn:aws:acm:*:${AWS::AccountId}:certificate/*'
- Action:
- acm:RequestCertificate
- acm:ListTagsForCertificate
- acm:ListCertificates
Effect: Allow
Resource:
- '*'
- Action:
- route53:ChangeResourceRecordSets
Effect: Allow
Resource:
- arn:aws:route53:::hostedzone/*
Version: '2012-10-17'
PolicyName: 'CustomAcmCertificateLambdaExecutionPolicy-${self:service}'
CustomAcmCertificateLambda:
Type: 'AWS::Lambda::Function'
Metadata:
Source: https://github.com/dflook/cloudformation-dns-certificate
Version: 1.7.2
Properties:
Description: Cloudformation custom resource for DNS validated certificates
Handler: index.handler
Role: { Fn::GetAtt: [ CustomAcmCertificateLambdaExecutionRole, Arn ] }
Runtime: python3.6
Timeout: 900
Code:
# Inlined handler source omitted; see github.com/dflook/cloudformation-dns-certificate
ZipFile: "..."
Outputs:
DomainName:
Description: API Gateway custom domain
Value: { Ref: Domain }
ServiceUrl:
Description: Custom API Gateway service URL
Value: ${self:custom.apiServiceUrl}
DomainMapping:
Description: Apigateway mapping
Value: { Ref: ApiMapping }
CertificateArn:
Description: ARN of custom domain cert
Value: { Ref: SSLCertificate }