lono 3.0.1 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +6 -0
- data/lib/lono.rb +1 -0
- data/lib/lono/cfn/diff.rb +1 -1
- data/lib/lono/cfn/util.rb +1 -1
- data/lib/lono/cli.rb +9 -1
- data/lib/lono/help.rb +14 -1
- data/lib/lono/importer.rb +60 -0
- data/lib/lono/template/upload.rb +57 -1
- data/lib/lono/version.rb +1 -1
- data/spec/fixtures/my_project/config/{lono.rb → templates/base/stacks.rb} +0 -0
- data/spec/fixtures/raw_templates/aws-waf-security-automations.template +1575 -0
- data/spec/lib/lono_spec.rb +10 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab4d099f039afce1da99fa539e4f09d5297b8405
|
4
|
+
data.tar.gz: 3a566f0d7c3b68dca04bdf5b62e0bdb435ad5d39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c40c0fe52867c7122464a6a2bd8d33f3a9508103b643d6a28f902c0fc965b60721264413c3faedf0ba8c898cd774b16f8aa1da14a0cd8c1565ab4923ea91f992
|
7
|
+
data.tar.gz: 3c2b37adac7f040ad08455c85d0326af2f64b743ca6a624d4c5c36512a33666fe7830008e78f8dd3863ec37c895bac882f983b5fc953e63f0ef11681e508cf88
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,12 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
5
5
|
|
6
|
+
## [3.1.1]
|
7
|
+
- update lono import and new cli help
|
8
|
+
|
9
|
+
## [3.1.0]
|
10
|
+
- lono import command
|
11
|
+
|
6
12
|
## [3.0.1]
|
7
13
|
- update aws-sdk to version 3
|
8
14
|
|
data/lib/lono.rb
CHANGED
data/lib/lono/cfn/diff.rb
CHANGED
data/lib/lono/cfn/util.rb
CHANGED
data/lib/lono/cli.rb
CHANGED
@@ -6,7 +6,7 @@ module Lono
|
|
6
6
|
class CLI < Lono::Command
|
7
7
|
|
8
8
|
desc "new [NAME]", "Generates lono starter project"
|
9
|
-
Help.new_long_desc
|
9
|
+
long_desc Help.new_long_desc
|
10
10
|
option :force, type: :boolean, aliases: "-f", desc: "override existing starter files"
|
11
11
|
option :quiet, type: :boolean, aliases: "-q", desc: "silence the output"
|
12
12
|
option :format, type: :string, default: "yaml", desc: "starter project template format: json or yaml"
|
@@ -14,6 +14,14 @@ module Lono
|
|
14
14
|
Lono::New.new(options.clone.merge(project_root: project_root)).run
|
15
15
|
end
|
16
16
|
|
17
|
+
desc "import [SOURCE]", "Imports raw CloudFormation template and lono-fies it"
|
18
|
+
long_desc Help.import
|
19
|
+
option :format, type: :string, default: "yaml", desc: "format for the final template"
|
20
|
+
option :project_root, default: ".", aliases: "-r", desc: "project root"
|
21
|
+
def import(source)
|
22
|
+
Importer.new(source, options).run
|
23
|
+
end
|
24
|
+
|
17
25
|
desc "generate", "Generate both CloudFormation templates and parameters files"
|
18
26
|
Help.generate
|
19
27
|
option :clean, type: :boolean, aliases: "-c", desc: "remove all output files before generating"
|
data/lib/lono/help.rb
CHANGED
@@ -9,6 +9,20 @@ $ lono new lono
|
|
9
9
|
EOL
|
10
10
|
end
|
11
11
|
|
12
|
+
def import
|
13
|
+
<<-EOL
|
14
|
+
Examples:
|
15
|
+
|
16
|
+
$ lono import /path/to/file
|
17
|
+
|
18
|
+
$ lono import http://url.com/path/to/template.json
|
19
|
+
|
20
|
+
$ lono import http://url.com/path/to/template.yml
|
21
|
+
|
22
|
+
Imports a raw CloudFormation template and lono-fies it.
|
23
|
+
EOL
|
24
|
+
end
|
25
|
+
|
12
26
|
def generate
|
13
27
|
<<-EOL
|
14
28
|
Examples:
|
@@ -19,7 +33,6 @@ $ lono g -c # shortcut
|
|
19
33
|
|
20
34
|
Builds both CloudFormation template and parameter files based on lono project and writes them to the output folder on the filesystem.
|
21
35
|
EOL
|
22
|
-
|
23
36
|
end
|
24
37
|
|
25
38
|
def template
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "json"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
class Lono::Importer
|
6
|
+
attr_reader :options
|
7
|
+
def initialize(source, options)
|
8
|
+
@source = source
|
9
|
+
@options = options
|
10
|
+
@format = normalize_format(@options[:format])
|
11
|
+
@project_root = options[:project_root] || '.'
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
download_template
|
16
|
+
add_template_definition
|
17
|
+
puts "Imported raw CloudFormation template and lono-fied it!"
|
18
|
+
end
|
19
|
+
|
20
|
+
def download_template
|
21
|
+
template = open(@source).read
|
22
|
+
|
23
|
+
result = if @format == 'yml'
|
24
|
+
YAML.dump(YAML.load(template))
|
25
|
+
else
|
26
|
+
JSON.pretty_generate(JSON.load(template))
|
27
|
+
end
|
28
|
+
|
29
|
+
folder = File.dirname(dest_path)
|
30
|
+
FileUtils.mkdir_p(folder) unless File.exist?(folder)
|
31
|
+
IO.write(dest_path, result)
|
32
|
+
puts "Template downloaded to #{dest_path}."
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add template definition to config/templates/base/stacks.rb.
|
36
|
+
def add_template_definition
|
37
|
+
path = "#{@project_root}/config/templates/base/stacks.rb"
|
38
|
+
lines = File.exist?(path) ? IO.readlines(path) : []
|
39
|
+
new_template_definition = %Q|template "#{template_name}"|
|
40
|
+
unless lines.detect { |l| l.include?(new_template_definition) }
|
41
|
+
lines << ["\n", new_template_definition]
|
42
|
+
result = lines.join('')
|
43
|
+
IO.write(path, result)
|
44
|
+
end
|
45
|
+
puts "Template definition added to #{path}."
|
46
|
+
end
|
47
|
+
|
48
|
+
def dest_path
|
49
|
+
"#{@project_root}/templates/#{template_name}.#{@format}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def template_name
|
53
|
+
File.basename(@source, ".*")
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def normalize_format(format)
|
58
|
+
format == 'yaml' ? 'yml' : format
|
59
|
+
end
|
60
|
+
end
|
data/lib/lono/template/upload.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'erb'
|
2
2
|
require 'json'
|
3
3
|
require 'base64'
|
4
|
+
require 'digest'
|
4
5
|
|
5
6
|
class Lono::Template::Upload
|
6
7
|
include Lono::Template::AwsServices
|
@@ -8,10 +9,13 @@ class Lono::Template::Upload
|
|
8
9
|
def initialize(options={})
|
9
10
|
@options = options
|
10
11
|
@project_root = options[:project_root] || '.'
|
12
|
+
@checksums = {}
|
11
13
|
end
|
12
14
|
|
13
15
|
def run
|
14
16
|
ensure_s3_setup!
|
17
|
+
load_checksums!
|
18
|
+
|
15
19
|
paths = Dir.glob("#{@project_root}/output/**/*")
|
16
20
|
paths.reject { |p| p =~ %r{output/params} }.
|
17
21
|
select { |p| File.file?(p) }.each do |path|
|
@@ -20,11 +24,48 @@ class Lono::Template::Upload
|
|
20
24
|
say "Templates uploaded to s3."
|
21
25
|
end
|
22
26
|
|
27
|
+
# Read existing files on s3 to grab their md5 checksum.
|
28
|
+
# We do this so we can see if we should avoid re-uploading the s3 child template
|
29
|
+
# entirely. If we upload a new child template that does not change AWS CloudFormation
|
30
|
+
# is not smart enough to know that it not has changed. I think all AWS CloudFormation
|
31
|
+
# does is check if the file's timestamp.
|
32
|
+
#
|
33
|
+
# Thought this would result in better AWS Change Set info but AWS still reports child
|
34
|
+
# stacks being changed even though they should not be reported. Leaving this s3 checksum
|
35
|
+
# in for now.
|
36
|
+
def load_checksums!
|
37
|
+
return if @options[:noop]
|
38
|
+
|
39
|
+
prefix = "#{s3_path}/#{LONO_ENV}" # s3://s3-bucket-and-path-from-settings/prod
|
40
|
+
resp = s3.list_objects(bucket: s3_bucket, prefix: prefix)
|
41
|
+
resp.contents.each do |object|
|
42
|
+
# key does not include the bucket name
|
43
|
+
# full path = s3://my-bucket/cloudformation-templates/prod/my-template.yml
|
44
|
+
# key = cloudformation-templates/prod/my-template.yml
|
45
|
+
# etag is the checksum as long as the file is not a multi-part file upload
|
46
|
+
# it has extra double quotes wrapped around it.
|
47
|
+
# etag = "\"9cb437490cee2cc96101baf326e5ca81\""
|
48
|
+
@checksums[object.key] = strip_surrounding_quotes(object.etag)
|
49
|
+
end
|
50
|
+
@checksums
|
51
|
+
end
|
52
|
+
|
53
|
+
def strip_surrounding_quotes(string)
|
54
|
+
string.sub(/^"/,'').sub(/"$/,'')
|
55
|
+
end
|
56
|
+
|
23
57
|
def upload(path)
|
24
58
|
pretty_path = path.sub(/^\.\//, '')
|
25
59
|
key = "#{s3_path}/#{LONO_ENV}/#{pretty_path.sub(/^output\//,'')}"
|
26
60
|
s3_full_path = "s3://#{s3_bucket}/#{key}"
|
27
61
|
|
62
|
+
local_checksum = Digest::MD5.hexdigest(IO.read(path))
|
63
|
+
remote_checksum = remote_checksum(path)
|
64
|
+
if local_checksum == remote_checksum
|
65
|
+
say("Not modified: #{pretty_path} to #{s3_full_path}".colorize(:yellow)) unless @options[:noop]
|
66
|
+
return # do not upload unless the checksum has changed
|
67
|
+
end
|
68
|
+
|
28
69
|
resp = s3.put_object(
|
29
70
|
body: IO.read(path),
|
30
71
|
bucket: s3_bucket,
|
@@ -32,11 +73,26 @@ class Lono::Template::Upload
|
|
32
73
|
storage_class: "REDUCED_REDUNDANCY"
|
33
74
|
) unless @options[:noop]
|
34
75
|
|
35
|
-
|
76
|
+
# Example output:
|
77
|
+
# Uploaded: output/docker.yml to s3://boltops-stag/cloudformation-templates/stag/docker.yml
|
78
|
+
# Uploaded: output/ecs/private.yml to s3://boltops-stag/cloudformation-templates/stag/ecs/private.yml
|
79
|
+
message = "Uploaded: #{pretty_path} to #{s3_full_path}".colorize(:green)
|
36
80
|
message = "NOOP: #{message}" if @options[:noop]
|
37
81
|
say message
|
38
82
|
end
|
39
83
|
|
84
|
+
# @checksums map has a key format: cloudformation-templates/stag/docker.yml
|
85
|
+
#
|
86
|
+
# path = ./output/docker.yml
|
87
|
+
# s3_path = s3://boltops-stag/cloudformation-templates/stag/docker.yml
|
88
|
+
def remote_checksum(path)
|
89
|
+
# first convert the local path to the path format that is stored in @checksums keys
|
90
|
+
# ./output/docker.yml => cloudformation-templates/stag/docker.yml
|
91
|
+
pretty_path = path.sub(/^\.\//, '')
|
92
|
+
key = "#{s3_path}/#{LONO_ENV}/#{pretty_path.sub(/^output\//,'')}"
|
93
|
+
@checksums[key]
|
94
|
+
end
|
95
|
+
|
40
96
|
# https://s3.amazonaws.com/mybucket/cloudformation-templates/prod/parent.yml
|
41
97
|
def s3_https_url(template_path)
|
42
98
|
ensure_s3_setup!
|
data/lib/lono/version.rb
CHANGED
File without changes
|
@@ -0,0 +1,1575 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
3
|
+
"Description": "(SO0006-CloudFront) - AWS WAF Security Automations: This AWS CloudFormation template helps you provision the AWS WAF Security Automations stack without worrying about creating and configuring the underlying AWS infrastructure. **WARNING** This template creates an AWS Lambda function, an AWS WAF Web ACL, an Amazon S3 bucket, and an Amazon CloudWatch custom metric. You will be billed for the AWS resources used if you create a stack from this template. **NOTICE** Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Amazon Software License (the License). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/asl/ or in the license file accompanying this file. This file is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and limitations under the License.",
|
4
|
+
"Metadata": {
|
5
|
+
"AWS::CloudFormation::Interface": {
|
6
|
+
"ParameterGroups": [{
|
7
|
+
"Label": {
|
8
|
+
"default": "Protection List"
|
9
|
+
},
|
10
|
+
"Parameters": ["SqlInjectionProtectionParam", "CrossSiteScriptingProtectionParam", "ActivateHttpFloodProtectionParam", "ActivateScansProbesProtectionParam", "ActivateReputationListsProtectionParam", "ActivateBadBotProtectionParam"]
|
11
|
+
}, {
|
12
|
+
"Label": {
|
13
|
+
"default": "Settings"
|
14
|
+
},
|
15
|
+
"Parameters": ["CloudFrontAccessLogBucket"]
|
16
|
+
}, {
|
17
|
+
"Label": {
|
18
|
+
"default": "Advanced Settings"
|
19
|
+
},
|
20
|
+
"Parameters": ["RequestThreshold", "ErrorThreshold", "WAFBlockPeriod"]
|
21
|
+
}, {
|
22
|
+
"Label": {
|
23
|
+
"default": "Anonymous Metrics Request"
|
24
|
+
},
|
25
|
+
"Parameters": ["SendAnonymousUsageData"]
|
26
|
+
}],
|
27
|
+
"ParameterLabels": {
|
28
|
+
"SqlInjectionProtectionParam": {
|
29
|
+
"default": "Activate SQL Injection Protection"
|
30
|
+
},
|
31
|
+
"CrossSiteScriptingProtectionParam": {
|
32
|
+
"default": "Activate Cross-site Scripting Protection"
|
33
|
+
},
|
34
|
+
"ActivateHttpFloodProtectionParam": {
|
35
|
+
"default": "Activate HTTP Flood Protection"
|
36
|
+
},
|
37
|
+
"ActivateScansProbesProtectionParam": {
|
38
|
+
"default": "Activate Scanner & Probe Protection"
|
39
|
+
},
|
40
|
+
"ActivateReputationListsProtectionParam": {
|
41
|
+
"default": "Activate Reputation List Protection"
|
42
|
+
},
|
43
|
+
"ActivateBadBotProtectionParam": {
|
44
|
+
"default": "Activate Bad Bot Protection"
|
45
|
+
},
|
46
|
+
"CloudFrontAccessLogBucket": {
|
47
|
+
"default": "CloudFront Access Log Bucket Name"
|
48
|
+
},
|
49
|
+
"SendAnonymousUsageData": {
|
50
|
+
"default": "Send Anonymous Usage Data"
|
51
|
+
},
|
52
|
+
"RequestThreshold": {
|
53
|
+
"default": "Request Threshold"
|
54
|
+
},
|
55
|
+
"ErrorThreshold": {
|
56
|
+
"default": "Error Threshold"
|
57
|
+
},
|
58
|
+
"WAFBlockPeriod": {
|
59
|
+
"default": "WAF Block Period"
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
},
|
64
|
+
|
65
|
+
|
66
|
+
"Parameters": {
|
67
|
+
"SqlInjectionProtectionParam": {
|
68
|
+
"Type": "String",
|
69
|
+
"Default": "yes",
|
70
|
+
"AllowedValues": ["yes", "no"],
|
71
|
+
"Description": "Choose yes to enable the component designed to block common SQL injection attacks."
|
72
|
+
},
|
73
|
+
"CrossSiteScriptingProtectionParam": {
|
74
|
+
"Type": "String",
|
75
|
+
"Default": "yes",
|
76
|
+
"AllowedValues": ["yes", "no"],
|
77
|
+
"Description": "Choose yes to enable the component designed to block common XSS attacks."
|
78
|
+
},
|
79
|
+
"ActivateHttpFloodProtectionParam": {
|
80
|
+
"Type": "String",
|
81
|
+
"Default": "yes",
|
82
|
+
"AllowedValues": ["yes", "no"],
|
83
|
+
"Description": "Choose yes to enable the component designed to block HTTP flood attacks."
|
84
|
+
},
|
85
|
+
"ActivateScansProbesProtectionParam": {
|
86
|
+
"Type": "String",
|
87
|
+
"Default": "yes",
|
88
|
+
"AllowedValues": ["yes", "no"],
|
89
|
+
"Description": "Choose yes to enable the component designed to block scanners and probes."
|
90
|
+
},
|
91
|
+
"ActivateReputationListsProtectionParam": {
|
92
|
+
"Type": "String",
|
93
|
+
"Default": "yes",
|
94
|
+
"AllowedValues": ["yes", "no"],
|
95
|
+
"Description": "Choose yes to block requests from IP addresses on third-party reputation lists (supported lists: spamhaus, torproject, and emergingthreats)."
|
96
|
+
},
|
97
|
+
"ActivateBadBotProtectionParam": {
|
98
|
+
"Type": "String",
|
99
|
+
"Default": "yes",
|
100
|
+
"AllowedValues": ["yes", "no"],
|
101
|
+
"Description": "Choose yes to enable the component designed to block bad bots and content scrapers."
|
102
|
+
},
|
103
|
+
"CloudFrontAccessLogBucket": {
|
104
|
+
"Type": "String",
|
105
|
+
"Default": "",
|
106
|
+
"AllowedPattern": "(^$|^([a-z]|(\\d(?!\\d{0,2}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})))([a-z\\d]|(\\.(?!(\\.|-)))|(-(?!\\.))){1,61}[a-z\\d]$)",
|
107
|
+
"Description": "Enter a name for the Amazon S3 bucket where you want to store Amazon CloudFront access logs. This can be the name of either an existing S3 bucket, or a new bucket that the template will create during stack launch (if it does not find a matching bucket name). The solution will modify the bucket’s notification configuration to trigger the Log Parser AWS Lambda function whenever a new log file is saved in this bucket. More about bucket name restriction here: http://amzn.to/1p1YlU5"
|
108
|
+
},
|
109
|
+
"SendAnonymousUsageData": {
|
110
|
+
"Type": "String",
|
111
|
+
"Default": "yes",
|
112
|
+
"AllowedValues": ["yes", "no"],
|
113
|
+
"Description": "Send anonymous data to AWS to help us understand solution usage across our customer base as a whole. To opt out of this feature, select No."
|
114
|
+
},
|
115
|
+
"RequestThreshold": {
|
116
|
+
"Type": "Number",
|
117
|
+
"Default": "400",
|
118
|
+
"Description": "If you chose yes for the Activate HTTP Flood Protection parameter, enter the maximum acceptable requests per minute per IP address. If you chose to deactivate this protection, ignore this parameter."
|
119
|
+
},
|
120
|
+
"ErrorThreshold": {
|
121
|
+
"Type": "Number",
|
122
|
+
"Default": "50",
|
123
|
+
"Description": "If you chose yes for the Activate Scanners & Probes Protection parameter, enter the maximum acceptable bad requests per minute per IP. If you chose to deactivate Scanners & Probes protection, ignore this parameter."
|
124
|
+
},
|
125
|
+
"WAFBlockPeriod": {
|
126
|
+
"Type": "Number",
|
127
|
+
"Default": "240",
|
128
|
+
"Description": "If you chose yes for the Activate HTTP Flood Protection or Activate Scanners & Probes Protection parameters, enter the period (in minutes) to block applicable IP addresses. If you chose to deactivate both types of protection, ignore this parameter."
|
129
|
+
}
|
130
|
+
},
|
131
|
+
|
132
|
+
|
133
|
+
"Conditions": {
|
134
|
+
"SqlInjectionProtectionActivated": {
|
135
|
+
"Fn::Equals": [{
|
136
|
+
"Ref": "SqlInjectionProtectionParam"
|
137
|
+
}, "yes"]
|
138
|
+
},
|
139
|
+
"CrossSiteScriptingProtectionActivated": {
|
140
|
+
"Fn::Equals": [{
|
141
|
+
"Ref": "CrossSiteScriptingProtectionParam"
|
142
|
+
}, "yes"]
|
143
|
+
},
|
144
|
+
"HttpFloodProtectionActivated": {
|
145
|
+
"Fn::Equals": [{
|
146
|
+
"Ref": "ActivateHttpFloodProtectionParam"
|
147
|
+
}, "yes"]
|
148
|
+
},
|
149
|
+
"ScansProbesProtectionActivated": {
|
150
|
+
"Fn::Equals": [{
|
151
|
+
"Ref": "ActivateScansProbesProtectionParam"
|
152
|
+
}, "yes"]
|
153
|
+
},
|
154
|
+
"ReputationListsProtectionActivated": {
|
155
|
+
"Fn::Equals": [{
|
156
|
+
"Ref": "ActivateReputationListsProtectionParam"
|
157
|
+
}, "yes"]
|
158
|
+
},
|
159
|
+
"BadBotProtectionActivated": {
|
160
|
+
"Fn::Equals": [{
|
161
|
+
"Ref": "ActivateBadBotProtectionParam"
|
162
|
+
}, "yes"]
|
163
|
+
},
|
164
|
+
"LogParserActivated": {
|
165
|
+
"Fn::Or": [{
|
166
|
+
"Condition": "HttpFloodProtectionActivated"
|
167
|
+
}, {
|
168
|
+
"Condition": "ScansProbesProtectionActivated"
|
169
|
+
}]
|
170
|
+
},
|
171
|
+
"CreateWebACL": {
|
172
|
+
"Fn::Or": [{
|
173
|
+
"Condition": "SqlInjectionProtectionActivated"
|
174
|
+
}, {
|
175
|
+
"Condition": "CrossSiteScriptingProtectionActivated"
|
176
|
+
}, {
|
177
|
+
"Condition": "LogParserActivated"
|
178
|
+
}, {
|
179
|
+
"Condition": "ReputationListsProtectionActivated"
|
180
|
+
}, {
|
181
|
+
"Condition": "BadBotProtectionActivated"
|
182
|
+
}]
|
183
|
+
}
|
184
|
+
},
|
185
|
+
"Resources": {
|
186
|
+
"WAFWhitelistSet": {
|
187
|
+
"Type": "AWS::WAF::IPSet",
|
188
|
+
"Condition": "CreateWebACL",
|
189
|
+
"Properties": {
|
190
|
+
"Name": {
|
191
|
+
"Fn::Join": [" - ", [{
|
192
|
+
"Ref": "AWS::StackName"
|
193
|
+
}, "Whitelist Set"]]
|
194
|
+
}
|
195
|
+
}
|
196
|
+
},
|
197
|
+
"WAFBlacklistSet": {
|
198
|
+
"Type": "AWS::WAF::IPSet",
|
199
|
+
"Condition": "LogParserActivated",
|
200
|
+
"Properties": {
|
201
|
+
"Name": {
|
202
|
+
"Fn::Join": [" - ", [{
|
203
|
+
"Ref": "AWS::StackName"
|
204
|
+
}, "Blacklist Set"]]
|
205
|
+
}
|
206
|
+
}
|
207
|
+
},
|
208
|
+
"WAFAutoBlockSet": {
|
209
|
+
"Type": "AWS::WAF::IPSet",
|
210
|
+
"Condition": "LogParserActivated",
|
211
|
+
"Properties": {
|
212
|
+
"Name": {
|
213
|
+
"Fn::Join": [" - ", [{
|
214
|
+
"Ref": "AWS::StackName"
|
215
|
+
}, "Auto Block Set"]]
|
216
|
+
}
|
217
|
+
}
|
218
|
+
},
|
219
|
+
"WAFReputationListsSet1": {
|
220
|
+
"Type": "AWS::WAF::IPSet",
|
221
|
+
"Condition": "ReputationListsProtectionActivated",
|
222
|
+
"Properties": {
|
223
|
+
"Name": {
|
224
|
+
"Fn::Join": [" - ", [{
|
225
|
+
"Ref": "AWS::StackName"
|
226
|
+
}, "IP Reputation Lists Set #1"]]
|
227
|
+
}
|
228
|
+
}
|
229
|
+
},
|
230
|
+
"WAFReputationListsSet2": {
|
231
|
+
"Type": "AWS::WAF::IPSet",
|
232
|
+
"Condition": "ReputationListsProtectionActivated",
|
233
|
+
"Properties": {
|
234
|
+
"Name": {
|
235
|
+
"Fn::Join": [" - ", [{
|
236
|
+
"Ref": "AWS::StackName"
|
237
|
+
}, "IP Reputation Lists Set #2"]]
|
238
|
+
}
|
239
|
+
}
|
240
|
+
},
|
241
|
+
"WAFBadBotSet": {
|
242
|
+
"Type": "AWS::WAF::IPSet",
|
243
|
+
"Condition": "BadBotProtectionActivated",
|
244
|
+
"Properties": {
|
245
|
+
"Name": {
|
246
|
+
"Fn::Join": [" - ", [{
|
247
|
+
"Ref": "AWS::StackName"
|
248
|
+
}, "IP Bad Bot Set"]]
|
249
|
+
}
|
250
|
+
}
|
251
|
+
},
|
252
|
+
"WAFSqlInjectionDetection": {
|
253
|
+
"Type": "AWS::WAF::SqlInjectionMatchSet",
|
254
|
+
"Condition": "SqlInjectionProtectionActivated",
|
255
|
+
"Properties": {
|
256
|
+
"Name": {
|
257
|
+
"Fn::Join": [" - ", [{
|
258
|
+
"Ref": "AWS::StackName"
|
259
|
+
}, "SQL injection Detection"]]
|
260
|
+
},
|
261
|
+
"SqlInjectionMatchTuples": [{
|
262
|
+
"FieldToMatch": {
|
263
|
+
"Type": "QUERY_STRING"
|
264
|
+
},
|
265
|
+
"TextTransformation": "URL_DECODE"
|
266
|
+
}, {
|
267
|
+
"FieldToMatch": {
|
268
|
+
"Type": "QUERY_STRING"
|
269
|
+
},
|
270
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
271
|
+
}, {
|
272
|
+
"FieldToMatch": {
|
273
|
+
"Type": "BODY"
|
274
|
+
},
|
275
|
+
"TextTransformation": "URL_DECODE"
|
276
|
+
}, {
|
277
|
+
"FieldToMatch": {
|
278
|
+
"Type": "BODY"
|
279
|
+
},
|
280
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
281
|
+
}, {
|
282
|
+
"FieldToMatch": {
|
283
|
+
"Type": "URI"
|
284
|
+
},
|
285
|
+
"TextTransformation": "URL_DECODE"
|
286
|
+
}, {
|
287
|
+
"FieldToMatch": {
|
288
|
+
"Type": "URI"
|
289
|
+
},
|
290
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
291
|
+
}]
|
292
|
+
}
|
293
|
+
},
|
294
|
+
"WAFXssDetection": {
|
295
|
+
"Type": "AWS::WAF::XssMatchSet",
|
296
|
+
"Condition": "CrossSiteScriptingProtectionActivated",
|
297
|
+
"Properties": {
|
298
|
+
"Name": {
|
299
|
+
"Fn::Join": [" - ", [{
|
300
|
+
"Ref": "AWS::StackName"
|
301
|
+
}, "XSS Detection Detection"]]
|
302
|
+
},
|
303
|
+
"XssMatchTuples": [{
|
304
|
+
"FieldToMatch": {
|
305
|
+
"Type": "QUERY_STRING"
|
306
|
+
},
|
307
|
+
"TextTransformation": "URL_DECODE"
|
308
|
+
}, {
|
309
|
+
"FieldToMatch": {
|
310
|
+
"Type": "QUERY_STRING"
|
311
|
+
},
|
312
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
313
|
+
}, {
|
314
|
+
"FieldToMatch": {
|
315
|
+
"Type": "BODY"
|
316
|
+
},
|
317
|
+
"TextTransformation": "URL_DECODE"
|
318
|
+
}, {
|
319
|
+
"FieldToMatch": {
|
320
|
+
"Type": "BODY"
|
321
|
+
},
|
322
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
323
|
+
}, {
|
324
|
+
"FieldToMatch": {
|
325
|
+
"Type": "URI"
|
326
|
+
},
|
327
|
+
"TextTransformation": "URL_DECODE"
|
328
|
+
}, {
|
329
|
+
"FieldToMatch": {
|
330
|
+
"Type": "URI"
|
331
|
+
},
|
332
|
+
"TextTransformation": "HTML_ENTITY_DECODE"
|
333
|
+
}]
|
334
|
+
}
|
335
|
+
},
|
336
|
+
"WAFWhitelistRule": {
|
337
|
+
"Type": "AWS::WAF::Rule",
|
338
|
+
"Condition": "CreateWebACL",
|
339
|
+
"DependsOn": "WAFWhitelistSet",
|
340
|
+
"Properties": {
|
341
|
+
"Name": {
|
342
|
+
"Fn::Join": [" - ", [{
|
343
|
+
"Ref": "AWS::StackName"
|
344
|
+
}, "Whitelist Rule"]]
|
345
|
+
},
|
346
|
+
"MetricName": "SecurityAutomationsWhitelistRule",
|
347
|
+
"Predicates": [{
|
348
|
+
"DataId": {
|
349
|
+
"Ref": "WAFWhitelistSet"
|
350
|
+
},
|
351
|
+
"Negated": false,
|
352
|
+
"Type": "IPMatch"
|
353
|
+
}]
|
354
|
+
}
|
355
|
+
},
|
356
|
+
"WAFBlacklistRule": {
|
357
|
+
"Type": "AWS::WAF::Rule",
|
358
|
+
"Condition": "LogParserActivated",
|
359
|
+
"DependsOn": "WAFBlacklistSet",
|
360
|
+
"Properties": {
|
361
|
+
"Name": {
|
362
|
+
"Fn::Join": [" - ", [{
|
363
|
+
"Ref": "AWS::StackName"
|
364
|
+
}, "Blacklist Rule"]]
|
365
|
+
},
|
366
|
+
"MetricName": "SecurityAutomationsBlacklistRule",
|
367
|
+
"Predicates": [{
|
368
|
+
"DataId": {
|
369
|
+
"Ref": "WAFBlacklistSet"
|
370
|
+
},
|
371
|
+
"Negated": false,
|
372
|
+
"Type": "IPMatch"
|
373
|
+
}]
|
374
|
+
}
|
375
|
+
},
|
376
|
+
"WAFAutoBlockRule": {
|
377
|
+
"Type": "AWS::WAF::Rule",
|
378
|
+
"Condition": "LogParserActivated",
|
379
|
+
"DependsOn": "WAFAutoBlockSet",
|
380
|
+
"Properties": {
|
381
|
+
"Name": {
|
382
|
+
"Fn::Join": [" - ", [{
|
383
|
+
"Ref": "AWS::StackName"
|
384
|
+
}, "Auto Block Rule"]]
|
385
|
+
},
|
386
|
+
"MetricName": "SecurityAutomationsAutoBlockRule",
|
387
|
+
"Predicates": [{
|
388
|
+
"DataId": {
|
389
|
+
"Ref": "WAFAutoBlockSet"
|
390
|
+
},
|
391
|
+
"Negated": false,
|
392
|
+
"Type": "IPMatch"
|
393
|
+
}]
|
394
|
+
}
|
395
|
+
},
|
396
|
+
"WAFIPReputationListsRule1": {
|
397
|
+
"Type": "AWS::WAF::Rule",
|
398
|
+
"Condition": "ReputationListsProtectionActivated",
|
399
|
+
"DependsOn": "WAFReputationListsSet1",
|
400
|
+
"Properties": {
|
401
|
+
"Name": {
|
402
|
+
"Fn::Join": [" - ", [{
|
403
|
+
"Ref": "AWS::StackName"
|
404
|
+
}, "WAF IP Reputation Lists Rule #1"]]
|
405
|
+
},
|
406
|
+
"MetricName": "SecurityAutomationsIPReputationListsRule1",
|
407
|
+
"Predicates": [{
|
408
|
+
"DataId": {
|
409
|
+
"Ref": "WAFReputationListsSet1"
|
410
|
+
},
|
411
|
+
"Type": "IPMatch",
|
412
|
+
"Negated": "false"
|
413
|
+
}]
|
414
|
+
}
|
415
|
+
},
|
416
|
+
"WAFIPReputationListsRule2": {
|
417
|
+
"Type": "AWS::WAF::Rule",
|
418
|
+
"Condition": "ReputationListsProtectionActivated",
|
419
|
+
"DependsOn": "WAFReputationListsSet2",
|
420
|
+
"Properties": {
|
421
|
+
"Name": {
|
422
|
+
"Fn::Join": [" - ", [{
|
423
|
+
"Ref": "AWS::StackName"
|
424
|
+
}, "WAF IP Reputation Lists Rule #2"]]
|
425
|
+
},
|
426
|
+
"MetricName": "SecurityAutomationsIPReputationListsRule2",
|
427
|
+
"Predicates": [{
|
428
|
+
"DataId": {
|
429
|
+
"Ref": "WAFReputationListsSet2"
|
430
|
+
},
|
431
|
+
"Type": "IPMatch",
|
432
|
+
"Negated": "false"
|
433
|
+
}]
|
434
|
+
}
|
435
|
+
},
|
436
|
+
"WAFBadBotRule": {
|
437
|
+
"Type": "AWS::WAF::Rule",
|
438
|
+
"Condition": "BadBotProtectionActivated",
|
439
|
+
"DependsOn": "WAFBadBotSet",
|
440
|
+
"Properties": {
|
441
|
+
"Name": {
|
442
|
+
"Fn::Join": [" - ", [{
|
443
|
+
"Ref": "AWS::StackName"
|
444
|
+
}, "Bad Bot Rule"]]
|
445
|
+
},
|
446
|
+
"MetricName": "SecurityAutomationsBadBotRule",
|
447
|
+
"Predicates": [{
|
448
|
+
"DataId": {
|
449
|
+
"Ref": "WAFBadBotSet"
|
450
|
+
},
|
451
|
+
"Type": "IPMatch",
|
452
|
+
"Negated": "false"
|
453
|
+
}]
|
454
|
+
}
|
455
|
+
},
|
456
|
+
"WAFSqlInjectionRule": {
|
457
|
+
"Type": "AWS::WAF::Rule",
|
458
|
+
"Condition": "SqlInjectionProtectionActivated",
|
459
|
+
"DependsOn": "WAFSqlInjectionDetection",
|
460
|
+
"Properties": {
|
461
|
+
"Name": {
|
462
|
+
"Fn::Join": [" - ", [{
|
463
|
+
"Ref": "AWS::StackName"
|
464
|
+
}, "SQL Injection Rule"]]
|
465
|
+
},
|
466
|
+
"MetricName": "SecurityAutomationsSqlInjectionRule",
|
467
|
+
"Predicates": [{
|
468
|
+
"DataId": {
|
469
|
+
"Ref": "WAFSqlInjectionDetection"
|
470
|
+
},
|
471
|
+
"Negated": false,
|
472
|
+
"Type": "SqlInjectionMatch"
|
473
|
+
}]
|
474
|
+
}
|
475
|
+
},
|
476
|
+
"WAFXssRule": {
|
477
|
+
"Type": "AWS::WAF::Rule",
|
478
|
+
"Condition": "CrossSiteScriptingProtectionActivated",
|
479
|
+
"DependsOn": "WAFXssDetection",
|
480
|
+
"Properties": {
|
481
|
+
"Name": {
|
482
|
+
"Fn::Join": [" - ", [{
|
483
|
+
"Ref": "AWS::StackName"
|
484
|
+
}, "XSS Rule"]]
|
485
|
+
},
|
486
|
+
"MetricName": "SecurityAutomationsXssRule",
|
487
|
+
"Predicates": [{
|
488
|
+
"DataId": {
|
489
|
+
"Ref": "WAFXssDetection"
|
490
|
+
},
|
491
|
+
"Negated": false,
|
492
|
+
"Type": "XssMatch"
|
493
|
+
}]
|
494
|
+
}
|
495
|
+
},
|
496
|
+
"WAFWebACL": {
|
497
|
+
"Type": "AWS::WAF::WebACL",
|
498
|
+
"Condition": "CreateWebACL",
|
499
|
+
"DependsOn": ["WAFWhitelistRule"],
|
500
|
+
"Properties": {
|
501
|
+
"Name": {
|
502
|
+
"Ref": "AWS::StackName"
|
503
|
+
},
|
504
|
+
"DefaultAction": {
|
505
|
+
"Type": "ALLOW"
|
506
|
+
},
|
507
|
+
"MetricName": "SecurityAutomationsMaliciousRequesters",
|
508
|
+
"Rules": [{
|
509
|
+
"Action": {
|
510
|
+
"Type": "ALLOW"
|
511
|
+
},
|
512
|
+
"Priority": 10,
|
513
|
+
"RuleId": {
|
514
|
+
"Ref": "WAFWhitelistRule"
|
515
|
+
}
|
516
|
+
}]
|
517
|
+
}
|
518
|
+
},
|
519
|
+
"LambdaRoleLogParser": {
|
520
|
+
"Type": "AWS::IAM::Role",
|
521
|
+
"Condition": "LogParserActivated",
|
522
|
+
"Properties": {
|
523
|
+
"AssumeRolePolicyDocument": {
|
524
|
+
"Version": "2012-10-17",
|
525
|
+
"Statement": [{
|
526
|
+
"Effect": "Allow",
|
527
|
+
"Principal": {
|
528
|
+
"Service": ["lambda.amazonaws.com"]
|
529
|
+
},
|
530
|
+
"Action": ["sts:AssumeRole"]
|
531
|
+
}]
|
532
|
+
},
|
533
|
+
"Path": "/",
|
534
|
+
"Policies": [{
|
535
|
+
"PolicyName": "S3Access",
|
536
|
+
"PolicyDocument": {
|
537
|
+
"Version": "2012-10-17",
|
538
|
+
"Statement": [{
|
539
|
+
"Effect": "Allow",
|
540
|
+
"Action": "s3:GetObject",
|
541
|
+
"Resource": {
|
542
|
+
"Fn::Join": ["", ["arn:aws:s3:::", {
|
543
|
+
"Ref": "CloudFrontAccessLogBucket"
|
544
|
+
}, "/*"]]
|
545
|
+
}
|
546
|
+
}]
|
547
|
+
}
|
548
|
+
}, {
|
549
|
+
"PolicyName": "S3AccessPut",
|
550
|
+
"PolicyDocument": {
|
551
|
+
"Version": "2012-10-17",
|
552
|
+
"Statement": [{
|
553
|
+
"Effect": "Allow",
|
554
|
+
"Action": "s3:PutObject",
|
555
|
+
"Resource": {
|
556
|
+
"Fn::Join": ["", ["arn:aws:s3:::", {
|
557
|
+
"Ref": "CloudFrontAccessLogBucket"
|
558
|
+
}, "/aws-waf-security-automations-current-blocked-ips.json"]]
|
559
|
+
}
|
560
|
+
}]
|
561
|
+
}
|
562
|
+
}, {
|
563
|
+
"PolicyName": "WAFGetChangeToken",
|
564
|
+
"PolicyDocument": {
|
565
|
+
"Statement": [{
|
566
|
+
"Effect": "Allow",
|
567
|
+
"Action": "waf:GetChangeToken",
|
568
|
+
"Resource": "*"
|
569
|
+
}]
|
570
|
+
}
|
571
|
+
}, {
|
572
|
+
"PolicyName": "WAFGetAndUpdateIPSet",
|
573
|
+
"PolicyDocument": {
|
574
|
+
"Statement": [{
|
575
|
+
"Effect": "Allow",
|
576
|
+
"Action": [
|
577
|
+
"waf:GetIPSet",
|
578
|
+
"waf:UpdateIPSet"
|
579
|
+
],
|
580
|
+
"Resource": [{
|
581
|
+
"Fn::Join": [
|
582
|
+
"", [
|
583
|
+
"arn:aws:waf::", {
|
584
|
+
"Ref": "AWS::AccountId"
|
585
|
+
},
|
586
|
+
":ipset/", {
|
587
|
+
"Ref": "WAFBlacklistSet"
|
588
|
+
}
|
589
|
+
]
|
590
|
+
]
|
591
|
+
}, {
|
592
|
+
"Fn::Join": [
|
593
|
+
"", [
|
594
|
+
"arn:aws:waf::", {
|
595
|
+
"Ref": "AWS::AccountId"
|
596
|
+
},
|
597
|
+
":ipset/", {
|
598
|
+
"Ref": "WAFAutoBlockSet"
|
599
|
+
}
|
600
|
+
]
|
601
|
+
]
|
602
|
+
}]
|
603
|
+
}]
|
604
|
+
}
|
605
|
+
}, {
|
606
|
+
"PolicyName": "LogsAccess",
|
607
|
+
"PolicyDocument": {
|
608
|
+
"Version": "2012-10-17",
|
609
|
+
"Statement": [{
|
610
|
+
"Effect": "Allow",
|
611
|
+
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
612
|
+
"Resource": { "Fn::Join" : [":", ["arn:aws:logs",{"Ref" : "AWS::Region"},{ "Ref" : "AWS::AccountId" }, "log-group:/aws/lambda/*" ]]}
|
613
|
+
}]
|
614
|
+
}
|
615
|
+
}, {
|
616
|
+
"PolicyName": "CloudWatchAccess",
|
617
|
+
"PolicyDocument": {
|
618
|
+
"Version": "2012-10-17",
|
619
|
+
"Statement": [{
|
620
|
+
"Effect": "Allow",
|
621
|
+
"Action": "cloudwatch:GetMetricStatistics",
|
622
|
+
"Resource": "*"
|
623
|
+
}]
|
624
|
+
}
|
625
|
+
}]
|
626
|
+
}
|
627
|
+
},
|
628
|
+
"LambdaWAFLogParserFunction": {
|
629
|
+
"Type": "AWS::Lambda::Function",
|
630
|
+
"Condition": "LogParserActivated",
|
631
|
+
"DependsOn": ["LambdaRoleLogParser", "WAFBlacklistSet", "WAFAutoBlockSet"],
|
632
|
+
"Properties": {
|
633
|
+
"Description": {
|
634
|
+
"Fn::Join": ["", [
|
635
|
+
"This function parses CloudFront access logs to identify suspicious behavior, such as an abnormal amount of requests or errors. It then blocks those IP addresses for a customer-defined period of time. Parameters: ", {
|
636
|
+
"Ref": "RequestThreshold"
|
637
|
+
},
|
638
|
+
",", {
|
639
|
+
"Ref": "ErrorThreshold"
|
640
|
+
},
|
641
|
+
",", {
|
642
|
+
"Ref": "WAFBlockPeriod"
|
643
|
+
},
|
644
|
+
"."
|
645
|
+
]]
|
646
|
+
},
|
647
|
+
"Handler": "log-parser.lambda_handler",
|
648
|
+
"Role": {
|
649
|
+
"Fn::GetAtt": ["LambdaRoleLogParser", "Arn"]
|
650
|
+
},
|
651
|
+
"Code": {
|
652
|
+
"S3Bucket": {
|
653
|
+
"Fn::Join": ["-", [
|
654
|
+
"solutions", {
|
655
|
+
"Ref": "AWS::Region"
|
656
|
+
}
|
657
|
+
]]
|
658
|
+
},
|
659
|
+
"S3Key": "aws-waf-security-automations/v2/log-parser.zip"
|
660
|
+
},
|
661
|
+
"Environment": {
|
662
|
+
"Variables": {
|
663
|
+
"OUTPUT_BUCKET": {
|
664
|
+
"Ref": "CloudFrontAccessLogBucket"
|
665
|
+
},
|
666
|
+
"IP_SET_ID_BLACKLIST": {
|
667
|
+
"Ref": "WAFBlacklistSet"
|
668
|
+
},
|
669
|
+
"IP_SET_ID_AUTO_BLOCK": {
|
670
|
+
"Ref": "WAFAutoBlockSet"
|
671
|
+
},
|
672
|
+
"BLACKLIST_BLOCK_PERIOD": {
|
673
|
+
"Ref": "WAFBlockPeriod"
|
674
|
+
},
|
675
|
+
"REQUEST_PER_MINUTE_LIMIT": {
|
676
|
+
"Ref": "RequestThreshold"
|
677
|
+
},
|
678
|
+
"ERROR_PER_MINUTE_LIMIT": {
|
679
|
+
"Ref": "ErrorThreshold"
|
680
|
+
},
|
681
|
+
"SEND_ANONYMOUS_USAGE_DATA": {
|
682
|
+
"Ref": "SendAnonymousUsageData"
|
683
|
+
},
|
684
|
+
"UUID": {
|
685
|
+
"Fn::GetAtt": ["CreateUniqueID", "UUID"]
|
686
|
+
},
|
687
|
+
"LIMIT_IP_ADDRESS_RANGES_PER_IP_MATCH_CONDITION": "10000",
|
688
|
+
"MAX_AGE_TO_UPDATE": "30",
|
689
|
+
"REGION": {
|
690
|
+
"Ref": "AWS::Region"
|
691
|
+
},
|
692
|
+
"LOG_TYPE": "cloudfront"
|
693
|
+
}
|
694
|
+
},
|
695
|
+
"Runtime": "python2.7",
|
696
|
+
"MemorySize": "512",
|
697
|
+
"Timeout": "300"
|
698
|
+
}
|
699
|
+
},
|
700
|
+
"LambdaInvokePermissionLogParser": {
|
701
|
+
"Type": "AWS::Lambda::Permission",
|
702
|
+
"Condition": "LogParserActivated",
|
703
|
+
"DependsOn": "LambdaWAFLogParserFunction",
|
704
|
+
"Properties": {
|
705
|
+
"FunctionName": {
|
706
|
+
"Fn::GetAtt": ["LambdaWAFLogParserFunction", "Arn"]
|
707
|
+
},
|
708
|
+
"Action": "lambda:*",
|
709
|
+
"Principal": "s3.amazonaws.com",
|
710
|
+
"SourceAccount": {
|
711
|
+
"Ref": "AWS::AccountId"
|
712
|
+
}
|
713
|
+
}
|
714
|
+
},
|
715
|
+
"LambdaRoleReputationListsParser": {
|
716
|
+
"Type": "AWS::IAM::Role",
|
717
|
+
"Condition": "ReputationListsProtectionActivated",
|
718
|
+
"Properties": {
|
719
|
+
"AssumeRolePolicyDocument": {
|
720
|
+
"Statement": [{
|
721
|
+
"Effect": "Allow",
|
722
|
+
"Principal": {
|
723
|
+
"Service": [
|
724
|
+
"lambda.amazonaws.com"
|
725
|
+
]
|
726
|
+
},
|
727
|
+
"Action": "sts:AssumeRole"
|
728
|
+
}]
|
729
|
+
},
|
730
|
+
"Policies": [{
|
731
|
+
"PolicyName": "CloudWatchLogs",
|
732
|
+
"PolicyDocument": {
|
733
|
+
"Statement": [{
|
734
|
+
"Effect": "Allow",
|
735
|
+
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
736
|
+
"Resource": { "Fn::Join" : [":", ["arn:aws:logs",{"Ref" : "AWS::Region"},{ "Ref" : "AWS::AccountId" }, "log-group:/aws/lambda/*" ]]}
|
737
|
+
}]
|
738
|
+
}
|
739
|
+
}, {
|
740
|
+
"PolicyName": "WAFGetChangeToken",
|
741
|
+
"PolicyDocument": {
|
742
|
+
"Statement": [{
|
743
|
+
"Effect": "Allow",
|
744
|
+
"Action": "waf:GetChangeToken",
|
745
|
+
"Resource": "*"
|
746
|
+
}]
|
747
|
+
}
|
748
|
+
}, {
|
749
|
+
"PolicyName": "WAFGetAndUpdateIPSet",
|
750
|
+
"PolicyDocument": {
|
751
|
+
"Statement": [{
|
752
|
+
"Effect": "Allow",
|
753
|
+
"Action": [
|
754
|
+
"waf:GetIPSet",
|
755
|
+
"waf:UpdateIPSet"
|
756
|
+
],
|
757
|
+
"Resource": [{
|
758
|
+
"Fn::Join": [
|
759
|
+
"", [
|
760
|
+
"arn:aws:waf::", {
|
761
|
+
"Ref": "AWS::AccountId"
|
762
|
+
},
|
763
|
+
":ipset/", {
|
764
|
+
"Ref": "WAFReputationListsSet1"
|
765
|
+
}
|
766
|
+
]
|
767
|
+
]
|
768
|
+
}, {
|
769
|
+
"Fn::Join": [
|
770
|
+
"", [
|
771
|
+
"arn:aws:waf::", {
|
772
|
+
"Ref": "AWS::AccountId"
|
773
|
+
},
|
774
|
+
":ipset/", {
|
775
|
+
"Ref": "WAFReputationListsSet2"
|
776
|
+
}
|
777
|
+
]
|
778
|
+
]
|
779
|
+
}]
|
780
|
+
}]
|
781
|
+
}
|
782
|
+
}, {
|
783
|
+
"PolicyName": "CloudFormationAccess",
|
784
|
+
"PolicyDocument": {
|
785
|
+
"Version": "2012-10-17",
|
786
|
+
"Statement": [{
|
787
|
+
"Effect": "Allow",
|
788
|
+
"Action": "cloudformation:DescribeStacks",
|
789
|
+
"Resource": {
|
790
|
+
"Fn::Join": [
|
791
|
+
"", [
|
792
|
+
"arn:aws:cloudformation:", {
|
793
|
+
"Ref": "AWS::Region"
|
794
|
+
},
|
795
|
+
":", {
|
796
|
+
"Ref": "AWS::AccountId"
|
797
|
+
},
|
798
|
+
":stack/", {
|
799
|
+
"Ref": "AWS::StackName"
|
800
|
+
},
|
801
|
+
"/*"
|
802
|
+
]
|
803
|
+
]
|
804
|
+
}
|
805
|
+
}]
|
806
|
+
}
|
807
|
+
}, {
|
808
|
+
"PolicyName": "CloudWatchAccess",
|
809
|
+
"PolicyDocument": {
|
810
|
+
"Version": "2012-10-17",
|
811
|
+
"Statement": [{
|
812
|
+
"Effect": "Allow",
|
813
|
+
"Action": "cloudwatch:GetMetricStatistics",
|
814
|
+
"Resource": "*"
|
815
|
+
}]
|
816
|
+
}
|
817
|
+
}]
|
818
|
+
}
|
819
|
+
},
|
820
|
+
"LambdaWAFReputationListsParserFunction": {
|
821
|
+
"Type": "AWS::Lambda::Function",
|
822
|
+
"Condition": "ReputationListsProtectionActivated",
|
823
|
+
"DependsOn": "LambdaRoleReputationListsParser",
|
824
|
+
"Properties": {
|
825
|
+
"Description": "This lambda function checks third-party IP reputation lists hourly for new IP ranges to block. These lists include the Spamhaus Dont Route Or Peer (DROP) and Extended Drop (EDROP) lists, the Proofpoint Emerging Threats IP list, and the Tor exit node list.",
|
826
|
+
"Handler": "reputation-lists-parser.handler",
|
827
|
+
"Role": {
|
828
|
+
"Fn::GetAtt": [
|
829
|
+
"LambdaRoleReputationListsParser",
|
830
|
+
"Arn"
|
831
|
+
]
|
832
|
+
},
|
833
|
+
"Code": {
|
834
|
+
"S3Bucket": {
|
835
|
+
"Fn::Join": ["-", [
|
836
|
+
"solutions", {
|
837
|
+
"Ref": "AWS::Region"
|
838
|
+
}
|
839
|
+
]]
|
840
|
+
},
|
841
|
+
"S3Key": "aws-waf-security-automations/v3/reputation-lists-parser.zip"
|
842
|
+
},
|
843
|
+
"Runtime": "nodejs6.10",
|
844
|
+
"MemorySize": "128",
|
845
|
+
"Timeout": "300"
|
846
|
+
}
|
847
|
+
},
|
848
|
+
"LambdaWAFReputationListsParserEventsRule": {
|
849
|
+
"Type": "AWS::Events::Rule",
|
850
|
+
"Condition": "ReputationListsProtectionActivated",
|
851
|
+
"DependsOn": ["LambdaWAFReputationListsParserFunction", "WAFReputationListsSet1", "WAFReputationListsSet2"],
|
852
|
+
"Properties": {
|
853
|
+
"Description": "Security Automations - WAF Reputation Lists",
|
854
|
+
"ScheduleExpression": "rate(1 hour)",
|
855
|
+
"Targets": [{
|
856
|
+
"Arn": {
|
857
|
+
"Fn::GetAtt": [
|
858
|
+
"LambdaWAFReputationListsParserFunction",
|
859
|
+
"Arn"
|
860
|
+
]
|
861
|
+
},
|
862
|
+
"Id": "LambdaWAFReputationListsParserFunction",
|
863
|
+
"Input": {
|
864
|
+
"Fn::Join": [
|
865
|
+
"", [
|
866
|
+
"{\"lists\":",
|
867
|
+
"[{\"url\":\"https://www.spamhaus.org/drop/drop.txt\"},{\"url\":\"https://check.torproject.org/exit-addresses\",\"prefix\":\"ExitAddress \"},{\"url\":\"https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt\"}]",
|
868
|
+
",\"logType\":\"cloudfront\"",
|
869
|
+
",\"region\":\"", {
|
870
|
+
"Ref": "AWS::Region"
|
871
|
+
}, "\",",
|
872
|
+
"\"ipSetIds\": [",
|
873
|
+
"\"", {
|
874
|
+
"Ref": "WAFReputationListsSet1"
|
875
|
+
},
|
876
|
+
"\",",
|
877
|
+
"\"", {
|
878
|
+
"Ref": "WAFReputationListsSet2"
|
879
|
+
},
|
880
|
+
"\"",
|
881
|
+
"]}"
|
882
|
+
]
|
883
|
+
]
|
884
|
+
}
|
885
|
+
}]
|
886
|
+
}
|
887
|
+
},
|
888
|
+
"LambdaInvokePermissionReputationListsParser": {
|
889
|
+
"Type": "AWS::Lambda::Permission",
|
890
|
+
"Condition": "ReputationListsProtectionActivated",
|
891
|
+
"DependsOn": ["LambdaWAFReputationListsParserFunction", "LambdaWAFReputationListsParserEventsRule"],
|
892
|
+
"Properties": {
|
893
|
+
"FunctionName": {
|
894
|
+
"Ref": "LambdaWAFReputationListsParserFunction"
|
895
|
+
},
|
896
|
+
"Action": "lambda:InvokeFunction",
|
897
|
+
"Principal": "events.amazonaws.com",
|
898
|
+
"SourceArn": {
|
899
|
+
"Fn::GetAtt": [
|
900
|
+
"LambdaWAFReputationListsParserEventsRule",
|
901
|
+
"Arn"
|
902
|
+
]
|
903
|
+
}
|
904
|
+
}
|
905
|
+
},
|
906
|
+
"LambdaRoleBadBot": {
|
907
|
+
"Type": "AWS::IAM::Role",
|
908
|
+
"Condition": "BadBotProtectionActivated",
|
909
|
+
"Properties": {
|
910
|
+
"AssumeRolePolicyDocument": {
|
911
|
+
"Version": "2012-10-17",
|
912
|
+
"Statement": [{
|
913
|
+
"Effect": "Allow",
|
914
|
+
"Principal": {
|
915
|
+
"Service": ["lambda.amazonaws.com"]
|
916
|
+
},
|
917
|
+
"Action": ["sts:AssumeRole"]
|
918
|
+
}]
|
919
|
+
},
|
920
|
+
"Path": "/",
|
921
|
+
"Policies": [{
|
922
|
+
"PolicyName": "WAFGetChangeToken",
|
923
|
+
"PolicyDocument": {
|
924
|
+
"Statement": [{
|
925
|
+
"Effect": "Allow",
|
926
|
+
"Action": "waf:GetChangeToken",
|
927
|
+
"Resource": "*"
|
928
|
+
}]
|
929
|
+
}
|
930
|
+
}, {
|
931
|
+
"PolicyName": "WAFGetAndUpdateIPSet",
|
932
|
+
"PolicyDocument": {
|
933
|
+
"Statement": [{
|
934
|
+
"Effect": "Allow",
|
935
|
+
"Action": [
|
936
|
+
"waf:GetIPSet",
|
937
|
+
"waf:UpdateIPSet"
|
938
|
+
],
|
939
|
+
"Resource": {
|
940
|
+
"Fn::Join": [
|
941
|
+
"", [
|
942
|
+
"arn:aws:waf::", {
|
943
|
+
"Ref": "AWS::AccountId"
|
944
|
+
},
|
945
|
+
":ipset/", {
|
946
|
+
"Ref": "WAFBadBotSet"
|
947
|
+
}
|
948
|
+
]
|
949
|
+
]
|
950
|
+
}
|
951
|
+
}]
|
952
|
+
}
|
953
|
+
}, {
|
954
|
+
"PolicyName": "LogsAccess",
|
955
|
+
"PolicyDocument": {
|
956
|
+
"Version": "2012-10-17",
|
957
|
+
"Statement": [{
|
958
|
+
"Effect": "Allow",
|
959
|
+
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
960
|
+
"Resource": { "Fn::Join" : [":", ["arn:aws:logs",{"Ref" : "AWS::Region"},{ "Ref" : "AWS::AccountId" }, "log-group:/aws/lambda/*" ]]}
|
961
|
+
}]
|
962
|
+
}
|
963
|
+
}, {
|
964
|
+
"PolicyName": "CloudFormationAccess",
|
965
|
+
"PolicyDocument": {
|
966
|
+
"Version": "2012-10-17",
|
967
|
+
"Statement": [{
|
968
|
+
"Effect": "Allow",
|
969
|
+
"Action": "cloudformation:DescribeStacks",
|
970
|
+
"Resource": {
|
971
|
+
"Fn::Join": [
|
972
|
+
"", [
|
973
|
+
"arn:aws:cloudformation:", {
|
974
|
+
"Ref": "AWS::Region"
|
975
|
+
},
|
976
|
+
":", {
|
977
|
+
"Ref": "AWS::AccountId"
|
978
|
+
},
|
979
|
+
":stack/", {
|
980
|
+
"Ref": "AWS::StackName"
|
981
|
+
},
|
982
|
+
"/*"
|
983
|
+
]
|
984
|
+
]
|
985
|
+
}
|
986
|
+
}]
|
987
|
+
}
|
988
|
+
}, {
|
989
|
+
"PolicyName": "CloudWatchAccess",
|
990
|
+
"PolicyDocument": {
|
991
|
+
"Version": "2012-10-17",
|
992
|
+
"Statement": [{
|
993
|
+
"Effect": "Allow",
|
994
|
+
"Action": "cloudwatch:GetMetricStatistics",
|
995
|
+
"Resource": "*"
|
996
|
+
}]
|
997
|
+
}
|
998
|
+
}]
|
999
|
+
}
|
1000
|
+
},
|
1001
|
+
"LambdaWAFBadBotParserFunction": {
|
1002
|
+
"Type": "AWS::Lambda::Function",
|
1003
|
+
"Condition": "BadBotProtectionActivated",
|
1004
|
+
"DependsOn": "LambdaRoleBadBot",
|
1005
|
+
"Properties": {
|
1006
|
+
"Description": "This lambda function will intercepts and inspects trap endpoint requests to extract its IP address, and then add it to an AWS WAF block list.",
|
1007
|
+
"Handler": "access-handler.lambda_handler",
|
1008
|
+
"Role": {
|
1009
|
+
"Fn::GetAtt": ["LambdaRoleBadBot", "Arn"]
|
1010
|
+
},
|
1011
|
+
"Code": {
|
1012
|
+
"S3Bucket": {
|
1013
|
+
"Fn::Join": ["-", [
|
1014
|
+
"solutions", {
|
1015
|
+
"Ref": "AWS::Region"
|
1016
|
+
}
|
1017
|
+
]]
|
1018
|
+
},
|
1019
|
+
"S3Key": "aws-waf-security-automations/v2/access-handler.zip"
|
1020
|
+
},
|
1021
|
+
"Environment": {
|
1022
|
+
"Variables": {
|
1023
|
+
"IP_SET_ID_BAD_BOT": {
|
1024
|
+
"Ref": "WAFBadBotSet"
|
1025
|
+
},
|
1026
|
+
"SEND_ANONYMOUS_USAGE_DATA": {
|
1027
|
+
"Ref": "SendAnonymousUsageData"
|
1028
|
+
},
|
1029
|
+
"UUID": {
|
1030
|
+
"Fn::GetAtt": ["CreateUniqueID", "UUID"]
|
1031
|
+
},
|
1032
|
+
"REGION": {
|
1033
|
+
"Ref": "AWS::Region"
|
1034
|
+
},
|
1035
|
+
"LOG_TYPE": "cloudfront"
|
1036
|
+
}
|
1037
|
+
},
|
1038
|
+
"Runtime": "python2.7",
|
1039
|
+
"MemorySize": "128",
|
1040
|
+
"Timeout": "300"
|
1041
|
+
}
|
1042
|
+
},
|
1043
|
+
"LambdaInvokePermissionBadBot": {
|
1044
|
+
"Type": "AWS::Lambda::Permission",
|
1045
|
+
"Condition": "BadBotProtectionActivated",
|
1046
|
+
"DependsOn": "LambdaWAFBadBotParserFunction",
|
1047
|
+
"Properties": {
|
1048
|
+
"FunctionName": {
|
1049
|
+
"Fn::GetAtt": ["LambdaWAFBadBotParserFunction", "Arn"]
|
1050
|
+
},
|
1051
|
+
"Action": "lambda:*",
|
1052
|
+
"Principal": "apigateway.amazonaws.com"
|
1053
|
+
}
|
1054
|
+
},
|
1055
|
+
"ApiGatewayBadBot": {
|
1056
|
+
"Type": "AWS::ApiGateway::RestApi",
|
1057
|
+
"Condition": "BadBotProtectionActivated",
|
1058
|
+
"Properties": {
|
1059
|
+
"Name": "Security Automations - WAF Bad Bot API",
|
1060
|
+
"Description": "API created by AWS WAF Security Automations CloudFormation template. This endpoint will be used to capture bad bots."
|
1061
|
+
}
|
1062
|
+
},
|
1063
|
+
"ApiGatewayBadBotResource": {
|
1064
|
+
"Type": "AWS::ApiGateway::Resource",
|
1065
|
+
"Properties": {
|
1066
|
+
"RestApiId": {
|
1067
|
+
"Ref": "ApiGatewayBadBot"
|
1068
|
+
},
|
1069
|
+
"ParentId": {
|
1070
|
+
"Fn::GetAtt": ["ApiGatewayBadBot", "RootResourceId"]
|
1071
|
+
},
|
1072
|
+
"PathPart": "{proxy+}"
|
1073
|
+
}
|
1074
|
+
},
|
1075
|
+
"ApiGatewayBadBotMethodRoot": {
|
1076
|
+
"Type": "AWS::ApiGateway::Method",
|
1077
|
+
"Condition": "BadBotProtectionActivated",
|
1078
|
+
"DependsOn": ["LambdaWAFBadBotParserFunction", "LambdaInvokePermissionBadBot", "ApiGatewayBadBot"],
|
1079
|
+
"Properties": {
|
1080
|
+
"RestApiId": {
|
1081
|
+
"Ref": "ApiGatewayBadBot"
|
1082
|
+
},
|
1083
|
+
"ResourceId": {
|
1084
|
+
"Fn::GetAtt": ["ApiGatewayBadBot", "RootResourceId"]
|
1085
|
+
},
|
1086
|
+
"HttpMethod": "ANY",
|
1087
|
+
"AuthorizationType": "NONE",
|
1088
|
+
"RequestParameters": {
|
1089
|
+
"method.request.header.X-Forwarded-For": false
|
1090
|
+
},
|
1091
|
+
"Integration": {
|
1092
|
+
"Type": "AWS_PROXY",
|
1093
|
+
"IntegrationHttpMethod": "POST",
|
1094
|
+
"Uri": {
|
1095
|
+
"Fn::Join": ["", [
|
1096
|
+
"arn:aws:apigateway:", {
|
1097
|
+
"Ref": "AWS::Region"
|
1098
|
+
},
|
1099
|
+
":lambda:path/2015-03-31/functions/", {
|
1100
|
+
"Fn::GetAtt": ["LambdaWAFBadBotParserFunction", "Arn"]
|
1101
|
+
},
|
1102
|
+
"/invocations"
|
1103
|
+
]]
|
1104
|
+
}
|
1105
|
+
}
|
1106
|
+
}
|
1107
|
+
},
|
1108
|
+
"ApiGatewayBadBotMethod": {
|
1109
|
+
"Type": "AWS::ApiGateway::Method",
|
1110
|
+
"Condition": "BadBotProtectionActivated",
|
1111
|
+
"DependsOn": ["LambdaWAFBadBotParserFunction", "LambdaInvokePermissionBadBot", "ApiGatewayBadBot"],
|
1112
|
+
"Properties": {
|
1113
|
+
"RestApiId": {
|
1114
|
+
"Ref": "ApiGatewayBadBot"
|
1115
|
+
},
|
1116
|
+
"ResourceId": {
|
1117
|
+
"Ref": "ApiGatewayBadBotResource"
|
1118
|
+
},
|
1119
|
+
"HttpMethod": "ANY",
|
1120
|
+
"AuthorizationType": "NONE",
|
1121
|
+
"RequestParameters": {
|
1122
|
+
"method.request.header.X-Forwarded-For": false
|
1123
|
+
},
|
1124
|
+
"Integration": {
|
1125
|
+
"Type": "AWS_PROXY",
|
1126
|
+
"IntegrationHttpMethod": "POST",
|
1127
|
+
"Uri": {
|
1128
|
+
"Fn::Join": ["", [
|
1129
|
+
"arn:aws:apigateway:", {
|
1130
|
+
"Ref": "AWS::Region"
|
1131
|
+
},
|
1132
|
+
":lambda:path/2015-03-31/functions/", {
|
1133
|
+
"Fn::GetAtt": ["LambdaWAFBadBotParserFunction", "Arn"]
|
1134
|
+
},
|
1135
|
+
"/invocations"
|
1136
|
+
]]
|
1137
|
+
}
|
1138
|
+
}
|
1139
|
+
}
|
1140
|
+
},
|
1141
|
+
"ApiGatewayBadBotDeployment": {
|
1142
|
+
"Type": "AWS::ApiGateway::Deployment",
|
1143
|
+
"Condition": "BadBotProtectionActivated",
|
1144
|
+
"DependsOn": "ApiGatewayBadBotMethod",
|
1145
|
+
"Properties": {
|
1146
|
+
"RestApiId": {
|
1147
|
+
"Ref": "ApiGatewayBadBot"
|
1148
|
+
},
|
1149
|
+
"Description": "CloudFormation Deployment Stage",
|
1150
|
+
"StageName": "CFDeploymentStage"
|
1151
|
+
}
|
1152
|
+
},
|
1153
|
+
"ApiGatewayBadBotStage": {
|
1154
|
+
"Type": "AWS::ApiGateway::Stage",
|
1155
|
+
"Condition": "BadBotProtectionActivated",
|
1156
|
+
"DependsOn": "ApiGatewayBadBotDeployment",
|
1157
|
+
"Properties": {
|
1158
|
+
"DeploymentId": {
|
1159
|
+
"Ref": "ApiGatewayBadBotDeployment"
|
1160
|
+
},
|
1161
|
+
"Description": "Production Stage",
|
1162
|
+
"RestApiId": {
|
1163
|
+
"Ref": "ApiGatewayBadBot"
|
1164
|
+
},
|
1165
|
+
"StageName": "ProdStage"
|
1166
|
+
}
|
1167
|
+
},
|
1168
|
+
"LambdaRoleCustomResource": {
|
1169
|
+
"Type": "AWS::IAM::Role",
|
1170
|
+
"Condition": "CreateWebACL",
|
1171
|
+
"DependsOn": "WAFWebACL",
|
1172
|
+
"Properties": {
|
1173
|
+
"AssumeRolePolicyDocument": {
|
1174
|
+
"Version": "2012-10-17",
|
1175
|
+
"Statement": [{
|
1176
|
+
"Effect": "Allow",
|
1177
|
+
"Principal": {
|
1178
|
+
"Service": ["lambda.amazonaws.com"]
|
1179
|
+
},
|
1180
|
+
"Action": ["sts:AssumeRole"]
|
1181
|
+
}]
|
1182
|
+
},
|
1183
|
+
"Path": "/",
|
1184
|
+
"Policies": [{
|
1185
|
+
"PolicyName": "S3Access",
|
1186
|
+
"PolicyDocument": {
|
1187
|
+
"Version": "2012-10-17",
|
1188
|
+
"Statement": [{
|
1189
|
+
"Effect": "Allow",
|
1190
|
+
"Action": [
|
1191
|
+
"s3:CreateBucket",
|
1192
|
+
"s3:GetBucketLocation",
|
1193
|
+
"s3:GetBucketNotification",
|
1194
|
+
"s3:GetObject",
|
1195
|
+
"s3:ListBucket",
|
1196
|
+
"s3:PutBucketNotification"
|
1197
|
+
],
|
1198
|
+
"Resource": {
|
1199
|
+
"Fn::Join": ["", ["arn:aws:s3:::", {
|
1200
|
+
"Ref": "CloudFrontAccessLogBucket"
|
1201
|
+
}]]
|
1202
|
+
}
|
1203
|
+
}]
|
1204
|
+
}
|
1205
|
+
}, {
|
1206
|
+
"PolicyName": "LambdaAccess",
|
1207
|
+
"PolicyDocument": {
|
1208
|
+
"Version": "2012-10-17",
|
1209
|
+
"Statement": [{
|
1210
|
+
"Effect": "Allow",
|
1211
|
+
"Action": "lambda:InvokeFunction",
|
1212
|
+
"Resource": {
|
1213
|
+
"Fn::Join": ["", ["arn:aws:lambda:", {
|
1214
|
+
"Ref": "AWS::Region"
|
1215
|
+
},
|
1216
|
+
":", {
|
1217
|
+
"Ref": "AWS::AccountId"
|
1218
|
+
},
|
1219
|
+
":function:", {
|
1220
|
+
"Ref": "AWS::StackName"
|
1221
|
+
},
|
1222
|
+
"-LambdaWAFReputationLists*"
|
1223
|
+
]]
|
1224
|
+
}
|
1225
|
+
}]
|
1226
|
+
}
|
1227
|
+
}, {
|
1228
|
+
"PolicyName": "WAFAccess",
|
1229
|
+
"PolicyDocument": {
|
1230
|
+
"Version": "2012-10-17",
|
1231
|
+
"Statement": [{
|
1232
|
+
"Effect": "Allow",
|
1233
|
+
"Action": [
|
1234
|
+
"waf:GetWebACL",
|
1235
|
+
"waf:UpdateWebACL"
|
1236
|
+
],
|
1237
|
+
"Resource": {
|
1238
|
+
"Fn::Join": ["", ["arn:aws:waf::", {
|
1239
|
+
"Ref": "AWS::AccountId"
|
1240
|
+
},
|
1241
|
+
":webacl/", {
|
1242
|
+
"Ref": "WAFWebACL"
|
1243
|
+
}
|
1244
|
+
]]
|
1245
|
+
}
|
1246
|
+
}]
|
1247
|
+
}
|
1248
|
+
}, {
|
1249
|
+
"PolicyName": "WAFRuleAccess",
|
1250
|
+
"PolicyDocument": {
|
1251
|
+
"Version": "2012-10-17",
|
1252
|
+
"Statement": [{
|
1253
|
+
"Effect": "Allow",
|
1254
|
+
"Action": "waf:GetRule",
|
1255
|
+
"Resource": {
|
1256
|
+
"Fn::Join": ["", ["arn:aws:waf::", {
|
1257
|
+
"Ref": "AWS::AccountId"
|
1258
|
+
},
|
1259
|
+
":rule/*"
|
1260
|
+
]]
|
1261
|
+
}
|
1262
|
+
}]
|
1263
|
+
}
|
1264
|
+
}, {
|
1265
|
+
"PolicyName": "CloudFormationAccess",
|
1266
|
+
"PolicyDocument": {
|
1267
|
+
"Version": "2012-10-17",
|
1268
|
+
"Statement": [{
|
1269
|
+
"Effect": "Allow",
|
1270
|
+
"Action": "cloudformation:DescribeStacks",
|
1271
|
+
"Resource": {
|
1272
|
+
"Fn::Join": [
|
1273
|
+
"", [
|
1274
|
+
"arn:aws:cloudformation:", {
|
1275
|
+
"Ref": "AWS::Region"
|
1276
|
+
},
|
1277
|
+
":", {
|
1278
|
+
"Ref": "AWS::AccountId"
|
1279
|
+
},
|
1280
|
+
":stack/", {
|
1281
|
+
"Ref": "AWS::StackName"
|
1282
|
+
},
|
1283
|
+
"/*"
|
1284
|
+
]
|
1285
|
+
]
|
1286
|
+
}
|
1287
|
+
}]
|
1288
|
+
}
|
1289
|
+
}, {
|
1290
|
+
"PolicyName": "WAFGetChangeToken",
|
1291
|
+
"PolicyDocument": {
|
1292
|
+
"Statement": [{
|
1293
|
+
"Effect": "Allow",
|
1294
|
+
"Action": "waf:GetChangeToken",
|
1295
|
+
"Resource": "*"
|
1296
|
+
}]
|
1297
|
+
}
|
1298
|
+
}, {
|
1299
|
+
"PolicyName": "LogsAccess",
|
1300
|
+
"PolicyDocument": {
|
1301
|
+
"Version": "2012-10-17",
|
1302
|
+
"Statement": [{
|
1303
|
+
"Effect": "Allow",
|
1304
|
+
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
1305
|
+
"Resource": { "Fn::Join" : [":", ["arn:aws:logs",{"Ref" : "AWS::Region"},{ "Ref" : "AWS::AccountId" }, "log-group:/aws/lambda/*" ]]}
|
1306
|
+
}]
|
1307
|
+
}
|
1308
|
+
}]
|
1309
|
+
}
|
1310
|
+
},
|
1311
|
+
"LambdaWAFCustomResourceFunction": {
|
1312
|
+
"Type": "AWS::Lambda::Function",
|
1313
|
+
"Condition": "CreateWebACL",
|
1314
|
+
"DependsOn": "LambdaRoleCustomResource",
|
1315
|
+
"Properties": {
|
1316
|
+
"Description": {
|
1317
|
+
"Fn::Join": ["", [
|
1318
|
+
"This lambda function configures the Web ACL rules based on the features enabled in the CloudFormation template. Parameters: ", {
|
1319
|
+
"Ref": "SendAnonymousUsageData"
|
1320
|
+
},
|
1321
|
+
"."
|
1322
|
+
]]
|
1323
|
+
},
|
1324
|
+
"Handler": "custom-resource.lambda_handler",
|
1325
|
+
"Role": {
|
1326
|
+
"Fn::GetAtt": ["LambdaRoleCustomResource", "Arn"]
|
1327
|
+
},
|
1328
|
+
"Code": {
|
1329
|
+
"S3Bucket": {
|
1330
|
+
"Fn::Join": ["-", [
|
1331
|
+
"solutions", {
|
1332
|
+
"Ref": "AWS::Region"
|
1333
|
+
}
|
1334
|
+
]]
|
1335
|
+
},
|
1336
|
+
"S3Key": "aws-waf-security-automations/v3/custom-resource.zip"
|
1337
|
+
},
|
1338
|
+
"Runtime": "python2.7",
|
1339
|
+
"MemorySize": "128",
|
1340
|
+
"Timeout": "300"
|
1341
|
+
}
|
1342
|
+
},
|
1343
|
+
"WafWebAclRuleControler": {
|
1344
|
+
"Type": "Custom::WafWebAclRuleControler",
|
1345
|
+
"Condition": "CreateWebACL",
|
1346
|
+
"DependsOn": ["LambdaWAFCustomResourceFunction", "WAFWebACL"],
|
1347
|
+
"Properties": {
|
1348
|
+
"ServiceToken": {
|
1349
|
+
"Fn::GetAtt": ["LambdaWAFCustomResourceFunction", "Arn"]
|
1350
|
+
},
|
1351
|
+
"WAFWebACL": {
|
1352
|
+
"Ref": "WAFWebACL"
|
1353
|
+
},
|
1354
|
+
"Region": {
|
1355
|
+
"Ref": "AWS::Region"
|
1356
|
+
},
|
1357
|
+
"LambdaWAFReputationListsParserFunction": {
|
1358
|
+
"Fn::If": ["ReputationListsProtectionActivated", {
|
1359
|
+
"Fn::GetAtt": ["LambdaWAFReputationListsParserFunction", "Arn"]
|
1360
|
+
}, {
|
1361
|
+
"Ref": "AWS::NoValue"
|
1362
|
+
}]
|
1363
|
+
},
|
1364
|
+
"WAFReputationListsSet1": {
|
1365
|
+
"Fn::If": ["ReputationListsProtectionActivated", {
|
1366
|
+
"Ref": "WAFReputationListsSet1"
|
1367
|
+
}, {
|
1368
|
+
"Ref": "AWS::NoValue"
|
1369
|
+
}]
|
1370
|
+
},
|
1371
|
+
"WAFReputationListsSet2": {
|
1372
|
+
"Fn::If": ["ReputationListsProtectionActivated", {
|
1373
|
+
"Ref": "WAFReputationListsSet2"
|
1374
|
+
}, {
|
1375
|
+
"Ref": "AWS::NoValue"
|
1376
|
+
}]
|
1377
|
+
},
|
1378
|
+
"CloudFrontAccessLogBucket": {
|
1379
|
+
"Fn::If": ["LogParserActivated", {
|
1380
|
+
"Ref": "CloudFrontAccessLogBucket"
|
1381
|
+
}, {
|
1382
|
+
"Ref": "AWS::NoValue"
|
1383
|
+
}]
|
1384
|
+
},
|
1385
|
+
"LambdaWAFLogParserFunction": {
|
1386
|
+
"Fn::If": ["LogParserActivated", {
|
1387
|
+
"Fn::GetAtt": ["LambdaWAFLogParserFunction", "Arn"]
|
1388
|
+
}, {
|
1389
|
+
"Ref": "AWS::NoValue"
|
1390
|
+
}]
|
1391
|
+
},
|
1392
|
+
"WAFWhitelistRule": {
|
1393
|
+
"Fn::If": ["CreateWebACL", {
|
1394
|
+
"Ref": "WAFWhitelistRule"
|
1395
|
+
}, {
|
1396
|
+
"Ref": "AWS::NoValue"
|
1397
|
+
}]
|
1398
|
+
},
|
1399
|
+
"WAFBlacklistRule": {
|
1400
|
+
"Fn::If": ["LogParserActivated", {
|
1401
|
+
"Ref": "WAFBlacklistRule"
|
1402
|
+
}, {
|
1403
|
+
"Ref": "AWS::NoValue"
|
1404
|
+
}]
|
1405
|
+
},
|
1406
|
+
"WAFAutoBlockRule": {
|
1407
|
+
"Fn::If": ["LogParserActivated", {
|
1408
|
+
"Ref": "WAFAutoBlockRule"
|
1409
|
+
}, {
|
1410
|
+
"Ref": "AWS::NoValue"
|
1411
|
+
}]
|
1412
|
+
},
|
1413
|
+
"WAFIPReputationListsRule1": {
|
1414
|
+
"Fn::If": ["ReputationListsProtectionActivated", {
|
1415
|
+
"Ref": "WAFIPReputationListsRule1"
|
1416
|
+
}, {
|
1417
|
+
"Ref": "AWS::NoValue"
|
1418
|
+
}]
|
1419
|
+
},
|
1420
|
+
"WAFIPReputationListsRule2": {
|
1421
|
+
"Fn::If": ["ReputationListsProtectionActivated", {
|
1422
|
+
"Ref": "WAFIPReputationListsRule2"
|
1423
|
+
}, {
|
1424
|
+
"Ref": "AWS::NoValue"
|
1425
|
+
}]
|
1426
|
+
},
|
1427
|
+
"WAFBadBotRule": {
|
1428
|
+
"Fn::If": ["BadBotProtectionActivated", {
|
1429
|
+
"Ref": "WAFBadBotRule"
|
1430
|
+
}, {
|
1431
|
+
"Ref": "AWS::NoValue"
|
1432
|
+
}]
|
1433
|
+
},
|
1434
|
+
"WAFSqlInjectionRule": {
|
1435
|
+
"Fn::If": ["SqlInjectionProtectionActivated", {
|
1436
|
+
"Ref": "WAFSqlInjectionRule"
|
1437
|
+
}, {
|
1438
|
+
"Ref": "AWS::NoValue"
|
1439
|
+
}]
|
1440
|
+
},
|
1441
|
+
"WAFXssRule": {
|
1442
|
+
"Fn::If": ["CrossSiteScriptingProtectionActivated", {
|
1443
|
+
"Ref": "WAFXssRule"
|
1444
|
+
}, {
|
1445
|
+
"Ref": "AWS::NoValue"
|
1446
|
+
}]
|
1447
|
+
},
|
1448
|
+
"SqlInjectionProtection": {
|
1449
|
+
"Ref": "SqlInjectionProtectionParam"
|
1450
|
+
},
|
1451
|
+
"CrossSiteScriptingProtection": {
|
1452
|
+
"Ref": "CrossSiteScriptingProtectionParam"
|
1453
|
+
},
|
1454
|
+
"ActivateHttpFloodProtection": {
|
1455
|
+
"Ref": "ActivateHttpFloodProtectionParam"
|
1456
|
+
},
|
1457
|
+
"ActivateScansProbesProtection": {
|
1458
|
+
"Ref": "ActivateScansProbesProtectionParam"
|
1459
|
+
},
|
1460
|
+
"ActivateReputationListsProtection": {
|
1461
|
+
"Ref": "ActivateReputationListsProtectionParam"
|
1462
|
+
},
|
1463
|
+
"ActivateBadBotProtection": {
|
1464
|
+
"Ref": "ActivateBadBotProtectionParam"
|
1465
|
+
},
|
1466
|
+
"RequestThreshold": {
|
1467
|
+
"Ref": "RequestThreshold"
|
1468
|
+
},
|
1469
|
+
"ErrorThreshold": {
|
1470
|
+
"Ref": "ErrorThreshold"
|
1471
|
+
},
|
1472
|
+
"WAFBlockPeriod": {
|
1473
|
+
"Ref": "WAFBlockPeriod"
|
1474
|
+
},
|
1475
|
+
"SendAnonymousUsageData": {
|
1476
|
+
"Ref": "SendAnonymousUsageData"
|
1477
|
+
},
|
1478
|
+
"UUID": {
|
1479
|
+
"Fn::GetAtt": ["CreateUniqueID", "UUID"]
|
1480
|
+
},
|
1481
|
+
"LOG_TYPE": "cloudfront"
|
1482
|
+
}
|
1483
|
+
},
|
1484
|
+
"SolutionHelperRole": {
|
1485
|
+
"Type": "AWS::IAM::Role",
|
1486
|
+
"Properties": {
|
1487
|
+
"AssumeRolePolicyDocument": {
|
1488
|
+
"Version": "2012-10-17",
|
1489
|
+
"Statement": [{
|
1490
|
+
"Effect": "Allow",
|
1491
|
+
"Principal": {
|
1492
|
+
"Service": "lambda.amazonaws.com"
|
1493
|
+
},
|
1494
|
+
"Action": "sts:AssumeRole"
|
1495
|
+
}]
|
1496
|
+
},
|
1497
|
+
"Path": "/",
|
1498
|
+
"Policies": [{
|
1499
|
+
"PolicyName": "Solution_Helper_Permissions",
|
1500
|
+
"PolicyDocument": {
|
1501
|
+
"Version": "2012-10-17",
|
1502
|
+
"Statement": [{
|
1503
|
+
"Effect": "Allow",
|
1504
|
+
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
1505
|
+
"Resource": { "Fn::Join" : [":", ["arn:aws:logs",{"Ref" : "AWS::Region"},{ "Ref" : "AWS::AccountId" }, "log-group:/aws/lambda/*" ]]}
|
1506
|
+
}]
|
1507
|
+
}
|
1508
|
+
}]
|
1509
|
+
}
|
1510
|
+
},
|
1511
|
+
"SolutionHelper": {
|
1512
|
+
"Type": "AWS::Lambda::Function",
|
1513
|
+
"DependsOn": "SolutionHelperRole",
|
1514
|
+
"Properties": {
|
1515
|
+
"Handler": "solution-helper.lambda_handler",
|
1516
|
+
"Role": {
|
1517
|
+
"Fn::GetAtt": [
|
1518
|
+
"SolutionHelperRole",
|
1519
|
+
"Arn"
|
1520
|
+
]
|
1521
|
+
},
|
1522
|
+
"Description": "This lambda function executes generic common tasks to support this solution.",
|
1523
|
+
"Code": {
|
1524
|
+
"S3Bucket": {
|
1525
|
+
"Fn::Join": [
|
1526
|
+
"", [
|
1527
|
+
"solutions-", {
|
1528
|
+
"Ref": "AWS::Region"
|
1529
|
+
}
|
1530
|
+
]
|
1531
|
+
]
|
1532
|
+
},
|
1533
|
+
"S3Key": "library/solution-helper/v1/solution-helper.zip"
|
1534
|
+
},
|
1535
|
+
"Runtime": "python2.7",
|
1536
|
+
"Timeout": "300"
|
1537
|
+
}
|
1538
|
+
},
|
1539
|
+
"CreateUniqueID": {
|
1540
|
+
"Type": "Custom::CreateUUID",
|
1541
|
+
"DependsOn": "SolutionHelper",
|
1542
|
+
"Properties": {
|
1543
|
+
"ServiceToken": {
|
1544
|
+
"Fn::GetAtt": [
|
1545
|
+
"SolutionHelper",
|
1546
|
+
"Arn"
|
1547
|
+
]
|
1548
|
+
},
|
1549
|
+
"Region": {
|
1550
|
+
"Ref": "AWS::Region"
|
1551
|
+
},
|
1552
|
+
"CreateUniqueID": "true"
|
1553
|
+
}
|
1554
|
+
}
|
1555
|
+
},
|
1556
|
+
"Outputs": {
|
1557
|
+
"BadBotHoneypotEndpoint": {
|
1558
|
+
"Description": "Bad Bot Honeypot Endpoint",
|
1559
|
+
"Value": {
|
1560
|
+
"Fn::Join": ["", [
|
1561
|
+
"https://", {
|
1562
|
+
"Ref": "ApiGatewayBadBot"
|
1563
|
+
},
|
1564
|
+
".execute-api.", {
|
1565
|
+
"Ref": "AWS::Region"
|
1566
|
+
},
|
1567
|
+
".amazonaws.com/", {
|
1568
|
+
"Ref": "ApiGatewayBadBotStage"
|
1569
|
+
}
|
1570
|
+
]]
|
1571
|
+
},
|
1572
|
+
"Condition": "BadBotProtectionActivated"
|
1573
|
+
}
|
1574
|
+
}
|
1575
|
+
}
|