cfn-guardian 0.1.0 → 0.3.3

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/Dockerfile +19 -0
  4. data/Gemfile.lock +31 -13
  5. data/README.md +441 -42
  6. data/cfn-guardian.gemspec +6 -2
  7. data/lib/cfnguardian.rb +301 -27
  8. data/lib/cfnguardian/cloudwatch.rb +121 -0
  9. data/lib/cfnguardian/codecommit.rb +54 -0
  10. data/lib/cfnguardian/codepipeline.rb +138 -0
  11. data/lib/cfnguardian/compile.rb +58 -17
  12. data/lib/cfnguardian/config/defaults.yaml +94 -0
  13. data/lib/cfnguardian/display_formatter.rb +164 -0
  14. data/lib/cfnguardian/drift.rb +79 -0
  15. data/lib/cfnguardian/log.rb +0 -1
  16. data/lib/cfnguardian/models/alarm.rb +98 -36
  17. data/lib/cfnguardian/models/check.rb +103 -26
  18. data/lib/cfnguardian/models/composite.rb +21 -0
  19. data/lib/cfnguardian/models/event.rb +164 -40
  20. data/lib/cfnguardian/models/metric_filter.rb +28 -0
  21. data/lib/cfnguardian/resources/application_targetgroup.rb +2 -0
  22. data/lib/cfnguardian/resources/base.rb +38 -16
  23. data/lib/cfnguardian/resources/ecs_service.rb +2 -2
  24. data/lib/cfnguardian/resources/http.rb +16 -1
  25. data/lib/cfnguardian/resources/internal_http.rb +74 -0
  26. data/lib/cfnguardian/resources/internal_port.rb +33 -0
  27. data/lib/cfnguardian/resources/internal_sftp.rb +58 -0
  28. data/lib/cfnguardian/resources/log_group.rb +26 -0
  29. data/lib/cfnguardian/resources/network_targetgroup.rb +1 -0
  30. data/lib/cfnguardian/resources/port.rb +25 -0
  31. data/lib/cfnguardian/resources/rds_instance.rb +2 -0
  32. data/lib/cfnguardian/resources/sftp.rb +50 -0
  33. data/lib/cfnguardian/resources/sql.rb +1 -1
  34. data/lib/cfnguardian/resources/tls.rb +66 -0
  35. data/lib/cfnguardian/s3.rb +3 -2
  36. data/lib/cfnguardian/stacks/main.rb +86 -65
  37. data/lib/cfnguardian/stacks/resources.rb +81 -42
  38. data/lib/cfnguardian/string.rb +12 -0
  39. data/lib/cfnguardian/version.rb +1 -1
  40. metadata +102 -5
@@ -0,0 +1,21 @@
1
+ module CfnGuardian
2
+ module Models
3
+ class Composite
4
+
5
+ attr_reader :type
6
+ attr_accessor :name,
7
+ :description,
8
+ :rule,
9
+ :alarm_action
10
+
11
+ def initialize(name,params = {})
12
+ @type = 'Composite'
13
+ @name = name
14
+ @description = params.fetch('Description', '')
15
+ @rule = params.fetch('Rule', 'FALSE')
16
+ @alarm_action = params.fetch('Action', nil)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -5,43 +5,30 @@ module CfnGuardian
5
5
  class Event
6
6
 
7
7
  attr_reader :type
8
- attr_accessor :class,
8
+ attr_accessor :group,
9
9
  :target,
10
10
  :hash,
11
11
  :name,
12
12
  :cron,
13
13
  :enabled,
14
- :resource
14
+ :resource,
15
+ :environment,
16
+ :payload,
17
+ :ssm_parameters
15
18
 
16
19
  def initialize(resource)
17
20
  @type = 'Event'
18
- @class = nil
21
+ @group = nil
19
22
  @target = nil
20
23
  @hash = Digest::MD5.hexdigest resource['Id']
21
24
  @name = @hash
22
25
  @cron = "* * * * ? *"
23
26
  @enabled = true
24
27
  @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
-
28
+ @environment = ""
29
+ @payload = {}.to_json
30
+ @ssm_parameters = []
31
+ end
45
32
  end
46
33
 
47
34
  class HttpEvent < Event
@@ -56,7 +43,7 @@ module CfnGuardian
56
43
 
57
44
  def initialize(resource)
58
45
  super(resource)
59
- @class = 'Http'
46
+ @group = 'Http'
60
47
  @name = 'HttpEvent'
61
48
  @target = 'HttpCheckFunction'
62
49
  @endpoint = resource['Id']
@@ -66,9 +53,10 @@ module CfnGuardian
66
53
  @body_regex = resource.fetch('BodyRegex',nil)
67
54
  @headers = resource.fetch('Headers',nil)
68
55
  @payload = resource.fetch('Payload',nil)
56
+ @compressed = resource.fetch('Compressed',false)
69
57
  end
70
58
 
71
- def event_payload
59
+ def payload
72
60
  payload = {
73
61
  'ENDPOINT' => @endpoint,
74
62
  'METHOD' => @method,
@@ -78,14 +66,56 @@ module CfnGuardian
78
66
  payload['BODY_REGEX_MATCH'] = @body_regex unless @body_regex.nil?
79
67
  payload['HEADERS'] = @headers unless @headers.nil?
80
68
  payload['PAYLOAD'] = @payload unless @payload.nil?
69
+ payload['COMPRESSED'] = '1' if @compressed
81
70
  return payload.to_json
82
71
  end
83
72
  end
84
73
 
85
- class NrpeEvent < Event
74
+ class InternalHttpEvent < HttpEvent
75
+ def initialize(resource,environment)
76
+ super(resource)
77
+ @group = 'InternalHttp'
78
+ @name = 'InternalHttpEvent'
79
+ @target = "InternalHttpCheckFunction#{environment}"
80
+ @environment = environment
81
+ end
82
+ end
83
+
84
+ class PortEvent < Event
85
+ def initialize(resource)
86
+ super(resource)
87
+ @group = 'Port'
88
+ @name = 'PortEvent'
89
+ @target = 'PortCheckFunction'
90
+ @hostname = resource['Id']
91
+ @port = resource['Port']
92
+ @timeout = resource.fetch('Timeout',120)
93
+ end
94
+
95
+ def payload
96
+ return {
97
+ 'HOSTNAME' => @hostname,
98
+ 'PORT' => @port,
99
+ 'TIMEOUT' => @timeout,
100
+ 'STATUS_CODE_MATCH' => @status_code
101
+ }.to_json
102
+ end
103
+ end
104
+
105
+ class InternalPortEvent < PortEvent
106
+ def initialize(resource,environment)
107
+ super(resource)
108
+ @group = 'InternalPort'
109
+ @name = 'InternalPortEvent'
110
+ @target = "InternalPortCheckFunction#{environment}"
111
+ @environment = environment
112
+ end
113
+ end
114
+
115
+ class NrpeEvent < Event
86
116
  def initialize(resource,environment,command)
87
117
  super(resource)
88
- @class = 'Nrpe'
118
+ @group = 'Nrpe'
89
119
  @name = 'NrpeEvent'
90
120
  @target = "NrpeCheckFunction#{environment}"
91
121
  @host = resource['Id']
@@ -94,7 +124,7 @@ module CfnGuardian
94
124
  @command = command
95
125
  end
96
126
 
97
- def event_payload
127
+ def payload
98
128
  return {
99
129
  'host' => @host,
100
130
  'environment' => @environment,
@@ -107,7 +137,7 @@ module CfnGuardian
107
137
  class SslEvent < Event
108
138
  def initialize(resource)
109
139
  super(resource)
110
- @class = 'Ssl'
140
+ @group = 'Ssl'
111
141
  @name = 'SslEvent'
112
142
  @target = 'SslCheckFunction'
113
143
  @cron = "0 12 * * ? *"
@@ -115,7 +145,7 @@ module CfnGuardian
115
145
  @region = resource.fetch('Region',"${AWS::Region}")
116
146
  end
117
147
 
118
- def event_payload
148
+ def payload
119
149
  return {
120
150
  'Url' => @url,
121
151
  'Region' => @region
@@ -123,6 +153,16 @@ module CfnGuardian
123
153
  end
124
154
  end
125
155
 
156
+ class InternalSslEvent < SslEvent
157
+ def initialize(resource,environment)
158
+ super(resource)
159
+ @group = 'InternalSsl'
160
+ @name = 'InternalSslEvent'
161
+ @target = "InternalSslCheckFunction#{environment}"
162
+ @environment = environment
163
+ end
164
+ end
165
+
126
166
  class DomainExpiryEvent < Event
127
167
 
128
168
  attr_accessor :domain,
@@ -130,7 +170,7 @@ module CfnGuardian
130
170
 
131
171
  def initialize(resource)
132
172
  super(resource)
133
- @class = 'DomainExpiry'
173
+ @group = 'DomainExpiry'
134
174
  @name = 'DomainExpiryEvent'
135
175
  @target = 'DomainExpiryCheckFunction'
136
176
  @cron = "0 12 * * ? *"
@@ -138,17 +178,17 @@ module CfnGuardian
138
178
  @region = resource.fetch('Region',"${AWS::Region}")
139
179
  end
140
180
 
141
- def event_payload
142
- {'Domain' => @domain}.to_json
181
+ def payload
182
+ return {'Domain' => @domain}.to_json
143
183
  end
144
184
  end
145
185
 
146
186
  class SqlEvent < Event
147
- def initialize(resource,query)
187
+ def initialize(resource,query,environment)
148
188
  super(resource)
149
- @class = 'Sql'
189
+ @group = 'Sql'
150
190
  @name = 'SqlEvent'
151
- @target = 'SqlCheckFunction'
191
+ @target = "SqlCheckFunction#{environment}"
152
192
  @host = resource['Id']
153
193
  @engine = resource['Engine']
154
194
  @port = resource['Port']
@@ -157,9 +197,10 @@ module CfnGuardian
157
197
  @query = query
158
198
  @region = resource.fetch('Region',"${AWS::Region}")
159
199
  @test_type = '1-row-1-value-zero-is-good'
200
+ @environment = environment
160
201
  end
161
202
 
162
- def event_payload
203
+ def payload
163
204
  return {
164
205
  'Host' => @host,
165
206
  'Engine' => @engine,
@@ -171,20 +212,103 @@ module CfnGuardian
171
212
  'TestType' => @test_type
172
213
  }.to_json
173
214
  end
215
+
216
+ def ssm_parameters
217
+ params = []
218
+ params << @ssm_username
219
+ params << @ssm_password
220
+ return params
221
+ end
174
222
  end
175
223
 
176
224
  class ContainerInstanceEvent < Event
177
225
  def initialize(resource)
178
226
  super(resource)
179
- @class = 'ContainerInstance'
227
+ @group = 'ContainerInstance'
180
228
  @name = 'ContainerInstanceEvent'
181
229
  @target = 'ContainerInstanceCheckFunction'
182
230
  @cron = "0/5 * * * ? *"
183
231
  @cluster = resource['Id']
184
232
  end
185
233
 
186
- def event_payload
187
- {'CLUSTER' => @cluster}.to_json
234
+ def payload
235
+ return {'CLUSTER' => @cluster}.to_json
236
+ end
237
+ end
238
+
239
+ class SFTPEvent < Event
240
+ def initialize(resource)
241
+ super(resource)
242
+ @group = 'SFTP'
243
+ @name = 'SFTPEvent'
244
+ @target = 'SFTPCheckFunction'
245
+ @cron = "0/5 * * * ? *"
246
+ @host = resource['Id']
247
+ @user = resource['User']
248
+ @port = resource.fetch('Port', nil)
249
+ @server_key = resource.fetch('ServerKey', nil)
250
+ @password = resource.fetch('Password', nil)
251
+ @private_key = resource.fetch('PrivateKey', nil)
252
+ @private_key_pass = resource.fetch('PrivateKeyPass', nil)
253
+ @file = resource.fetch('File', nil)
254
+ @file_regex_match = resource.fetch('FileRegexMatch', nil)
255
+ end
256
+
257
+ def payload
258
+ payload = {
259
+ 'HOSTNAME' => @host,
260
+ 'USERNAME' => @user
261
+ }
262
+ payload['PORT'] = @port unless @port.nil?
263
+ payload['SERVER_KEY'] = @server_key unless @server_key.nil?
264
+ payload['PASSWORD'] = @password unless @password.nil?
265
+ payload['PRIVATEKEY'] = @private_key unless @private_key.nil?
266
+ payload['PRIVATEKEY_PASSWORD'] = @private_key_pass unless @private_key_pass.nil?
267
+ payload['FILE'] = @file unless @file.nil?
268
+ payload['FILE_REGEX_MATCH'] = @file_regex_match unless @file_regex_match.nil?
269
+ return payload.to_json
270
+ end
271
+
272
+ def ssm_parameters
273
+ params = []
274
+ params << @password unless @password.nil?
275
+ params << @private_key unless @private_key.nil?
276
+ params << @private_key_pass unless @private_key_pass.nil?
277
+ return params
278
+ end
279
+ end
280
+
281
+ class InternalSFTPEvent < SFTPEvent
282
+ def initialize(resource,environment)
283
+ super(resource)
284
+ @group = 'InternalSFTP'
285
+ @name = 'InternalSFTPEvent'
286
+ @target = "InternalSFTPCheckFunction#{environment}"
287
+ @environment = environment
288
+ end
289
+ end
290
+
291
+ class TLSEvent < Event
292
+ def initialize(resource)
293
+ super(resource)
294
+ @group = 'TLS'
295
+ @name = 'TLSEvent'
296
+ @target = 'TLSCheckFunction'
297
+ @cron = "0/5 * * * ? *"
298
+ @host = resource['Id']
299
+ @port = resource.fetch('Port', 443)
300
+ @check_max = resource.fetch('MaxSupported', nil)
301
+ @versions = resource.fetch('Versions', ['SSLv2','SSLv3','TLSv1','TLSv1.1','TLSv1.2'])
302
+ end
303
+
304
+ def payload
305
+ payload = {
306
+ 'HOSTNAME' => @host,
307
+ 'PORT' => @port
308
+ }
309
+ payload['CHECK_MAX_SUPPORTED'] = @check_max.nil?
310
+ payload['PROTOCOLS'] = @versions unless @versions.nil?
311
+ return payload.to_json
188
312
  end
189
313
  end
190
314
 
@@ -0,0 +1,28 @@
1
+ require 'cfnguardian/string'
2
+ require 'digest/md5'
3
+
4
+ module CfnGuardian
5
+ module Models
6
+ class MetricFilter
7
+
8
+ attr_reader :type,
9
+ :metric_namespace,
10
+ :name
11
+ attr_accessor :log_group,
12
+ :pattern,
13
+ :metric_value,
14
+ :metric_name
15
+
16
+ def initialize(log_group,filter)
17
+ @type = 'MetricFilter'
18
+ @name = Digest::MD5.hexdigest(log_group + filter['MetricName'])
19
+ @log_group = log_group
20
+ @pattern = filter['Pattern']
21
+ @metric_value = filter.fetch('MetricValue', '1').to_s
22
+ @metric_name = filter['MetricName']
23
+ @metric_namespace = "MetricFilters"
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -5,9 +5,11 @@ module CfnGuardian::Resource
5
5
  alarm = CfnGuardian::Models::ApplicationTargetGroupAlarm.new(@resource)
6
6
  alarm.name = 'HealthyHosts'
7
7
  alarm.metric_name = 'HealthyHostCount'
8
+ alarm.comparison_operator = 'LessThanThreshold'
8
9
  alarm.statistic = 'Minimum'
9
10
  alarm.threshold = 2
10
11
  alarm.evaluation_periods = 1
12
+ alarm.comparison_operator = 'LessThanThreshold'
11
13
  @alarms.push(alarm)
12
14
 
13
15
  alarm = CfnGuardian::Models::ApplicationTargetGroupAlarm.new(@resource)
@@ -1,24 +1,29 @@
1
1
  require 'cfnguardian/string'
2
+ require 'cfnguardian/cloudwatch'
2
3
  require 'cfnguardian/models/alarm'
3
4
  require 'cfnguardian/models/event'
4
5
  require 'cfnguardian/models/check'
6
+ require 'cfnguardian/models/metric_filter'
5
7
 
6
8
  module CfnGuardian::Resource
7
9
  class Base
8
10
  include Logging
9
11
 
10
- def initialize(resource)
12
+ def initialize(resource, override_group = nil)
11
13
  @resource = resource
14
+ @override_group = override_group
12
15
  @alarms = []
13
16
  @events = []
14
17
  @checks = []
18
+ @metric_filters = []
15
19
  end
16
20
 
21
+ # Overidden by inheritted classes to define default alarms
17
22
  def default_alarms()
18
23
  return @alarms
19
24
  end
20
25
 
21
- def get_alarms(overides={})
26
+ def get_alarms(overides={},resource={})
22
27
  # generate default alarms
23
28
  default_alarms()
24
29
 
@@ -31,18 +36,20 @@ module CfnGuardian::Resource
31
36
 
32
37
  if !alarm.nil?
33
38
  alarm.enabled = false
34
- logger.debug "Disabling alarm '#{name}' for resource #{alarm.resource}"
39
+ logger.debug "Disabling alarm '#{name}' for resource #{alarm.resource_id}"
35
40
  next
36
41
  end
37
42
  end
38
43
 
44
+ # continue if the override is in the incorrect format
39
45
  unless properties.is_a?(Hash)
40
46
  if name != 'Inherit'
41
- logger.warn "Incorrect format for alarm '#{name}'. Should be of type 'Hash', instead got type '#{properties.class}'"
47
+ logger.warn "Incorrect format for alarm '#{name}'. Should be of type 'Hash', instead got type '#{properties.group}'"
42
48
  end
43
49
  next
44
50
  end
45
51
 
52
+ # Create a new alarm inheriting the defaults of an existing alarm
46
53
  if properties.has_key?('Inherit')
47
54
  alarm = find_alarm(properties['Inherit'])
48
55
  if !alarm.nil?
@@ -59,37 +66,52 @@ module CfnGuardian::Resource
59
66
  alarm = find_alarm(name)
60
67
 
61
68
  if alarm.nil?
62
- alarm = Kernel.const_get("CfnGuardian::Models::#{self.class.to_s.split('::').last}Alarm").new(properties)
63
- end
64
-
65
- if alarm.name.nil?
69
+ # if alarm doesn't exist create a new one
70
+ alarm = Kernel.const_get("CfnGuardian::Models::#{self.class.to_s.split('::').last}Alarm").new(resource)
71
+ properties.each {|attr,value| update_alarm(alarm,attr,value)}
66
72
  alarm.name = name
73
+ @alarms.push(alarm)
74
+ else
75
+ # if there is an existing alarm update the properties
76
+ properties.each {|attr,value| update_alarm(alarm,attr,value)}
67
77
  end
68
-
69
- properties.each {|attr,value| update_alarm(alarm,attr,value)}
70
- @alarms.push(alarm)
71
-
72
78
  end
73
79
 
74
- return @alarms.select{|a| a.enabled}.map {|a| a.to_h}
80
+ unless @override_group.nil?
81
+ @alarms.each {|a| a.group = @override_group}
82
+ end
83
+
84
+ return @alarms.select{|a| a.enabled}
75
85
  end
76
86
 
87
+ # Overidden by inheritted classes to define default events
77
88
  def default_events()
78
89
  return @events
79
90
  end
80
91
 
81
92
  def get_events()
82
93
  default_events()
83
- return @events.select{|e| e.enabled}.map {|e| e.to_h}
94
+ return @events.select{|e| e.enabled}
84
95
  end
85
96
 
97
+ # Overidden by inheritted classes to define default checks
86
98
  def default_checks()
87
99
  return @checks
88
100
  end
89
101
 
90
102
  def get_checks()
91
103
  default_checks()
92
- return @checks.map {|c| c.to_h}
104
+ return @checks
105
+ end
106
+
107
+ # Overidden by inheritted classes to define default checks
108
+ def default_metric_filters()
109
+ return @metric_filters
110
+ end
111
+
112
+ def get_metric_filters()
113
+ default_metric_filters()
114
+ return @metric_filters
93
115
  end
94
116
 
95
117
  def get_cost()
@@ -107,7 +129,7 @@ module CfnGuardian::Resource
107
129
  alarm.send("#{attr.to_underscore}=",value)
108
130
  rescue NoMethodError => e
109
131
  if !e.message.match?(/inherit/)
110
- logger.warn "Unknown key '#{attr}' for #{alarm.resource} alarm #{alarm.name}"
132
+ logger.warn "Unknown key '#{attr}' for #{alarm.resource_id} alarm #{alarm.name}"
111
133
  end
112
134
  end
113
135
  end