-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtemplate.yml
348 lines (333 loc) · 13.1 KB
/
template.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-authoring.html
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >
Mailing list system providing address validation and unsubscribe URIs.
Parameters:
ApiDomainName:
Type: String
ApiMappingKey:
Type: String
EmailDomainName:
Type: String
EmailSiteTitle:
Type: String
SenderName:
Type: String
SenderUserName:
Type: String
UnsubscribeUserName:
Type: String
UnsubscribeFormPath:
Type: String
ReceiptRuleSetName:
Type: String
SubscribersTableName:
Type: String
MaxBulkSendCapacity:
Type: Number
MinValue: "0"
MaxValue: "1"
Default: "0.8"
Description: Portion of quota to use for bulk sending, in range [0.0,1.0]
InvalidRequestPath:
Type: String
AlreadySubscribedPath:
Type: String
VerifyLinkSentPath:
Type: String
SubscribedPath:
Type: String
NotSubscribedPath:
Type: String
UnsubscribedPath:
Type: String
Resources:
Function:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html
Type: AWS::Serverless::Function
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html#examples-makefile-identifier
# https://docs.aws.amazon.com/lambda/latest/dg/golang-package.html
# https://github.com/aws-samples/sessions-with-aws-sam/tree/master/go-al2
Metadata:
BuildMethod: makefile
DependsOn: FunctionLogs
Properties:
Handler: bootstrap
# https://aws.amazon.com/blogs/compute/migrating-aws-lambda-functions-from-the-go1-x-runtime-to-the-custom-runtime-on-amazon-linux-2/
Runtime: provided.al2
Architectures:
- "arm64"
Description: Coordinates between the API Gateway, DynamoDB, SES, and SNS
Timeout: 300
FunctionName: !Sub "${AWS::StackName}-function"
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-template-list.html
Policies:
- AWSLambdaBasicExecutionRole
- Statement:
Sid: DynamoDbPolicy
Effect: Allow
Action:
- "dynamoDb:GetItem"
- "dynamoDb:PutItem"
- "dynamoDb:DeleteItem"
- "dynamoDb:Scan"
Resource:
- !Sub "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SubscribersTableName}"
- !Sub "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SubscribersTableName}/index/*"
- Statement:
Sid: SESSendEmailPolicy
Effect: Allow
Action:
- "ses:SendRawEmail"
- "ses:SendBounce"
Resource:
- !Sub "arn:${AWS::Partition}:ses:${AWS::Region}:${AWS::AccountId}:identity/${EmailDomainName}"
- !Sub "arn:${AWS::Partition}:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/${AWS::StackName}"
- Statement:
Sid: SESGeneralPolicy
Effect: Allow
Action:
- "ses:GetAccount"
- "ses:GetSuppressedDestination"
- "ses:PutSuppressedDestination"
- "ses:DeleteSuppressedDestination"
Resource: "*"
Tracing: Active
Environment:
Variables:
API_DOMAIN_NAME: !Ref ApiDomainName
API_MAPPING_KEY: !Ref ApiMappingKey
EMAIL_DOMAIN_NAME: !Ref EmailDomainName
EMAIL_SITE_TITLE: !Ref EmailSiteTitle
SENDER_NAME: !Ref SenderName
SENDER_USER_NAME: !Ref SenderUserName
UNSUBSCRIBE_USER_NAME: !Ref UnsubscribeUserName
UNSUBSCRIBE_FORM_PATH: !Ref UnsubscribeFormPath
SUBSCRIBERS_TABLE_NAME: !Ref SubscribersTableName
CONFIGURATION_SET: !Ref SendingConfigurationSet
MAX_BULK_SEND_CAPACITY: !Ref MaxBulkSendCapacity
INVALID_REQUEST_PATH: !Ref InvalidRequestPath
ALREADY_SUBSCRIBED_PATH: !Ref AlreadySubscribedPath
VERIFY_LINK_SENT_PATH: !Ref VerifyLinkSentPath
SUBSCRIBED_PATH: !Ref SubscribedPath
NOT_SUBSCRIBED_PATH: !Ref NotSubscribedPath
UNSUBSCRIBED_PATH: !Ref UnsubscribedPath
Events:
Subscribe:
Type: Api
Properties:
RestApiId: !Ref Api
Path: /subscribe
Method: POST
Verify:
Type: Api
Properties:
RestApiId: !Ref Api
Path: /verify/{email}/{uid}
Method: GET
UnsubscribeGet:
Type: Api
Properties:
RestApiId: !Ref Api
Path: /unsubscribe/{email}/{uid}
Method: GET
UnsubscribePost:
Type: Api
Properties:
RestApiId: !Ref Api
Path: /unsubscribe/{email}/{uid}
Method: POST
DeliveryNotification:
Type: SNS
Properties:
Topic: !Ref DeliveryNotificationsTopic
ApiMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: !Ref Api
DomainName: !Ref ApiDomainName
ApiMappingKey: !Ref ApiMappingKey
# https://github.com/aws/serverless-application-model/issues/192#issuecomment-520893111
Stage: !Ref Api.Stage
Api:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html
Type: AWS::Serverless::Api
DependsOn: ApiAccessLogs
Properties:
# Block automatic creation of the "Stage" stage per:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-openapiversion
OpenApiVersion: "3.0.1"
StageName: !Ref AWS::StackName
AccessLogSetting:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-stage-accesslogsettings.html
# https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging.html
# https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging-variables.html
DestinationArn: !GetAtt ApiAccessLogs.Arn
Format: '"$context.identity.sourceIp - - [$context.requestTime] "$context.httpMethod $context.path $context.protocol" $context.status $context.responseLength $context.requestId"'
MethodSettings:
# https://stackoverflow.com/a/70423772
# https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html
- HttpMethod: "*"
ResourcePath: "/*"
ThrottlingRateLimit: 10
ThrottlingBurstLimit: 100
FunctionLogs:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays
# https://awslabs.github.io/serverless-rules/rules/lambda/log_retention/
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${AWS::StackName}-function"
RetentionInDays: 14
ApiAccessLogs:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "${AWS::StackName}-api"
RetentionInDays: 14
WebAcl:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub "${AWS::StackName}-api-acl"
Description: "Protect the /subscribe form from spam bots"
DefaultAction:
Allow: {}
Scope: REGIONAL
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${AWS::StackName}-api-acl"
SampledRequestsEnabled: true
TokenDomains:
- !Ref EmailDomainName
Rules:
- Name: "subscribe"
Priority: 0
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${AWS::StackName}-api-acl-subscribe"
SampledRequestsEnabled: true
Statement:
ByteMatchStatement:
FieldToMatch:
UriPath: {}
SearchString: !Sub "/${ApiMappingKey}/subscribe"
PositionalConstraint: STARTS_WITH
TextTransformations:
- Priority: 0
Type: URL_DECODE
- Priority: 1
Type: NORMALIZE_PATH
Action:
Captcha: {}
CaptchaConfig:
ImmunityTimeProperty:
ImmunityTime: 60
WebAclAssociation:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webaclassociation.html
Type: AWS::WAFv2::WebACLAssociation
Properties:
# https://docs.aws.amazon.com/apigateway/latest/developerguide/arn-format-reference.html
ResourceArn: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:${AWS::AccountId}:/restapis/${Api}/stages/${AWS::StackName}"
WebACLArn: !GetAtt WebAcl.Arn
ReceiptRuleSetPermission:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt Function.Arn
Principal: "ses.amazonaws.com"
SourceArn: !Sub "arn:${AWS::Partition}:ses:${AWS::Region}:${AWS::AccountId}:receipt-rule-set/${ReceiptRuleSetName}:receipt-rule/${UnsubscribeUserName}"
UnsubscribeReceiptRule:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-receiptrule.html
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-receiptruleset.html
Type: AWS::SES::ReceiptRule
Properties:
RuleSetName: !Ref ReceiptRuleSetName
Rule:
Name: !Ref UnsubscribeUserName
Enabled: true
TlsPolicy: Require
ScanEnabled: true
Recipients:
- !Sub "${UnsubscribeUserName}@${EmailDomainName}"
Actions:
- LambdaAction:
FunctionArn: !GetAtt Function.Arn
DependsOn: ReceiptRuleSetPermission
DeliveryNotificationsTopic:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-topic.html
Type: AWS::SNS::Topic
Properties:
TopicName: !Ref AWS::StackName
DeliveryNotificationsTopicPolicy:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-policy.html
Type: AWS::SNS::TopicPolicy
Properties:
Topics:
- !Ref DeliveryNotificationsTopic
PolicyDocument:
# Note that this is for an event destination for event publishing:
# - https://docs.aws.amazon.com/ses/latest/dg/event-publishing-add-event-destination-sns.html
#
# as opposed to notifications for a verified identity:
# - https://docs.aws.amazon.com/ses/latest/dg/configure-sns-notifications.html
#
# It's subtle. It's possible to have one topic for multiple
# deployments using the verified identity notifications. For this
# application, however, we set up different topics and destinations
# for each deployment. This keeps each deployment's notifications
# encapsulated from one another.
Version: "2012-10-17"
Id: !Sub "${AWS::StackName}-notification-policy"
Statement:
- Effect: Allow
Principal:
Service: ses.amazonaws.com
Action: "sns:Publish"
Resource: !Ref DeliveryNotificationsTopic
Condition:
ArnEquals:
"AWS:SourceArn": !Sub "arn:${AWS::Partition}:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/${SendingConfigurationSet}"
SendingConfigurationSet:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-configurationset.html
Type: AWS::SES::ConfigurationSet
Properties:
Name: !Ref AWS::StackName
DeliveryOptions:
TlsPolicy: REQUIRE
SendingOptions:
SendingEnabled: true
SuppressionOptions:
SuppressedReasons:
- COMPLAINT
- BOUNCE
SendingConfigurationSetDestination:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-configurationseteventdestination.html
Type: AWS::SES::ConfigurationSetEventDestination
Properties:
ConfigurationSetName: !Ref SendingConfigurationSet
EventDestination:
Name: !Sub "${AWS::StackName}-delivery-notifications"
Enabled: true
MatchingEventTypes:
- send
- delivery
- reject
- bounce
- complaint
SnsDestination:
TopicARN: !Ref DeliveryNotificationsTopic
Outputs:
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
ApiRootUrl:
Description: "Root URL for the EListMan API Gateway endpoint"
Value: !Sub "https://${ApiDomainName}/${ApiMappingKey}"
EListManFunctionArn:
Description: "EListMan Lambda function ARN"
Value: !GetAtt Function.Arn
IamRole:
Description: "Implicit IAM Role created for EListMan function"
Value: !GetAtt FunctionRole.Arn
SenderEmailAddress:
Description: "Sender address for EListMan verification emails"
Value: !Sub "${SenderName} <${SenderUserName}@${EmailDomainName}>"