cfn-guardian 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +52 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +412 -0
  7. data/Rakefile +2 -0
  8. data/cfn-guardian.gemspec +39 -0
  9. data/exe/cfn-guardian +4 -0
  10. data/lib/cfnguardian.rb +146 -0
  11. data/lib/cfnguardian/compile.rb +116 -0
  12. data/lib/cfnguardian/deploy.rb +144 -0
  13. data/lib/cfnguardian/log.rb +40 -0
  14. data/lib/cfnguardian/models/alarm.rb +292 -0
  15. data/lib/cfnguardian/models/check.rb +114 -0
  16. data/lib/cfnguardian/models/event.rb +192 -0
  17. data/lib/cfnguardian/resources/amazonmq_broker.rb +45 -0
  18. data/lib/cfnguardian/resources/apigateway.rb +34 -0
  19. data/lib/cfnguardian/resources/application_targetgroup.rb +31 -0
  20. data/lib/cfnguardian/resources/autoscaling_group.rb +22 -0
  21. data/lib/cfnguardian/resources/base.rb +116 -0
  22. data/lib/cfnguardian/resources/cloudfront_distribution.rb +26 -0
  23. data/lib/cfnguardian/resources/domain_expiry.rb +30 -0
  24. data/lib/cfnguardian/resources/dynamodb_table.rb +48 -0
  25. data/lib/cfnguardian/resources/ec2_instance.rb +24 -0
  26. data/lib/cfnguardian/resources/ecs_cluster.rb +44 -0
  27. data/lib/cfnguardian/resources/ecs_service.rb +32 -0
  28. data/lib/cfnguardian/resources/elastic_file_system.rb +17 -0
  29. data/lib/cfnguardian/resources/elastic_loadbalancer.rb +26 -0
  30. data/lib/cfnguardian/resources/elasticache_replication_group.rb +45 -0
  31. data/lib/cfnguardian/resources/http.rb +49 -0
  32. data/lib/cfnguardian/resources/lambda.rb +38 -0
  33. data/lib/cfnguardian/resources/network_targetgroup.rb +31 -0
  34. data/lib/cfnguardian/resources/nrpe.rb +42 -0
  35. data/lib/cfnguardian/resources/rds_cluster_instance.rb +30 -0
  36. data/lib/cfnguardian/resources/rds_instance.rb +45 -0
  37. data/lib/cfnguardian/resources/redshift_cluster.rb +30 -0
  38. data/lib/cfnguardian/resources/sql.rb +37 -0
  39. data/lib/cfnguardian/resources/sqs_queue.rb +23 -0
  40. data/lib/cfnguardian/s3.rb +35 -0
  41. data/lib/cfnguardian/stacks/main.rb +149 -0
  42. data/lib/cfnguardian/stacks/resources.rb +80 -0
  43. data/lib/cfnguardian/string.rb +19 -0
  44. data/lib/cfnguardian/validate.rb +80 -0
  45. data/lib/cfnguardian/version.rb +4 -0
  46. metadata +215 -0
@@ -0,0 +1,40 @@
1
+ require 'logger'
2
+
3
+ module Logging
4
+ class << self
5
+ def colors
6
+ @colors ||= {
7
+ ERROR: 31, # red
8
+ WARN: 33, # yellow
9
+ INFO: 0,
10
+ DEBUG: 32 # green
11
+ }
12
+ end
13
+
14
+ def logger
15
+ @logger ||= Logger.new($stdout)
16
+ @logger.level = Logger::DEBUG
17
+ @logger.formatter = proc do |severity, datetime, progname, msg|
18
+ "\e[#{colors[severity.to_sym]}m#{severity}: #{msg}\e[0m\n"
19
+ end
20
+ @logger
21
+ end
22
+
23
+ def logger=(logger)
24
+ @logger = logger
25
+ end
26
+ end
27
+
28
+ # Addition
29
+ def self.included(base)
30
+ class << base
31
+ def logger
32
+ Logging.logger
33
+ end
34
+ end
35
+ end
36
+
37
+ def logger
38
+ Logging.logger
39
+ end
40
+ end
@@ -0,0 +1,292 @@
1
+ require 'cfnguardian/string'
2
+
3
+ module CfnGuardian
4
+ module Models
5
+ class Alarm
6
+
7
+ attr_reader :type
8
+ attr_accessor :class,
9
+ :name,
10
+ :metric_name,
11
+ :namespace,
12
+ :dimensions,
13
+ :threshold,
14
+ :period,
15
+ :evaluation_periods,
16
+ :comparison_operator,
17
+ :statistic,
18
+ :actions_enabled,
19
+ :enabled,
20
+ :resource,
21
+ :alarm_action,
22
+ :treat_missing_data,
23
+ :datapoints_to_alarm,
24
+ :extended_statistic,
25
+ :evaluate_low_sample_count_percentile,
26
+ :unit
27
+
28
+ def initialize(resource)
29
+ @type = 'Alarm'
30
+ @class = nil
31
+ @name = ''
32
+ @metric_name = nil
33
+ @namespace = nil
34
+ @dimensions = {}
35
+ @threshold = 0
36
+ @period = 60
37
+ @evaluation_periods = 1
38
+ @comparison_operator = 'GreaterThanThreshold'
39
+ @statistic = 'Maximum'
40
+ @actions_enabled = true
41
+ @datapoints_to_alarm = nil
42
+ @extended_statistic = nil
43
+ @evaluate_low_sample_count_percentile = nil
44
+ @unit = nil
45
+ @enabled = true
46
+ @resource_name = Digest::MD5.hexdigest resource['Id']
47
+ @resource = resource['Id']
48
+ @alarm_action = 'Critical'
49
+ @treat_missing_data = nil
50
+ end
51
+
52
+ def metric_name=(metric_name)
53
+ raise ArgumentError.new("metric_name '#{metric_name}' must be of type String, provided type '#{metric_name.class}'") unless metric_name.is_a?(String)
54
+ @metric_name=metric_name
55
+ end
56
+
57
+ def to_h
58
+ Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] } ]
59
+ end
60
+
61
+ end
62
+
63
+
64
+ class ApiGatewayAlarm < Alarm
65
+ def initialize(resource)
66
+ super(resource)
67
+ @class = 'ApiGateway'
68
+ @namespace = 'AWS/ApiGateway'
69
+ @dimensions = { ApiName: resource['Id'] }
70
+ end
71
+ end
72
+
73
+ class ApplicationTargetGroupAlarm < Alarm
74
+ def initialize(resource)
75
+ super(resource)
76
+ @class = 'ApplicationTargetGroup'
77
+ @namespace = 'AWS/ApplicationELB'
78
+ @dimensions = {
79
+ TargetGroup: resource['Id'],
80
+ LoadBalancer: resource['LoadBalancer']
81
+ }
82
+ end
83
+ end
84
+
85
+ class AmazonMQBrokerAlarm < Alarm
86
+ def initialize(resource)
87
+ super(resource)
88
+ @class = 'AmazonMQBroker'
89
+ @namespace = 'AWS/AmazonMQ'
90
+ @dimensions = { Broker: resource['Id'] }
91
+ end
92
+ end
93
+
94
+ class CloudFrontDistributionAlarm < Alarm
95
+ def initialize(resource)
96
+ super(resource)
97
+ @class = 'CloudFrontDistribution'
98
+ @namespace = 'AWS/CloudFront'
99
+ @dimensions = {
100
+ DistributionId: resource['Id'],
101
+ Region: 'Global'
102
+ }
103
+ @statistic = 'Average'
104
+ @evaluation_periods = 5
105
+ end
106
+ end
107
+
108
+ class AutoScalingGroupAlarm < Alarm
109
+ def initialize(resource)
110
+ super(resource)
111
+ @class = 'AutoScalingGroup'
112
+ @namespace = 'AWS/EC2'
113
+ @dimensions = { AutoScalingGroupName: resource['Id'] }
114
+ end
115
+ end
116
+
117
+ class DomainExpiryAlarm < Alarm
118
+ def initialize(resource)
119
+ super(resource)
120
+ @class = 'DomainExpiry'
121
+ @namespace = 'DNS'
122
+ @dimensions = { Domain: resource['Id'] }
123
+ @comparison_operator = 'LessThanThreshold'
124
+ end
125
+ end
126
+
127
+ class DynamoDBTableAlarm < Alarm
128
+ def initialize(resource)
129
+ super(resource)
130
+ @class = 'DynamoDBTable'
131
+ @namespace = 'AWS/DynamoDB'
132
+ @dimensions = { TableName: resource['Id'] }
133
+ end
134
+ end
135
+
136
+ class Ec2InstanceAlarm < Alarm
137
+ def initialize(resource)
138
+ super(resource)
139
+ @class = 'Ec2Instance'
140
+ @namespace = 'AWS/EC2'
141
+ @dimensions = { InstanceId: resource['Id'] }
142
+ end
143
+ end
144
+
145
+ class ECSClusterAlarm < Alarm
146
+ def initialize(resource)
147
+ super(resource)
148
+ @class = 'ECSCluster'
149
+ @namespace = 'AWS/ECS'
150
+ @dimensions = { ClusterName: resource['Id'] }
151
+ @threshold = 75
152
+ @alarm_action = 'Warning'
153
+ @evaluation_periods = 10
154
+ end
155
+ end
156
+
157
+ class ECSServiceAlarm < Alarm
158
+ def initialize(resource)
159
+ super(resource)
160
+ @class = 'ECSService'
161
+ @namespace = 'AWS/ECS'
162
+ @dimensions = {
163
+ ServiceName: resource['Id'],
164
+ ClusterName: resource['Cluster']
165
+ }
166
+ end
167
+ end
168
+
169
+ class ElastiCacheReplicationGroupAlarm < Alarm
170
+ def initialize(resource)
171
+ super(resource)
172
+ @class = 'ElastiCacheReplicationGroup'
173
+ @namespace = 'AWS/ElastiCache'
174
+ @dimensions = { CacheClusterId: resource['Id'] }
175
+ end
176
+ end
177
+
178
+ class ElasticLoadBalancerAlarm < Alarm
179
+ def initialize(resource)
180
+ super(resource)
181
+ @class = 'ElasticLoadBalancer'
182
+ @namespace = 'AWS/ELB'
183
+ @dimensions = { LoadBalancerName: resource['Id'] }
184
+ end
185
+ end
186
+
187
+ class ElasticFileSystemAlarm < Alarm
188
+ def initialize(resource)
189
+ super(resource)
190
+ @class = 'ElasticFileSystem'
191
+ @namespace = 'AWS/EFS'
192
+ @dimensions = { FileSystemId: resource['Id'] }
193
+ end
194
+ end
195
+
196
+ class HttpAlarm < Alarm
197
+ def initialize(resource)
198
+ super(resource)
199
+ @class = 'Http'
200
+ @namespace = 'HttpCheck'
201
+ @dimensions = { Endpoint: resource['Id'] }
202
+ @comparison_operator = 'LessThanThreshold'
203
+ @threshold = 1
204
+ @evaluation_periods = 2
205
+ end
206
+ end
207
+
208
+ class NrpeAlarm < Alarm
209
+ def initialize(resource,environment)
210
+ super(resource)
211
+ @class = 'Nrpe'
212
+ @namespace = 'NRPE'
213
+ @dimensions = { Host: "#{environment}-#{resource['Id']}" }
214
+ @treat_missing_data = 'breaching'
215
+ @evaluation_periods = 2
216
+ end
217
+ end
218
+
219
+ class LambdaAlarm < Alarm
220
+ def initialize(resource)
221
+ super(resource)
222
+ @class = 'Lambda'
223
+ @namespace = 'AWS/Lambda'
224
+ @dimensions = { FunctionName: resource['Id'] }
225
+ @statistic = 'Average'
226
+ @evaluation_periods = 5
227
+ end
228
+ end
229
+
230
+ class NetworkTargetGroupAlarm < Alarm
231
+ def initialize(resource)
232
+ super(resource)
233
+ @class = 'NetworkTargetGroup'
234
+ @namespace = 'AWS/NetworkELB'
235
+ @dimensions = {
236
+ TargetGroup: resource['Id'],
237
+ LoadBalancer: resource['LoadBalancer']
238
+ }
239
+ end
240
+ end
241
+
242
+ class RedshiftClusterAlarm < Alarm
243
+ def initialize(resource)
244
+ super(resource)
245
+ @class = 'RedshiftCluster'
246
+ @namespace = 'AWS/Redshift'
247
+ @dimensions = { ClusterIdentifier: resource['Id'] }
248
+ end
249
+ end
250
+
251
+ class RDSClusterInstanceAlarm < Alarm
252
+ def initialize(resource)
253
+ super(resource)
254
+ @class = 'RDSClusterInstance'
255
+ @namespace = 'AWS/RDS'
256
+ @dimensions = { DBInstanceIdentifier: resource['Id'] }
257
+ end
258
+ end
259
+
260
+ class RDSInstanceAlarm < Alarm
261
+ def initialize(resource)
262
+ super(resource)
263
+ @class = 'RDSInstance'
264
+ @namespace = 'AWS/RDS'
265
+ @dimensions = { DBInstanceIdentifier: resource['Id'] }
266
+ end
267
+ end
268
+
269
+ class SqlAlarm < Alarm
270
+ def initialize(resource)
271
+ super(resource)
272
+ @class = 'Sql'
273
+ @namespace = 'SQL'
274
+ @dimensions = { Host: resource['Id'] }
275
+ @treat_missing_data = 'breaching'
276
+ @evaluation_periods = 1
277
+ end
278
+ end
279
+
280
+ class SQSQueueAlarm < Alarm
281
+ def initialize(resource)
282
+ super(resource)
283
+ @class = 'SQSQueue'
284
+ @namespace = 'AWS/SQS'
285
+ @dimensions = { QueueName: resource['Id'] }
286
+ @statistic = 'Average'
287
+ @period = 300
288
+ end
289
+ end
290
+
291
+ end
292
+ end
@@ -0,0 +1,114 @@
1
+ require 'cfnguardian/string'
2
+
3
+ module CfnGuardian
4
+ module Models
5
+ class Check
6
+
7
+ attr_reader :type
8
+ attr_accessor :class,
9
+ :name,
10
+ :handler,
11
+ :version,
12
+ :runtime,
13
+ :environment
14
+
15
+ def initialize(resource)
16
+ @type = 'Check'
17
+ @class = nil
18
+ @name = nil
19
+ @package = nil
20
+ @handler = nil
21
+ @version = nil
22
+ @runtime = nil
23
+ @environment = ''
24
+ end
25
+
26
+ def to_h
27
+ Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] } ]
28
+ end
29
+ end
30
+
31
+ class HttpCheck < Check
32
+ def initialize(resource)
33
+ super(resource)
34
+ @class = 'Http'
35
+ @name = 'HttpCheck'
36
+ @package = 'aws-lambda-http-check'
37
+ @handler = 'handler.main'
38
+ @version = '0.1'
39
+ @runtime = 'python3.6'
40
+ end
41
+ end
42
+
43
+ class NrpeCheck < Check
44
+ attr_accessor :subnets, :vpc
45
+
46
+ def initialize(resource)
47
+ super(resource)
48
+ @class = 'Nrpe'
49
+ @name = 'NrpeCheck'
50
+ @package = 'aws-lambda-nrpe-check'
51
+ @handler = 'main'
52
+ @version = '0.2'
53
+ @runtime = 'go1.x'
54
+ @subnets = resource['Subnets']
55
+ @vpc = resource['VpcId']
56
+ @environment = resource['Environment']
57
+ end
58
+ end
59
+
60
+ class SslCheck < Check
61
+ def initialize(resource)
62
+ super(resource)
63
+ @class = 'Ssl'
64
+ @name = 'SslCheck'
65
+ @package = 'aws-lambda-ssl-check'
66
+ @handler = 'main'
67
+ @version = '0.1'
68
+ @runtime = 'go1.x'
69
+ end
70
+ end
71
+
72
+ class DomainExpiryCheck < Check
73
+ def initialize(resource)
74
+ super(resource)
75
+ @class = 'DomainExpiry'
76
+ @name = 'DomainExpiryCheck'
77
+ @package = 'aws-lambda-dns-check'
78
+ @handler = 'main'
79
+ @version = '0.1'
80
+ @runtime = 'go1.x'
81
+ end
82
+ end
83
+
84
+ class SqlCheck < Check
85
+ attr_accessor :subnets, :vpc
86
+
87
+ def initialize(resource)
88
+ super(resource)
89
+ @class = 'Sql'
90
+ @name = 'SqlCheck'
91
+ @package = 'aws-lambda-sql-check'
92
+ @handler = 'main'
93
+ @version = '0.1'
94
+ @runtime = 'go1.x'
95
+ @subnets = resource['Subnets']
96
+ @vpc = resource['VpcId']
97
+ @environment = resource['Environment']
98
+ end
99
+ end
100
+
101
+ class ContainerInstanceCheck < Check
102
+ def initialize(resource)
103
+ super(resource)
104
+ @class = 'ContainerInstance'
105
+ @name = 'ContainerInstanceCheck'
106
+ @package = 'aws-lambda-ecs-container-instance-check'
107
+ @handler = 'handler.run_check'
108
+ @version = '0.1'
109
+ @runtime = 'python3.6'
110
+ end
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,192 @@
1
+ require 'cfnguardian/string'
2
+
3
+ module CfnGuardian
4
+ module Models
5
+ class Event
6
+
7
+ attr_reader :type
8
+ attr_accessor :class,
9
+ :target,
10
+ :hash,
11
+ :name,
12
+ :cron,
13
+ :enabled,
14
+ :resource
15
+
16
+ def initialize(resource)
17
+ @type = 'Event'
18
+ @class = nil
19
+ @target = nil
20
+ @hash = Digest::MD5.hexdigest resource['Id']
21
+ @name = @hash
22
+ @cron = "* * * * ? *"
23
+ @enabled = true
24
+ @resource = resource['Id'].to_resource_name
25
+ end
26
+
27
+ def to_h
28
+ return {
29
+ type: @type,
30
+ class: @class,
31
+ target: @target,
32
+ hash: @hash,
33
+ name: @name,
34
+ cron: @cron,
35
+ enabled: @enabled,
36
+ resource: @resource,
37
+ payload: event_payload()
38
+ }
39
+ end
40
+
41
+ def event_payload
42
+ {}.to_json
43
+ end
44
+
45
+ end
46
+
47
+ class HttpEvent < Event
48
+
49
+ attr_accessor :endpoint,
50
+ :method,
51
+ :timeout,
52
+ :status_code,
53
+ :body_regex,
54
+ :headers,
55
+ :payload
56
+
57
+ def initialize(resource)
58
+ super(resource)
59
+ @class = 'Http'
60
+ @name = 'HttpEvent'
61
+ @target = 'HttpCheckFunction'
62
+ @endpoint = resource['Id']
63
+ @method = resource.fetch('Method','GET')
64
+ @timeout = resource.fetch('Timeout',50)
65
+ @status_code = resource.fetch('StatusCode',200)
66
+ @body_regex = resource.fetch('BodyRegex',nil)
67
+ @headers = resource.fetch('Headers',nil)
68
+ @payload = resource.fetch('Payload',nil)
69
+ end
70
+
71
+ def event_payload
72
+ payload = {
73
+ 'ENDPOINT' => @endpoint,
74
+ 'METHOD' => @method,
75
+ 'TIMEOUT' => @timeout,
76
+ 'STATUS_CODE_MATCH' => @status_code
77
+ }
78
+ payload['BODY_REGEX_MATCH'] = @body_regex unless @body_regex.nil?
79
+ payload['HEADERS'] = @headers unless @headers.nil?
80
+ payload['PAYLOAD'] = @payload unless @payload.nil?
81
+ return payload.to_json
82
+ end
83
+ end
84
+
85
+ class NrpeEvent < Event
86
+ def initialize(resource,environment,command)
87
+ super(resource)
88
+ @class = 'Nrpe'
89
+ @name = 'NrpeEvent'
90
+ @target = "NrpeCheckFunction#{environment}"
91
+ @host = resource['Id']
92
+ @environment = environment
93
+ @region = resource.fetch('Region',"${AWS::Region}")
94
+ @command = command
95
+ end
96
+
97
+ def event_payload
98
+ return {
99
+ 'host' => @host,
100
+ 'environment' => @environment,
101
+ 'region' => @region,
102
+ 'cmd' => @command
103
+ }.to_json
104
+ end
105
+ end
106
+
107
+ class SslEvent < Event
108
+ def initialize(resource)
109
+ super(resource)
110
+ @class = 'Ssl'
111
+ @name = 'SslEvent'
112
+ @target = 'SslCheckFunction'
113
+ @cron = "0 12 * * ? *"
114
+ @url = resource['Id']
115
+ @region = resource.fetch('Region',"${AWS::Region}")
116
+ end
117
+
118
+ def event_payload
119
+ return {
120
+ 'Url' => @url,
121
+ 'Region' => @region
122
+ }.to_json
123
+ end
124
+ end
125
+
126
+ class DomainExpiryEvent < Event
127
+
128
+ attr_accessor :domain,
129
+ :region
130
+
131
+ def initialize(resource)
132
+ super(resource)
133
+ @class = 'DomainExpiry'
134
+ @name = 'DomainExpiryEvent'
135
+ @target = 'DomainExpiryCheckFunction'
136
+ @cron = "0 12 * * ? *"
137
+ @domain = resource['Id']
138
+ @region = resource.fetch('Region',"${AWS::Region}")
139
+ end
140
+
141
+ def event_payload
142
+ {'Domain' => @domain}.to_json
143
+ end
144
+ end
145
+
146
+ class SqlEvent < Event
147
+ def initialize(resource,query)
148
+ super(resource)
149
+ @class = 'Sql'
150
+ @name = 'SqlEvent'
151
+ @target = 'SqlCheckFunction'
152
+ @host = resource['Id']
153
+ @engine = resource['Engine']
154
+ @port = resource['Port']
155
+ @ssm_username = resource['SSMUsername']
156
+ @ssm_password = resource['SSMPassword']
157
+ @query = query
158
+ @region = resource.fetch('Region',"${AWS::Region}")
159
+ @test_type = '1-row-1-value-zero-is-good'
160
+ end
161
+
162
+ def event_payload
163
+ return {
164
+ 'Host' => @host,
165
+ 'Engine' => @engine,
166
+ 'Port' => @port,
167
+ 'SqlCall' => @query,
168
+ 'SSMUsername' => @ssm_username,
169
+ 'SSMPassword' => @ssm_password,
170
+ 'Region' => @region,
171
+ 'TestType' => @test_type
172
+ }.to_json
173
+ end
174
+ end
175
+
176
+ class ContainerInstanceEvent < Event
177
+ def initialize(resource)
178
+ super(resource)
179
+ @class = 'ContainerInstance'
180
+ @name = 'ContainerInstanceEvent'
181
+ @target = 'ContainerInstanceCheckFunction'
182
+ @cron = "0/5 * * * ? *"
183
+ @cluster = resource['Id']
184
+ end
185
+
186
+ def event_payload
187
+ {'CLUSTER' => @cluster}.to_json
188
+ end
189
+ end
190
+
191
+ end
192
+ end