cfn_monitor 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06a68ecda9a61437e7147f46a19f1f0eae4657fed927a5707055bef7393dff89
4
- data.tar.gz: de1388e92d9c75303dafec4ce4172813b36ccf373a4847281ecaada1dec422c3
3
+ metadata.gz: 466206e0f5cedb6a5006c98a0004ce27dddf0fe0d793ed1842e1ec3d11fb8382
4
+ data.tar.gz: 72c10dbcead4deccf275f20609342474b37ae41c310643bc52d0340a0af67017
5
5
  SHA512:
6
- metadata.gz: e977e75432f12b896a96ca0d42967e3d113d67e4394f48380e624b2939bf2f1fe93ef73e1369f60ef0d6b2b009f9125dce12e8e63d038a058dc6be2cfd043847
7
- data.tar.gz: c774fecdcbfa6d8cb4f756f10bf2fa7987d9434e80c30b2980ff7911cca65d20bf12b10ba8e30d3124b4ba30326fc9ebcd38d9345974dbda501f8f2bcd1be7b2
6
+ metadata.gz: a1622b83a7cdfbf96757582b130b7c04c2ce7789f8f9de4f04c0610e2ab59795c2207361f79f157777bf7d858f42018cdaf0f430b9bb05053888b5950118058c
7
+ data.tar.gz: 9bd6dced53b3f49fa8516194a031239fe290bce680b3b59d9284a789e8509de2916c27988d830dc7b70038d561b0fa45402453e1258ebf138ce4235ee797affa
data/README.md CHANGED
@@ -9,7 +9,7 @@ monitorable resources that can be placed into a config file. This config
9
9
  can then be used to generate a cloudformation stack to create and manage
10
10
  cloudwatch alarms.
11
11
 
12
- It is packaged as a docker container `base2/cfn_monitor` and
12
+ It is packaged as a docker container `base2/cfn-monitor` and
13
13
  can be run by volume mounting in a local directory to access the config
14
14
  or by using within AWS CodePipeline.
15
15
 
@@ -152,6 +152,25 @@ timeOut | A timeout value for the endpoint monitoring | 120 seconds
152
152
  scheduleExpression | A cron expression used to schedule the endpoint monitoring | Every minute
153
153
  environments | A string or array of environment names. Monitoring will only be deployed for these environments (if specified) | All environments
154
154
 
155
+ #### SSL certificate expiry date checking
156
+ To alert on the expiry date of an SSL certificate for a particular domain, add the following config:
157
+
158
+ ```YAML
159
+ ssl:
160
+ https://www.base2services.com: Ssl
161
+ ```
162
+
163
+ The `Ssl` template is scheduled to push a metric once every day.
164
+
165
+ #### DNS domain expiry date checking
166
+ To alert on the expiry date of an DNS domain, add the following config:
167
+
168
+ ```YAML
169
+ dns:
170
+ base2services.com: Dns
171
+ ```
172
+
173
+ The `Dns` template is scheduled to push a metric once every day.
155
174
 
156
175
  #### Multiple templates
157
176
  You can specify multiple templates for the resource by providing a list/array. You may want to do this if you want to deploy some custom alarms in addition to the default alarms for a resource.
@@ -2,6 +2,7 @@ require "thor"
2
2
  require "cfn_monitor/version"
3
3
  require "cfn_monitor/query"
4
4
  require "cfn_monitor/generate"
5
+ require "cfn_monitor/validate"
5
6
  require "cfn_monitor/deploy"
6
7
 
7
8
  module CfnMonitor
@@ -13,6 +14,12 @@ module CfnMonitor
13
14
  puts CfnMonitor::VERSION
14
15
  end
15
16
 
17
+ class_option :silent,
18
+ aliases: :s,
19
+ type: :boolean,
20
+ default: false,
21
+ desc: "Don't print Cfndsl output"
22
+
16
23
  # class_option :verbose,
17
24
  # aliases: :V,
18
25
  # type: :boolean,
@@ -32,35 +39,47 @@ module CfnMonitor
32
39
  # type: :string,
33
40
  # desc: "Profile name in AWS credentials file"
34
41
 
35
- desc "generate", "Generate monitoring cloudformation templates"
42
+ desc "generate", "Generate monitoring CloudFormation templates"
36
43
  long_desc <<-LONG
37
- Generates cloudformation templates from the alarm configuration and output to the output/ directory.
44
+ Generates CloudFormation templates from the alarm configuration and output to the output/ directory.
38
45
  LONG
39
46
  method_option :application, aliases: :a, type: :string, desc: "application name"
40
- # method_option :validate, aliases: :v, type: :boolean, default: true, desc: "validate cfn templates"
47
+ method_option :validate, aliases: :v, type: :boolean, default: false, desc: "validate cfn templates"
41
48
  def generate
42
49
  CfnMonitor::Generate.run(options)
50
+ if options['validate']
51
+ CfnMonitor::Validate.run(options)
52
+ end
43
53
  end
44
54
 
45
- desc "query", "Queries a cloudformation stack for monitorable resources"
55
+ desc "query", "Queries a CloudFormation stack for monitorable resources"
46
56
  long_desc <<-LONG
47
57
  This will provide a list of resources in the correct config syntax,
48
58
  including the nested stacks and the default templates for those resources.
49
59
  LONG
50
60
  method_option :application, aliases: :a, type: :string, desc: "application name"
51
- method_option :stack, aliases: :s, type: :string, desc: "cfn stack name"
61
+ method_option :stack, aliases: :s, type: :string, desc: "CloudFormation stack name"
52
62
  def query
53
63
  CfnMonitor::Query.run(options)
54
64
  end
55
65
 
56
- desc "deploy", "Deploys gerenated cfn templates to S3 bucket"
66
+ desc "deploy", "Deploys generated CloudFormation templates to an S3 bucket"
57
67
  long_desc <<-LONG
58
- Deploys gerenated cloudformation templates to the specified S3 source_bucket
68
+ Deploys generated CloudFormation templates to the specified S3 source bucket
59
69
  LONG
60
70
  method_option :application, aliases: :a, type: :string, desc: "application name"
61
71
  def deploy
62
72
  CfnMonitor::Deploy.run(options)
63
73
  end
64
74
 
75
+ desc "validate", "Validates CloudFormation templates in an S3 bucket"
76
+ long_desc <<-LONG
77
+ Validates generated CloudFormation templates in a specified S3 source bucket
78
+ LONG
79
+ method_option :application, aliases: :a, type: :string, desc: "application name"
80
+ def validate
81
+ CfnMonitor::Validate.run(options)
82
+ end
83
+
65
84
  end
66
85
  end
@@ -6,14 +6,16 @@ module CfnMonitor
6
6
 
7
7
  def self.run(options)
8
8
 
9
- if !options['application']
10
- raise "No application specified"
9
+ if options['application']
10
+ application = options['application']
11
+ custom_alarms_config_file = "#{application}/alarms.yml"
12
+ output_path = "output/#{application}"
13
+ else
14
+ application = File.basename(Dir.getwd)
15
+ custom_alarms_config_file = "alarms.yml"
16
+ output_path = "output"
11
17
  end
12
18
 
13
- application = options['application']
14
-
15
- custom_alarms_config_file = "#{application}/alarms.yml"
16
- output_path = "output/#{application}"
17
19
  upload_path = "cloudformation/monitoring/#{application}"
18
20
 
19
21
  # Load custom config files
@@ -10,11 +10,13 @@ module CfnMonitor
10
10
 
11
11
  def self.run(options)
12
12
 
13
- if !options['application']
14
- raise "No application specified"
13
+ if options['silent']
14
+ verbose_cfndsl = false
15
+ else
16
+ verbose_cfndsl = STDOUT
15
17
  end
16
18
 
17
- application = options['application']
19
+ application = options['application'] || '.'
18
20
 
19
21
  template_path = File.join(File.dirname(__FILE__),'../config/templates.yml')
20
22
  config_path = File.join(File.dirname(__FILE__),'../config/config.yml')
@@ -47,74 +49,81 @@ module CfnMonitor
47
49
  alarms = []
48
50
  resources = custom_alarms_config['resources']
49
51
  metrics = custom_alarms_config['metrics']
50
- hosts = custom_alarms_config['hosts']
51
- hosts ||= {}
52
- services = custom_alarms_config['services']
53
- services ||= {}
54
- endpoints = custom_alarms_config['endpoints']
55
- endpoints ||= {}
56
- rme = { resources: resources, metrics: metrics, endpoints: endpoints, hosts: hosts, services: services }
52
+ hosts = custom_alarms_config['hosts'] || {}
53
+ ssl = custom_alarms_config['ssl'] || {}
54
+ dns = custom_alarms_config['dns'] || {}
55
+ services = custom_alarms_config['services'] || {}
56
+ endpoints = custom_alarms_config['endpoints'] || {}
57
+ ecsClusters = custom_alarms_config['ecsClusters'] || {}
58
+
59
+ alarm_parameters = { resources: resources, metrics: metrics, endpoints: endpoints, hosts: hosts, ssl: ssl, dns: dns, services: services, ecsClusters: ecsClusters }
57
60
  source_bucket = custom_alarms_config['source_bucket']
58
61
 
59
- rme.each do | k,v |
62
+ alarm_parameters.each do | k,v |
60
63
  if !v.nil?
61
- v.each do | resource,attributes |
64
+ v.each do | resource,attributeList |
62
65
  # set environments to 'all' by default
63
66
  environments = ['all']
64
67
  # Support config hashs for additional parameters
65
68
  params = {}
66
- if attributes.kind_of?(Hash)
67
- attributes.each do | a,b |
68
- environments = b if a == 'environments'
69
- # Convert strings to arrays for consistency
70
- if !environments.kind_of?(Array) then environments = environments.split end
71
- params[a] = b if !['template','environments'].member? a
72
- end
73
- templatesEnabled = attributes['template']
74
- else
75
- templatesEnabled = attributes
69
+ if !attributeList.kind_of?(Array)
70
+ attributeList = [attributeList]
76
71
  end
77
- # Convert strings to arrays for looping
78
- if !templatesEnabled.kind_of?(Array) then templatesEnabled = templatesEnabled.split end
79
- templatesEnabled.each do | templateEnabled |
80
- if !templates['templates'][templateEnabled].nil?
81
- # If a template is provided, inherit that template
82
- if !templates['templates'][templateEnabled]['template'].nil?
83
- template_from = Marshal.load( Marshal.dump(templates['templates'][templates['templates'][templateEnabled]['template']]) )
84
- template_to = templates['templates'][templateEnabled].without('template')
85
- template_merged = CfnMonitor::Utils.deep_merge(template_from, template_to)
86
- templates['templates'][templateEnabled] = template_merged
72
+ attributeList.each do | attributes |
73
+ if attributes.kind_of?(Hash)
74
+ attributes.each do | a,b |
75
+ environments = b if a == 'environments'
76
+ # Convert strings to arrays for consistency
77
+ if !environments.kind_of?(Array) then environments = environments.split end
78
+ params[a] = b if !['template','environments'].member? a
87
79
  end
88
- templates['templates'][templateEnabled].each do | alarm,parameters |
89
- resourceParams = parameters.clone
90
- # Override template params if overrides provided
91
- params.each do | x,y |
92
- resourceParams[x] = y
80
+ templatesEnabled = attributes['template']
81
+ else
82
+ templatesEnabled = attributes
83
+ end
84
+ # Convert strings to arrays for looping
85
+ if !templatesEnabled.kind_of?(Array) then templatesEnabled = templatesEnabled.split end
86
+ templatesEnabled.each do | templateEnabled |
87
+ if !templates['templates'][templateEnabled].nil?
88
+ # If a template is provided, inherit that template
89
+ if !templates['templates'][templateEnabled]['template'].nil?
90
+ template_from = Marshal.load( Marshal.dump(templates['templates'][templates['templates'][templateEnabled]['template']]) )
91
+ template_to = templates['templates'][templateEnabled].without('template')
92
+ template_merged = CfnMonitor::Utils.deep_merge(template_from, template_to)
93
+ templates['templates'][templateEnabled] = template_merged
93
94
  end
94
- if k == :hosts
95
- resourceParams['cmds'].each do |cmd|
96
- hostParams = resourceParams.clone
97
- hostParams['cmd'] = cmd
98
- # Construct alarm object per cmd
95
+ templates['templates'][templateEnabled].each do | alarm,parameters |
96
+ resourceParams = parameters.clone
97
+ # Override template params if overrides provided
98
+ params.each do | x,y |
99
+ resourceParams[x] = y
100
+ end
101
+
102
+ if k == :hosts
103
+ resourceParams['cmds'].each do |cmd|
104
+ hostParams = resourceParams.clone
105
+ hostParams['cmd'] = cmd
106
+ # Construct alarm object per cmd
107
+ alarms << {
108
+ resource: resource,
109
+ type: k[0...-1],
110
+ template: templateEnabled,
111
+ alarm: alarm,
112
+ parameters: hostParams,
113
+ environments: environments
114
+ }
115
+ end
116
+ else
117
+ # Construct alarm object
99
118
  alarms << {
100
119
  resource: resource,
101
120
  type: k[0...-1],
102
121
  template: templateEnabled,
103
122
  alarm: alarm,
104
- parameters: hostParams,
123
+ parameters: resourceParams,
105
124
  environments: environments
106
125
  }
107
126
  end
108
- else
109
- # Construct alarm object
110
- alarms << {
111
- resource: resource,
112
- type: k[0...-1],
113
- template: templateEnabled,
114
- alarm: alarm,
115
- parameters: resourceParams,
116
- environments: environments
117
- }
118
127
  end
119
128
  end
120
129
  end
@@ -149,27 +158,29 @@ module CfnMonitor
149
158
  temp_files[i].rewind
150
159
  end
151
160
 
152
- write_cfdndsl_template(temp_file_path, temp_file_paths, custom_alarms_config_file, source_bucket, template_envs, output_path, upload_path)
161
+ write_cfdndsl_template(alarm_parameters, temp_file_path, temp_file_paths, custom_alarms_config_file, source_bucket, template_envs, output_path, upload_path, verbose_cfndsl)
153
162
 
154
163
  end
155
164
 
156
- def self.write_cfdndsl_template(alarms_config,configs,custom_alarms_config_file,source_bucket,template_envs,output_path,upload_path)
165
+ def self.write_cfdndsl_template(alarm_parameters,alarms_config_file,configs,custom_alarms_config_file,source_bucket,template_envs,output_path,upload_path,verbose_cfndsl)
157
166
  template_path = File.expand_path("../../templates", __FILE__)
158
167
  FileUtils::mkdir_p output_path
159
168
  configs.each_with_index do |config,index|
160
169
  File.open("#{output_path}/resources#{index}.json", 'w') { |file|
161
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/resources.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "source_bucket='#{source_bucket}'"],[:raw, "upload_path='#{upload_path}'"]],STDOUT)))}
170
+ file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/resources.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "source_bucket='#{source_bucket}'"],[:raw, "upload_path='#{upload_path}'"]],verbose_cfndsl)))}
162
171
  File.open("#{output_path}/alarms#{index}.json", 'w') { |file|
163
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/alarms.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
172
+ file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/alarms.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "template_envs=#{template_envs}"]],verbose_cfndsl)))}
173
+ end
174
+ ['endpoints', 'ssl', "dns", 'hosts', 'services','ecsClusters'].each do |template|
175
+ if !alarm_parameters[template.to_sym].nil? and alarm_parameters[template.to_sym] != {}
176
+ File.open("#{output_path}/#{template}.json", 'w') { |file|
177
+ file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/#{template}.rb",[[:yaml, alarms_config_file],[:raw, "template_envs=#{template_envs}"]],verbose_cfndsl)))
178
+ }
179
+ end
164
180
  end
165
- File.open("#{output_path}/endpoints.json", 'w') { |file|
166
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/endpoints.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
167
- File.open("#{output_path}/hosts.json", 'w') { |file|
168
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/hosts.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
169
- File.open("#{output_path}/services.json", 'w') { |file|
170
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/services.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
181
+
171
182
  File.open("#{output_path}/master.json", 'w') { |file|
172
- file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/master.rb",[[:yaml, custom_alarms_config_file],[:raw, "templateCount=#{configs.count}"],[:raw, "template_envs=#{template_envs}"],[:raw, "upload_path='#{upload_path}'"]],STDOUT)))}
183
+ file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/master.rb",[[:yaml, custom_alarms_config_file],[:raw, "templateCount=#{configs.count}"],[:raw, "template_envs=#{template_envs}"],[:raw, "upload_path='#{upload_path}'"]],verbose_cfndsl)))}
173
184
  end
174
185
 
175
186
  def self.get_alarm_envs(params)
@@ -6,19 +6,23 @@ module CfnMonitor
6
6
 
7
7
  def self.run(options)
8
8
 
9
- if !options['application'] || !options['stack']
10
- raise "No application specified"
11
- end
12
-
13
9
  if !options['stack']
14
10
  raise "No stack specified"
15
11
  end
16
12
 
13
+ if options['application']
14
+ application = options['application']
15
+ custom_alarms_config_file = "#{application}/alarms.yml"
16
+ else
17
+ application = File.basename(Dir.getwd)
18
+ custom_alarms_config_file = "alarms.yml"
19
+ end
20
+
17
21
  config_path = File.join(File.dirname(__FILE__),'../config/config.yml')
18
22
  # Load global config files
19
23
  config = YAML.load(File.read(config_path))
20
24
 
21
- custom_alarms_config_file = "#{options['application']}/alarms.yml"
25
+ custom_alarms_config_file = "#{application}/alarms.yml"
22
26
 
23
27
  # Load custom config files
24
28
  custom_alarms_config = YAML.load(File.read(custom_alarms_config_file)) if File.file?(custom_alarms_config_file)
@@ -27,7 +31,7 @@ module CfnMonitor
27
31
 
28
32
  puts "-----------------------------------------------"
29
33
  puts "stack: #{options['stack']}"
30
- puts "application: #{options['application']}"
34
+ puts "application: #{application}"
31
35
  puts "-----------------------------------------------"
32
36
  puts "Searching Stacks for Monitorable Resources"
33
37
  puts "-----------------------------------------------"
@@ -75,7 +79,7 @@ module CfnMonitor
75
79
  puts "-----------------------------------------------"
76
80
  end
77
81
  puts "Monitorable resources in #{options['stack']} stack: #{stackResourceCount}"
78
- puts "Resources in #{options['application']} alarms config: #{configResourceCount}"
82
+ puts "Resources in #{application} alarms config: #{configResourceCount}"
79
83
  if stackResourceCount > 0
80
84
  puts "Coverage: #{100-(configResources.count*100/stackResourceCount)}%"
81
85
  end
@@ -0,0 +1,76 @@
1
+ require 'aws-sdk-s3'
2
+ require 'yaml'
3
+
4
+ module CfnMonitor
5
+ class Validate
6
+
7
+ def self.run(options)
8
+
9
+ if options['application']
10
+ application = options['application']
11
+ custom_alarms_config_file = "#{application}/alarms.yml"
12
+ output_path = "output/#{application}"
13
+ else
14
+ application = File.basename(Dir.getwd)
15
+ custom_alarms_config_file = "alarms.yml"
16
+ output_path = "output"
17
+ end
18
+
19
+ validate_path = "cloudformation/monitoring/#{application}/validate"
20
+
21
+ # Load custom config files
22
+ if File.file?(custom_alarms_config_file)
23
+ custom_alarms_config = YAML.load(File.read(custom_alarms_config_file)) if File.file?(custom_alarms_config_file)
24
+ else
25
+ puts "Failed to load #{custom_alarms_config_file}"
26
+ exit 1
27
+ end
28
+
29
+ source_region = custom_alarms_config['source_region']
30
+ source_bucket = custom_alarms_config['source_bucket']
31
+
32
+ cfn = Aws::CloudFormation::Client.new(region: source_region)
33
+ s3 = Aws::S3::Client.new(region: source_region)
34
+ validated = 0
35
+ unvalidated = 0
36
+
37
+ puts "-----------------------------------------------"
38
+
39
+ ["#{output_path}/*.json"].each { |path|
40
+ Dir.glob(path) do |file|
41
+ template = File.open(file, 'rb')
42
+ filename = file.gsub("#{output_path}/", "")
43
+ begin
44
+ puts "INFO - Copying #{file} to s3://#{source_bucket}/#{validate_path}/#{filename}"
45
+ s3.put_object({
46
+ body: template,
47
+ bucket: "#{source_bucket}",
48
+ key: "#{validate_path}/#{filename}",
49
+ })
50
+ template_url = "https://#{source_bucket}.s3.amazonaws.com/#{validate_path}/#{filename}"
51
+ puts "INFO - Validating #{template_url}"
52
+ begin
53
+ resp = cfn.validate_template({ template_url: template_url })
54
+ puts "INFO - Template #{filename} validated successfully"
55
+ validated += 1
56
+ rescue => e
57
+ puts "ERROR - Template #{filename} failed to validate: #{e}"
58
+ unvalidated += 1
59
+ end
60
+ rescue => e
61
+ puts "ERROR - #{e.class}, #{e}"
62
+ exit 1
63
+ end
64
+ end
65
+ }
66
+
67
+ if unvalidated > 0
68
+ puts "ERROR - #{validated}/#{Dir["output/**/*.json"].count} templates validated successfully"
69
+ exit 1
70
+ else
71
+ puts "INFO - #{validated}/#{Dir["output/**/*.json"].count} templates validated successfully"
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -1,3 +1,3 @@
1
1
  module CfnMonitor
2
- VERSION = "0.1.1".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
@@ -1,4 +1,16 @@
1
1
  templates:
2
+ EcsCICheck:
3
+ ECSContianerInstancesDisconnected:
4
+ AlarmActions: crit
5
+ Namespace: EcsCICheck
6
+ ComparisonOperator: GreaterThanThreshold
7
+ DimensionsName: Cluster
8
+ Statistic: Maximum
9
+ Threshold: 0
10
+ Period: 60
11
+ EvaluationPeriods: 2
12
+ MetricName: ECSContianerInstancesDisconnected
13
+ TreatMissingData: notBreaching
2
14
  HttpCheck:
3
15
  EndpointAvailable:
4
16
  AlarmActions: crit
@@ -66,6 +78,52 @@ templates:
66
78
  # MetricName: Failed_SSM_Agent
67
79
  # AlarmDescription: { 'Fn::Join': [ ' ', [ Ref: 'MonitoredStack', { 'Fn::Sub': ['${resource}', {'env': { Ref: 'EnvironmentName' } } ] }, '${alarmName}' ] ] }
68
80
  # TreatMissingData: notBreaching
81
+ Ssl:
82
+ Crit:
83
+ AlarmActions: crit
84
+ Namespace: SSL
85
+ ComparisonOperator: LessThanThreshold
86
+ Dimensions: [ { Name: 'URL', Value: '${resource}' } ]
87
+ Statistic: Maximum
88
+ Threshold: 30 # 30 days
89
+ Period: 60
90
+ EvaluationPeriods: 1
91
+ MetricName: ExpiresInDays
92
+ AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
93
+ Warn:
94
+ AlarmActions: warn
95
+ Namespace: SSL
96
+ ComparisonOperator: LessThanThreshold
97
+ Dimensions: [ { Name: 'URL', Value: '${resource}' } ]
98
+ Statistic: Maximum
99
+ Threshold: 60 # 60 days
100
+ Period: 60
101
+ EvaluationPeriods: 1
102
+ MetricName: ExpiresInDays
103
+ AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
104
+ Dns:
105
+ Crit:
106
+ AlarmActions: crit
107
+ Namespace: DNS
108
+ ComparisonOperator: LessThanThreshold
109
+ Dimensions: [ { Name: 'Domain', Value: '${resource}' } ]
110
+ Statistic: Maximum
111
+ Threshold: 30 # 30 days
112
+ Period: 60
113
+ EvaluationPeriods: 1
114
+ MetricName: ExpiresInDays
115
+ AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
116
+ Warn:
117
+ AlarmActions: warn
118
+ Namespace: DNS
119
+ ComparisonOperator: LessThanThreshold
120
+ Dimensions: [ { Name: 'Domain', Value: '${resource}' } ]
121
+ Statistic: Maximum
122
+ Threshold: 60 # 60 days
123
+ Period: 60
124
+ EvaluationPeriods: 1
125
+ MetricName: ExpiresInDays
126
+ AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
69
127
  Nrpe:
70
128
  Warn:
71
129
  AlarmActions: warn
@@ -399,14 +457,14 @@ templates:
399
457
  EvaluationPeriods: 5
400
458
  ApiGateway: #AWS:ApiGateway
401
459
  ApiEndpoint5xx:
402
- AlarmActions: crit
460
+ AlarmActions: warn
403
461
  Namespace: AWS/ApiGateway
404
462
  MetricName: 5XXError
405
463
  ComparisonOperator: GreaterThanThreshold
406
464
  DimensionsName: ApiName
407
465
  Statistic: Sum
408
466
  Threshold: 5
409
- EvaluationPdyneriods: 2
467
+ EvaluationPeriods: 2
410
468
  DynamoDBTable: #AWS::DynamoDB::Table
411
469
  DynamoDBReadUsage:
412
470
  AlarmActions: warn
@@ -425,4 +483,4 @@ templates:
425
483
  DimensionsName: TableName
426
484
  Statistic: Sum
427
485
  Threshold: 80
428
- EvaluationPeriods: 2
486
+ EvaluationPeriods: 2
@@ -53,7 +53,7 @@ CloudFormation do
53
53
  template = alarm[:template]
54
54
  name = alarm[:alarm]
55
55
  params = alarm[:parameters]
56
- cmd = params['cmd']
56
+ cmd = params['cmd'] || ''
57
57
 
58
58
  alarmHash = Digest::MD5.hexdigest "#{resourceGroup}#{template}#{name}#{cmd}"
59
59
 
@@ -104,7 +104,7 @@ CloudFormation do
104
104
  conditions = []
105
105
 
106
106
  # Configure resource parameters
107
- if type == 'resource'
107
+ if ['resource','ecsCluster'].include? type
108
108
  # Configure physical resource inputs
109
109
  dimensionsNames = params['DimensionsName'].split('/')
110
110
  dimensions = []
@@ -0,0 +1,61 @@
1
+ require 'cfndsl'
2
+
3
+ CloudFormation do
4
+ Description("CloudWatch Endpoints")
5
+
6
+ Parameter("MonitoredStack"){
7
+ Type 'String'
8
+ }
9
+ Parameter("EnvironmentName"){
10
+ Type 'String'
11
+ }
12
+ Parameter("DnsCheckFunctionArn"){
13
+ Type 'String'
14
+ }
15
+
16
+ alarms.each do |alarm|
17
+ if alarm[:type] == 'dn'
18
+ endpointHash = Digest::MD5.hexdigest "dns-" + alarm[:resource]
19
+
20
+ # Conditionally create shedule based on environments attribute
21
+ if alarm[:environments] != ['all']
22
+ conditions = []
23
+ alarm[:environments].each do | env |
24
+ conditions << FnEquals(Ref("EnvironmentName"),env)
25
+ end
26
+ if conditions.length > 1
27
+ Condition("Condition#{endpointHash}", FnOr(conditions))
28
+ else
29
+ Condition("Condition#{endpointHash}", conditions[0])
30
+ end
31
+ end
32
+
33
+ params = alarm[:parameters]
34
+
35
+ # Set defaults
36
+ params['scheduleExpression'] ||= "0 12 * * ? *" # 12PM every day
37
+
38
+ # Create payload
39
+ payload = {}
40
+ payload['Domain'] = alarm[:resource]
41
+ payload['Region'] = "${region}"
42
+
43
+ endpointHash = Digest::MD5.hexdigest alarm[:resource]
44
+ Resource("DnsCheckSchedule#{endpointHash}") do
45
+ Condition "Condition#{endpointHash}" if alarm[:environments] != ['all']
46
+ Type 'AWS::Events::Rule'
47
+ Property('Description', "#{payload['Domain']}")
48
+ Property('ScheduleExpression', "cron(#{params['scheduleExpression']})")
49
+ Property('State', 'ENABLED')
50
+ Property('Targets', [
51
+ {
52
+ Arn: Ref('DnsCheckFunctionArn'),
53
+ Id: endpointHash,
54
+ Input: FnSub(payload.to_json, region: Ref("AWS::Region"))
55
+ }
56
+ ])
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,76 @@
1
+ require 'cfndsl'
2
+
3
+ CloudFormation do
4
+ Description("CloudWatch ECS Container Instances")
5
+
6
+ Parameter("EcsCICheckFunctionArn"){
7
+ Type 'String'
8
+ }
9
+
10
+ Parameter("MonitoredStack"){
11
+ Type 'String'
12
+ }
13
+ Parameter("GetPhysicalIdFunctionArn"){
14
+ Type 'String'
15
+ }
16
+ Parameter("EnvironmentName"){
17
+ Type 'String'
18
+ }
19
+ Parameter("ConfigToggle"){
20
+ Type 'String'
21
+ }
22
+
23
+ alarms.each do |alarm|
24
+ if alarm[:type] == 'ecsCluster'
25
+
26
+ resourceHash = Digest::MD5.hexdigest alarm[:resource]
27
+
28
+ Resource("GetPhysicalId#{resourceHash}") do
29
+ Type 'Custom::GetResourcePhysicalId'
30
+ Property('ServiceToken', Ref('GetPhysicalIdFunctionArn'))
31
+ Property('StackName', Ref('MonitoredStack'))
32
+ if alarm[:resource].include? "::"
33
+ Property('LogicalResourceId', alarm[:resource].gsub('::','.') )
34
+ else
35
+ Property('LogicalResourceId', FnJoin( '.', [ Ref('MonitoredStack'), alarm[:resource] ] ))
36
+ end
37
+ Property('Region', Ref('AWS::Region'))
38
+ Property('ConfigToggle', Ref('ConfigToggle'))
39
+ end
40
+
41
+ # Conditionally create shedule based on environments attribute
42
+ if alarm[:environments] != ['all']
43
+ conditions = []
44
+ alarm[:environments].each do | env |
45
+ conditions << FnEquals(Ref("EnvironmentName"),env)
46
+ end
47
+ if conditions.length > 1
48
+ Condition("Condition#{resourceHash}", FnOr(conditions))
49
+ else
50
+ Condition("Condition#{resourceHash}", conditions[0])
51
+ end
52
+ end
53
+
54
+ # Set defaults
55
+ params = alarm[:parameters]
56
+ params['scheduleExpression'] ||= "0/5 * * * ? *"
57
+
58
+ Events_Rule("EcsCICheckSchedule#{resourceHash}") {
59
+ Condition "Condition#{resourceHash}" if alarm[:environments] != ['all']
60
+ Description FnJoin("",["ECS container instance check for the ", FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId') ," ECS cluster"])
61
+ ScheduleExpression "cron(#{params['scheduleExpression']})"
62
+ State params['enabled'] ? 'ENABLED' : 'DISABLED'
63
+ Targets([
64
+ {
65
+ Arn: Ref('EcsCICheckFunctionArn'),
66
+ Id: resourceHash,
67
+ Input: FnSub({CLUSTER: '${cluster}'}.to_json, cluster: FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId'))
68
+ }
69
+ ])
70
+ }
71
+
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -73,8 +73,8 @@ CloudFormation do
73
73
  Resource("NrpeCheckFunction#{hostHash}") do
74
74
  Condition "Condition#{hostHash}" if alarm[:environments] != ['all']
75
75
  Type 'AWS::Lambda::Function'
76
- Property('Code', { S3Bucket: FnJoin('.',['base2.lambda',Ref('AWS::Region')]), S3Key: 'nrpe.zip' })
77
- Property('Handler', 'nrpe')
76
+ Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-nrpe-check/0.1/handler.zip' })
77
+ Property('Handler', 'main')
78
78
  Property('MemorySize', 128)
79
79
  Property('Runtime', 'go1.x')
80
80
  Property('Timeout', 300)
@@ -64,7 +64,41 @@ CloudFormation do
64
64
  ])
65
65
  end
66
66
 
67
- Resource("HttpLambdaExecutionRole") do
67
+ Resource("EcsCICheckLambdaExecutionRole") do
68
+ Type 'AWS::IAM::Role'
69
+ Property('AssumeRolePolicyDocument', {
70
+ Version: '2012-10-17',
71
+ Statement: [{
72
+ Effect: 'Allow',
73
+ Principal: { Service: [ 'lambda.amazonaws.com' ] },
74
+ Action: [ 'sts:AssumeRole' ]
75
+ }]
76
+ })
77
+ Property('Path','/')
78
+ Property('Policies', [
79
+ PolicyName: 'CloudFormationReadOnly',
80
+ PolicyDocument: {
81
+ Version: '2012-10-17',
82
+ Statement: [{
83
+ Effect: 'Allow',
84
+ Action: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents' ],
85
+ Resource: 'arn:aws:logs:*:*:*'
86
+ },
87
+ {
88
+ Effect: 'Allow',
89
+ Action: [ 'ecs:ListContainerInstances', 'ecs:DescribeContainerInstances' ],
90
+ Resource: '*'
91
+ },
92
+ {
93
+ Effect: 'Allow',
94
+ Action: [ 'cloudwatch:PutMetricData' ],
95
+ Resource: '*'
96
+ }]
97
+ }
98
+ ])
99
+ end
100
+
101
+ Resource("LambdaExecutionRole") do
68
102
  Type 'AWS::IAM::Role'
69
103
  Property('AssumeRolePolicyDocument', {
70
104
  Version: '2012-10-17',
@@ -123,12 +157,12 @@ CloudFormation do
123
157
 
124
158
  Resource("HttpCheckFunction") do
125
159
  Type 'AWS::Lambda::Function'
126
- Property('Code', { S3Bucket: FnJoin('.',[Ref('AWS::Region'),'aws-lambda-http-check']), S3Key: 'httpCheck-v2.zip' })
127
- Property('Handler', 'handler.http_check')
160
+ Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-http-check/0.1/handler.zip' })
161
+ Property('Handler', 'handler.main')
128
162
  Property('MemorySize', 128)
129
163
  Property('Runtime', 'python3.6')
130
164
  Property('Timeout', 300)
131
- Property('Role', FnGetAtt('HttpLambdaExecutionRole','Arn'))
165
+ Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
132
166
  end
133
167
 
134
168
  Resource("HttpCheckPermissions") do
@@ -138,6 +172,57 @@ CloudFormation do
138
172
  Property('Principal', 'events.amazonaws.com')
139
173
  end
140
174
 
175
+ Resource("SslCheckFunction") do
176
+ Type 'AWS::Lambda::Function'
177
+ Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-ssl-check/0.1/handler.zip' })
178
+ Property('Handler', 'main')
179
+ Property('MemorySize', 128)
180
+ Property('Runtime', 'go1.x')
181
+ Property('Timeout', 300)
182
+ Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
183
+ end
184
+
185
+ Resource("SslCheckPermissions") do
186
+ Type 'AWS::Lambda::Permission'
187
+ Property('FunctionName', Ref('SslCheckFunction'))
188
+ Property('Action', 'lambda:InvokeFunction')
189
+ Property('Principal', 'events.amazonaws.com')
190
+ end
191
+
192
+ Resource("DnsCheckFunction") do
193
+ Type 'AWS::Lambda::Function'
194
+ Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-dns-check/0.1/handler.zip' })
195
+ Property('Handler', 'main')
196
+ Property('MemorySize', 128)
197
+ Property('Runtime', 'go1.x')
198
+ Property('Timeout', 300)
199
+ Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
200
+ end
201
+
202
+ Resource("DnsCheckPermissions") do
203
+ Type 'AWS::Lambda::Permission'
204
+ Property('FunctionName', Ref('DnsCheckFunction'))
205
+ Property('Action', 'lambda:InvokeFunction')
206
+ Property('Principal', 'events.amazonaws.com')
207
+ end
208
+
209
+ Resource("EcsCICheckFunction") do
210
+ Type 'AWS::Lambda::Function'
211
+ Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-ecs-container-instance-check/0.1/handler.zip' })
212
+ Property('Handler', 'handler.run_check')
213
+ Property('MemorySize', 128)
214
+ Property('Runtime', 'python3.6')
215
+ Property('Timeout', 300)
216
+ Property('Role', FnGetAtt('EcsCICheckLambdaExecutionRole','Arn'))
217
+ end
218
+
219
+ Resource("EcsCICheckPermissions") do
220
+ Type 'AWS::Lambda::Permission'
221
+ Property('FunctionName', Ref('EcsCICheckFunction'))
222
+ Property('Action', 'lambda:InvokeFunction')
223
+ Property('Principal', 'events.amazonaws.com')
224
+ end
225
+
141
226
  params = {
142
227
  MonitoredStack: Ref('MonitoredStack'),
143
228
  SnsTopicCrit: Ref('SnsTopicCrit'),
@@ -175,6 +260,38 @@ CloudFormation do
175
260
  end
176
261
  end
177
262
 
263
+ sslParams = {
264
+ MonitoredStack: Ref('MonitoredStack'),
265
+ SslCheckFunctionArn: FnGetAtt('SslCheckFunction','Arn'),
266
+ EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
267
+ }
268
+
269
+ ssl ||= {}
270
+ if !ssl.empty?
271
+ Resource("SslStack") do
272
+ Type 'AWS::CloudFormation::Stack'
273
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/ssl.json")
274
+ Property('TimeoutInMinutes', 5)
275
+ Property('Parameters', sslParams)
276
+ end
277
+ end
278
+
279
+ dnsParams = {
280
+ MonitoredStack: Ref('MonitoredStack'),
281
+ DnsCheckFunctionArn: FnGetAtt('DnsCheckFunction','Arn'),
282
+ EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
283
+ }
284
+
285
+ dns ||= {}
286
+ if !dns.empty?
287
+ Resource("DnsStack") do
288
+ Type 'AWS::CloudFormation::Stack'
289
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/dns.json")
290
+ Property('TimeoutInMinutes', 5)
291
+ Property('Parameters', dnsParams)
292
+ end
293
+ end
294
+
178
295
  hostParams = {
179
296
  EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
180
297
  }
@@ -203,6 +320,24 @@ CloudFormation do
203
320
  end
204
321
  end
205
322
 
323
+ ecsClusterParams = {
324
+ MonitoredStack: Ref('MonitoredStack'),
325
+ GetPhysicalIdFunctionArn: FnGetAtt('GetPhysicalIdFunction','Arn'),
326
+ EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' ),
327
+ ConfigToggle: Ref('ConfigToggle'),
328
+ EcsCICheckFunctionArn: FnGetAtt('EcsCICheckFunction','Arn')
329
+ }
330
+
331
+ ecsClusters ||= {}
332
+ if !ecsClusters.empty?
333
+ Resource("ServicesStack") do
334
+ Type 'AWS::CloudFormation::Stack'
335
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/ecsClusters.json")
336
+ Property('TimeoutInMinutes', 5)
337
+ Property('Parameters', ecsClusterParams)
338
+ end
339
+ end
340
+
206
341
  Output("CfnMonitorVersion") { Value(CfnMonitor::VERSION) }
207
342
  Output("RenderDate") { Value(Time.now.strftime("%Y-%m-%d")) }
208
343
  Output("MonitoredStack") { Value(Ref("MonitoredStack")) }
@@ -46,13 +46,17 @@ CloudFormation do
46
46
  Type 'Custom::GetResourcePhysicalId'
47
47
  Property('ServiceToken', Ref('GetPhysicalIdFunctionArn'))
48
48
  Property('StackName', Ref('MonitoredStack'))
49
- Property('LogicalResourceId', FnJoin( '.', [ Ref('MonitoredStack'), resource ] ))
49
+ if resource.include? "::"
50
+ Property('LogicalResourceId', resource.gsub('::','.') )
51
+ else
52
+ Property('LogicalResourceId', FnJoin( '.', [ Ref('MonitoredStack'), resource ] ))
53
+ end
50
54
  Property('Region', Ref('AWS::Region'))
51
55
  Property('ConfigToggle', Ref('ConfigToggle'))
52
56
  end
53
57
  customResources << "GetPhysicalId#{resourceHash}"
54
58
  # Create outputs for user reference
55
- Output("#{resource.delete('.')}") { Value(FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId')) }
59
+ Output("#{resource.delete('.').delete('-').delete('::')}") { Value(FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId')) }
56
60
  end
57
61
  end
58
62
  end
@@ -0,0 +1,61 @@
1
+ require 'cfndsl'
2
+
3
+ CloudFormation do
4
+ Description("CloudWatch Endpoints")
5
+
6
+ Parameter("MonitoredStack"){
7
+ Type 'String'
8
+ }
9
+ Parameter("EnvironmentName"){
10
+ Type 'String'
11
+ }
12
+ Parameter("SslCheckFunctionArn"){
13
+ Type 'String'
14
+ }
15
+
16
+ alarms.each do |alarm|
17
+ if alarm[:type] == 'ss'
18
+ endpointHash = Digest::MD5.hexdigest "ssl-" + alarm[:resource]
19
+
20
+ # Conditionally create shedule based on environments attribute
21
+ if alarm[:environments] != ['all']
22
+ conditions = []
23
+ alarm[:environments].each do | env |
24
+ conditions << FnEquals(Ref("EnvironmentName"),env)
25
+ end
26
+ if conditions.length > 1
27
+ Condition("Condition#{endpointHash}", FnOr(conditions))
28
+ else
29
+ Condition("Condition#{endpointHash}", conditions[0])
30
+ end
31
+ end
32
+
33
+ params = alarm[:parameters]
34
+
35
+ # Set defaults
36
+ params['scheduleExpression'] ||= "0 12 * * ? *" # 12PM every day
37
+
38
+ # Create payload
39
+ payload = {}
40
+ payload['Url'] = alarm[:resource]
41
+ payload['Region'] = "${region}"
42
+
43
+ endpointHash = Digest::MD5.hexdigest alarm[:resource]
44
+ Resource("SslCheckSchedule#{endpointHash}") do
45
+ Condition "Condition#{endpointHash}" if alarm[:environments] != ['all']
46
+ Type 'AWS::Events::Rule'
47
+ Property('Description', "#{payload['Url']}")
48
+ Property('ScheduleExpression', "cron(#{params['scheduleExpression']})")
49
+ Property('State', 'ENABLED')
50
+ Property('Targets', [
51
+ {
52
+ Arn: Ref('SslCheckFunctionArn'),
53
+ Id: endpointHash,
54
+ Input: FnSub(payload.to_json, region: Ref("AWS::Region"))
55
+ }
56
+ ])
57
+ end
58
+ end
59
+ end
60
+
61
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Base2Services
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2018-10-25 00:00:00.000000000 Z
13
+ date: 2019-01-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: thor
@@ -211,6 +211,7 @@ files:
211
211
  - lib/cfn_monitor/generate.rb
212
212
  - lib/cfn_monitor/query.rb
213
213
  - lib/cfn_monitor/utils.rb
214
+ - lib/cfn_monitor/validate.rb
214
215
  - lib/cfn_monitor/version.rb
215
216
  - lib/config/config.yml
216
217
  - lib/config/templates.yml
@@ -218,11 +219,14 @@ files:
218
219
  - lib/lambda/getEnvironmentName.py
219
220
  - lib/lambda/getPhysicalId.py
220
221
  - lib/templates/alarms.rb
222
+ - lib/templates/dns.rb
223
+ - lib/templates/ecsClusters.rb
221
224
  - lib/templates/endpoints.rb
222
225
  - lib/templates/hosts.rb
223
226
  - lib/templates/master.rb
224
227
  - lib/templates/resources.rb
225
228
  - lib/templates/services.rb
229
+ - lib/templates/ssl.rb
226
230
  homepage: https://github.com/base2Services/cfn-monitor/blob/master/README.md
227
231
  licenses:
228
232
  - MIT
@@ -242,8 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
246
  - !ruby/object:Gem::Version
243
247
  version: '0'
244
248
  requirements: []
245
- rubyforge_project:
246
- rubygems_version: 2.7.7
249
+ rubygems_version: 3.0.2
247
250
  signing_key:
248
251
  specification_version: 4
249
252
  summary: Configure and generate a cloudwatch monitoring cloudformation stack