Skip to content

Instantly share code, notes, and snippets.

@bshelling
Created June 20, 2023 10:17
Show Gist options
  • Save bshelling/ddc51269da2c0cdc6e141bdfb1602d61 to your computer and use it in GitHub Desktop.
Save bshelling/ddc51269da2c0cdc6e141bdfb1602d61 to your computer and use it in GitHub Desktop.
Cloudformation Template for a email subscription backend
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs18.x
Description: "Full stack email subscription application for kiosks architected by B.Shelling"
Parameters:
ROOTDOMAIN:
Type: "String"
Default: "kiosk.com"
CLIENTDOMAIN:
Type: "String"
Default: "kiosk.site.com"
APIDOMAIN:
Type: "String"
Default: "kioskapi.site.com"
RECAPSECRET:
Type: "String"
Default: ""
STAGENAME:
Type: "String"
Default: "dev"
BUCKETNAME:
Type: "String"
Default: "kiosk-test-bshelling"
TABLENAME:
Type: "String"
Default: "kioskTable"
ORIGINURL:
Type: "String"
Default: "*"
Resources:
# Bucket creation for web assets
KioskSiteBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BUCKETNAME
# Hosted Zone for application's domain
KioskSiteHostedZone:
Type: AWS::Route53::HostedZone
Properties:
HostedZoneConfig:
Comment: 'Hosted zone for kiosk application'
Name: !Ref ROOTDOMAIN
# Auto-generated certificate for domain - *note DomainValidationOptions property is key to the DNS validation being automated
KioskSiteCert:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Sub '*.${ROOTDOMAIN}'
DomainValidationOptions:
- DomainName: !Sub '*.${ROOTDOMAIN}'
HostedZoneId: !Ref KioskSiteHostedZone
ValidationMethod: DNS
# IAM Policy
KioskExecutionPolicy:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: 'KioskExecutionRole'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 'lambda:InvokeFunction'
Resource: '*'
# Cloudfront distribution for web assets hosted in s3
KioskSiteDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- !Ref CLIENTDOMAIN
CNAMEs:
- !Ref CLIENTDOMAIN
DefaultRootObject: index.html
ViewerCertificate:
AcmCertificateArn: !Ref KioskSiteCert
SslSupportMethod: sni-only
Enabled: True
PriceClass: PriceClass_100
DefaultCacheBehavior:
ForwardedValues:
QueryString: false
AllowedMethods:
- GET
- HEAD
ViewerProtocolPolicy: redirect-to-https
TargetOriginId: KioskSiteOrigin
Origins:
- Id: KioskSiteOrigin
DomainName: !Sub '${KioskSiteBucket}.s3.${AWS::Region}.amazonaws.com'
OriginAccessControlId: !Ref KioskSiteOAC
S3OriginConfig:
OriginAccessIdentity: ""
# Api for function execution
KioskRestApi:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref STAGENAME
DefinitionBody:
openapi: 3.0.0
info:
version: 1.0
title: Kiosk API
servers:
- url: !Sub 'https://${APIDOMAIN}'
paths:
/send:
options:
description: Options method
responses:
'200':
description: 'Cors configuration'
headers:
Access-Control-Allow-Origin:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Headers:
schema:
type: string
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: OPTIONS
credentials: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${KioskExecutionPolicy}'
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${KioskFunction}/invocations'
responses:
default:
statusCode: 200
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization'"
method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: !Sub "'https://${KioskSiteRecordSet}'"
post:
description: Validate recaptcha and send email subscription
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: POST
credentials: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${KioskExecutionPolicy}'
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${KioskFunction}/invocations'
responses:
default:
statusCode: 200
'500':
statusCode: 500
responseTemplates:
application/json: '{"message": "Something is messed up "}'
# Custom api domain name
KioskApiDomainName:
Type: AWS::ApiGatewayV2::DomainName
Properties:
DomainName: !Ref APIDOMAIN
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: !Ref KioskSiteCert
KioskApiMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: !Ref KioskRestApi
DomainName: !Ref APIDOMAIN
Stage: !Ref STAGENAME
# Record Set for domain - alias target is the application's cloudfront distribution
KioskSiteRecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt KioskSiteDistribution.DomainName
EvaluateTargetHealth: true
HostedZoneId: Z2FDTNDATAQYW2
Name: !Ref CLIENTDOMAIN
HostedZoneId: !Ref KioskSiteHostedZone
Type: A
KioskApiRecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt KioskApiDomainName.RegionalDomainName
EvaluateTargetHealth: true
HostedZoneId: !GetAtt KioskApiDomainName.RegionalHostedZoneId
Name: !Ref APIDOMAIN
HostedZoneId: !Ref KioskSiteHostedZone
Type: A
# Function Role to allow write permissions to db table
KioskDbTableRole:
Type: AWS::IAM::Role
Properties:
Description: 'Allow db table writes from send function'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: dbpolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'dynamodb:PutItem'
Resource: !GetAtt KioskSiteDbTable.Arn
# Send function for validating recaptcha and dynamodb storage
KioskFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.send
CodeUri: src/functions/send/
Role: !GetAtt KioskDbTableRole.Arn
Environment:
Variables:
RECAPSECRET: !Ref RECAPSECRET
ORIGINURL: !Ref ORIGINURL
TABLENAME: !Ref TABLENAME
Events:
SendKiosk:
Type: Api
Properties:
Path: /send
Method: post
RestApiId: !Ref KioskRestApi
Metadata:
BuildMethod: makefile
# Bucket policy to only allow traffic from distribution - explicit
KioskSiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref KioskSiteBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:GetObject'
Effect: Allow
Principal:
Service: 'cloudfront.amazonaws.com'
Condition:
StringEquals:
"AWS:SourceArn": !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${KioskSiteDistribution}'
Resource: !Sub 'arn:aws:s3:::${KioskSiteBucket}/*'
KioskSiteCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 3600
MaxTTL: 7200
MinTTL: 3600
Name: KioskDefaultCachePolicy
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingGzip: true
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
KioskSiteOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Description: !Sub 'Kiosk to S3 OAC'
Name: !Sub 'KioskSiteOAC'
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
# Kiosk Database for storing form submissions
KioskSiteDbTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: lastname
AttributeType: S
- AttributeName: email
AttributeType: S
BillingMode: PAY_PER_REQUEST
TableClass: STANDARD
TableName: !Ref TABLENAME
KeySchema:
- AttributeName: email
KeyType: HASH
- AttributeName: lastname
KeyType: RANGE
# Outputs of all key resources
# If an external domain is used apply the nameservers from the output to the external domain registrar
Outputs:
KioskSiteApiUrl:
Description: 'Kiosk API Url'
Value: !Sub '${KioskRestApi}.execute-api.${AWS::Region}.amazonaws.com/${STAGENAME}'
KioskSiteExecution:
Description: 'Kiosk API Url'
Value: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${KioskExecutionPolicy}'
KioskRecordSet:
Description: ''
Value: !Sub 'https://${KioskApiRecordSet}'
KioskSiteCertArn:
Description: 'Kiosk Site Cert Arn'
Value: !Sub '${KioskSiteCert}'
KioskSiteNS:
Description: 'Nameservers for kiosk applicaiton hosted zone'
Value: !Join
- ','
- !GetAtt KioskSiteHostedZone.NameServers
KioskSiteDomainName:
Description: 'Kiosk Distribution Domain Name'
Value: !GetAtt KioskSiteDistribution.DomainName
KioskSiteContentBucket:
Description: 'Kiosk Asset Bucket Name'
Value: !GetAtt KioskSiteBucket.DomainName
KioskSiteDatabase:
Description: 'Kiosk Data Table Name'
Value: !Ref KioskSiteDbTable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment