portertech-sensu 1.10.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +961 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +65 -0
  5. data/exe/sensu-api +10 -0
  6. data/exe/sensu-client +10 -0
  7. data/exe/sensu-install +195 -0
  8. data/exe/sensu-server +10 -0
  9. data/lib/sensu/api/http_handler.rb +434 -0
  10. data/lib/sensu/api/process.rb +79 -0
  11. data/lib/sensu/api/routes/aggregates.rb +196 -0
  12. data/lib/sensu/api/routes/checks.rb +44 -0
  13. data/lib/sensu/api/routes/clients.rb +171 -0
  14. data/lib/sensu/api/routes/events.rb +86 -0
  15. data/lib/sensu/api/routes/health.rb +45 -0
  16. data/lib/sensu/api/routes/info.rb +37 -0
  17. data/lib/sensu/api/routes/request.rb +44 -0
  18. data/lib/sensu/api/routes/resolve.rb +32 -0
  19. data/lib/sensu/api/routes/results.rb +153 -0
  20. data/lib/sensu/api/routes/settings.rb +23 -0
  21. data/lib/sensu/api/routes/silenced.rb +182 -0
  22. data/lib/sensu/api/routes/stashes.rb +107 -0
  23. data/lib/sensu/api/routes.rb +88 -0
  24. data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
  25. data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
  26. data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
  27. data/lib/sensu/api/utilities/resolve_event.rb +29 -0
  28. data/lib/sensu/api/utilities/servers_info.rb +43 -0
  29. data/lib/sensu/api/utilities/transport_info.rb +43 -0
  30. data/lib/sensu/api/validators/check.rb +55 -0
  31. data/lib/sensu/api/validators/client.rb +35 -0
  32. data/lib/sensu/api/validators/invalid.rb +8 -0
  33. data/lib/sensu/cli.rb +69 -0
  34. data/lib/sensu/client/http_socket.rb +217 -0
  35. data/lib/sensu/client/process.rb +655 -0
  36. data/lib/sensu/client/socket.rb +207 -0
  37. data/lib/sensu/client/utils.rb +53 -0
  38. data/lib/sensu/client/validators/check.rb +53 -0
  39. data/lib/sensu/constants.rb +17 -0
  40. data/lib/sensu/daemon.rb +396 -0
  41. data/lib/sensu/sandbox.rb +19 -0
  42. data/lib/sensu/server/filter.rb +227 -0
  43. data/lib/sensu/server/handle.rb +201 -0
  44. data/lib/sensu/server/mutate.rb +92 -0
  45. data/lib/sensu/server/process.rb +1646 -0
  46. data/lib/sensu/server/socket.rb +54 -0
  47. data/lib/sensu/server/tessen.rb +170 -0
  48. data/lib/sensu/utilities.rb +398 -0
  49. data/lib/sensu.rb +3 -0
  50. data/sensu.gemspec +36 -0
  51. metadata +322 -0
@@ -0,0 +1,55 @@
1
+ require "sensu/api/validators/invalid"
2
+ require "sensu/settings/rules"
3
+ require "sensu/settings/validators/check"
4
+
5
+ module Sensu
6
+ module API
7
+ module Validators
8
+ class Check
9
+ # Include Sensu Settings rules and check validator.
10
+ include Sensu::Settings::Rules
11
+ include Sensu::Settings::Validators::Check
12
+
13
+ # Validate a check result, selectively using check definition
14
+ # validation methods.
15
+ #
16
+ # @param check [Hash]
17
+ def validate_check_result(check)
18
+ must_be_a_string(check[:output]) ||
19
+ invalid(check, "check output must be a string")
20
+ must_be_an_integer(check[:status]) ||
21
+ invalid(check, "check status must be an integer")
22
+ must_be_an_integer(check[:executed]) ||
23
+ invalid(check, "check executed timestamp must be an integer")
24
+ validate_check_name(check)
25
+ validate_check_handling(check)
26
+ validate_check_aggregate(check)
27
+ validate_check_flap_detection(check)
28
+ validate_check_truncate_output(check)
29
+ validate_check_source(check) if check[:source]
30
+ validate_check_ttl(check) if check[:ttl]
31
+ end
32
+
33
+ # Determine if a check definition is valid.
34
+ #
35
+ # @param check [Hash]
36
+ # @return [TrueClass, FalseClass]
37
+ def valid?(check)
38
+ validate_check_result(check)
39
+ true
40
+ rescue Invalid
41
+ false
42
+ end
43
+
44
+ private
45
+
46
+ # This method is called when validation methods encounter an
47
+ # invalid definition object. This method raises an exception
48
+ # to be caught by `valid?()`.
49
+ def invalid(*arguments)
50
+ raise Invalid
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ require "sensu/api/validators/invalid"
2
+ require "sensu/settings/rules"
3
+ require "sensu/settings/validators/client"
4
+
5
+ module Sensu
6
+ module API
7
+ module Validators
8
+ class Client
9
+ # Include Sensu Settings rules and client validator.
10
+ include Sensu::Settings::Rules
11
+ include Sensu::Settings::Validators::Client
12
+
13
+ # Determine if a client definition is valid.
14
+ #
15
+ # @param client [Hash]
16
+ # @return [TrueClass, FalseClass]
17
+ def valid?(client)
18
+ validate_client(client)
19
+ true
20
+ rescue Invalid
21
+ false
22
+ end
23
+
24
+ private
25
+
26
+ # This method is called when `validate_client()` encounters an
27
+ # invalid definition object. This method raises an exception
28
+ # to be caught by `valid?()`.
29
+ def invalid(*arguments)
30
+ raise Invalid
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ module Sensu
2
+ module API
3
+ module Validators
4
+ # The error class for validation.
5
+ class Invalid < RuntimeError; end
6
+ end
7
+ end
8
+ end
data/lib/sensu/cli.rb ADDED
@@ -0,0 +1,69 @@
1
+ require "optparse"
2
+ require "sensu/logger/constants"
3
+
4
+ module Sensu
5
+ class CLI
6
+ # Parse CLI arguments using Ruby stdlib `optparse`. This method
7
+ # provides Sensu with process options (eg. log file), and can
8
+ # provide users with information, such as the Sensu version.
9
+ #
10
+ # @param arguments [Array] to parse.
11
+ # @return [Hash] options
12
+ def self.read(arguments=ARGV)
13
+ options = {}
14
+ if File.exist?("/etc/sensu/config.json")
15
+ options[:config_file] = "/etc/sensu/config.json"
16
+ end
17
+ if Dir.exist?("/etc/sensu/conf.d")
18
+ options[:config_dirs] = ["/etc/sensu/conf.d"]
19
+ end
20
+ optparse = OptionParser.new do |opts|
21
+ opts.on("-h", "--help", "Display this message") do
22
+ puts opts
23
+ exit
24
+ end
25
+ opts.on("-V", "--version", "Display version") do
26
+ puts VERSION
27
+ exit
28
+ end
29
+ opts.on("-c", "--config FILE", "Sensu JSON config FILE. Default: /etc/sensu/config.json (if exists)") do |file|
30
+ options[:config_file] = file
31
+ end
32
+ opts.on("-d", "--config_dir DIR[,DIR]", "DIR or comma-delimited DIR list for Sensu JSON config files. Default: /etc/sensu/conf.d (if exists)") do |dir|
33
+ options[:config_dirs] = dir.split(",")
34
+ end
35
+ opts.on("--validate_config", "Validate the compiled configuration and exit") do
36
+ options[:validate_config] = true
37
+ end
38
+ opts.on("-P", "--print_config", "Print the compiled configuration and exit") do
39
+ options[:print_config] = true
40
+ end
41
+ opts.on("-e", "--extension_dir DIR", "DIR for Sensu extensions") do |dir|
42
+ options[:extension_dir] = dir
43
+ end
44
+ opts.on("-l", "--log FILE", "Log to a given FILE. Default: STDOUT") do |file|
45
+ options[:log_file] = file
46
+ end
47
+ opts.on("-L", "--log_level LEVEL", "Log severity LEVEL") do |level|
48
+ log_level = level.to_s.downcase.to_sym
49
+ unless Logger::LEVELS.include?(log_level)
50
+ puts "Unknown log level: #{level}"
51
+ exit 1
52
+ end
53
+ options[:log_level] = log_level
54
+ end
55
+ opts.on("-v", "--verbose", "Enable verbose logging") do
56
+ options[:log_level] = :debug
57
+ end
58
+ opts.on("-b", "--background", "Fork into the background") do
59
+ options[:daemonize] = true
60
+ end
61
+ opts.on("-p", "--pid_file FILE", "Write the PID to a given FILE") do |file|
62
+ options[:pid_file] = file
63
+ end
64
+ end
65
+ optparse.parse!(arguments)
66
+ options
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,217 @@
1
+ require "base64"
2
+ require "em-http-server"
3
+ require "sensu/json"
4
+ require "sensu/utilities"
5
+ require "sensu/api/utilities/transport_info"
6
+ require "sensu/client/utils"
7
+
8
+ module Sensu
9
+ module Client
10
+ # EventMachine connection handler for the Sensu HTTP client's socket.
11
+ #
12
+ # The Sensu client listens on localhost, port 3031 (by default), for
13
+ # TCP HTTP connections. This allows software running on the host to
14
+ # push check results (that may contain metrics) into Sensu, without
15
+ # needing to know anything about Sensu's internal implementation.
16
+ #
17
+ # All requests and responses expect a json-encoded body (if a body
18
+ # is expected at all).
19
+ #
20
+ # This socket requires receiving a proper HTTP request to any of
21
+ # the following endpoints:
22
+ #
23
+ # GET /info
24
+ # This endpoint returns 200 OK with some basic Sensu info
25
+ #
26
+ # POST /results
27
+ # This endpoint expects application/json body with a check result
28
+ #
29
+ # GET /settings
30
+ # This endpoint responds with 200 OK and the Sensu configuration
31
+ #
32
+ # GET /brew
33
+ # This endpoint gets you some fresh coffee
34
+ class HTTPSocket < EM::HttpServer::Server
35
+ include Sensu::API::Utilities::TransportInfo
36
+ include Utilities
37
+ include CheckUtils
38
+
39
+ attr_accessor :logger, :settings, :transport
40
+
41
+ def initialize
42
+ super
43
+ @endpoints = {
44
+ "/info" => {
45
+ "methods" => {
46
+ "GET" => method(:process_request_info)
47
+ },
48
+ "help" => "Sensu client information"
49
+ },
50
+ "/results" => {
51
+ "methods" => {
52
+ "POST" => method(:process_request_results)
53
+ },
54
+ "help" => "Send check JSON results here"
55
+ },
56
+ "/settings" => {
57
+ "methods" => {
58
+ "GET" => method(:process_request_settings)
59
+ },
60
+ "help" => "Get redacted Sensu settings (requires basic auth). Use ?redacted=false if you want the setting unredacted."
61
+ },
62
+ "/brew" => {
63
+ "methods" => {
64
+ "GET" => Proc.new { |response|
65
+ send_response(418, "I'm a teapot", {
66
+ :response => "I'm a teapot!"
67
+ })
68
+ }
69
+ },
70
+ "help" => "Ask Sensu to brew a cup of joe (try it!)"
71
+ }
72
+ }
73
+ @response = nil
74
+ end
75
+
76
+ def authorized?
77
+ http_options = @settings[:client][:http_socket] || Hash.new
78
+ if http_options[:user] and http_options[:password]
79
+ if @http[:authorization]
80
+ scheme, base64 = @http[:authorization].split("\s")
81
+ if scheme == "Basic"
82
+ user, password = Base64.decode64(base64).split(":")
83
+ return (user == http_options[:user] && password == http_options[:password])
84
+ end
85
+ end
86
+ end
87
+ false
88
+ end
89
+
90
+ def unauthorized_response
91
+ @logger.warn("http socket refusing to serve unauthorized request")
92
+ @response.headers["WWW-Authenticate"] = 'Basic realm="Sensu Client Restricted Area"'
93
+ send_response(401, "Unauthorized", {
94
+ :response => "You must be authenticated using your http_options user and password settings"
95
+ })
96
+ end
97
+
98
+ def send_response(status, status_string, content)
99
+ @logger.debug("http socket sending response", {
100
+ :status => status,
101
+ :status_string => status_string,
102
+ :content => content
103
+ })
104
+ @response.status = status
105
+ @response.status_string = status_string
106
+ @response.content = Sensu::JSON::dump(content)
107
+ @response.send_response
108
+ end
109
+
110
+ def process_request_info
111
+ if @settings[:client][:http_socket] &&
112
+ @settings[:client][:http_socket][:protect_all_endpoints]
113
+ return unauthorized_response unless authorized?
114
+ end
115
+ transport_info do |info|
116
+ send_response(200, "OK", {
117
+ :sensu => {
118
+ :version => VERSION
119
+ },
120
+ :transport => info
121
+ })
122
+ end
123
+ end
124
+
125
+ def process_request_results
126
+ if @settings[:client][:http_socket] &&
127
+ @settings[:client][:http_socket][:protect_all_endpoints]
128
+ return unauthorized_response unless authorized?
129
+ end
130
+ if @http[:content_type] and @http[:content_type].include?("application/json") and @http_content
131
+ begin
132
+ object = Sensu::JSON::load(@http_content)
133
+ if object.is_a?(Array)
134
+ object.each do |check|
135
+ process_check_result(check)
136
+ end
137
+ else
138
+ process_check_result(object)
139
+ end
140
+ send_response(202, "OK", {:response => "ok"})
141
+ rescue Sensu::JSON::ParseError, ArgumentError
142
+ send_response(400, "Failed to parse JSON body", {:response => "Failed to parse JSON body"})
143
+ end
144
+ else
145
+ send_response(415, "Only application/json content type accepted", {:response => "Invalid content type"})
146
+ end
147
+ end
148
+
149
+ def process_request_settings
150
+ return unauthorized_response unless authorized?
151
+ @logger.info("http socket responding to request for configuration settings")
152
+ if @http_query_string and @http_query_string.downcase.include?("redacted=false")
153
+ send_response(200, "OK", @settings.to_hash)
154
+ else
155
+ send_response(200, "OK", redact_sensitive(@settings.to_hash))
156
+ end
157
+ end
158
+
159
+ def http_request_errback(error)
160
+ @logger.error("http socket error while processing request", {
161
+ :error => error.to_s,
162
+ :backtrace => error.backtrace
163
+ })
164
+ @response = EM::DelegatedHttpResponse.new(self)
165
+ @response.content_type "application/json"
166
+ send_response(500, "Internal Server Error", {
167
+ "response" => "Internal Server Error: Check your Sensu logs for error details"
168
+ })
169
+ end
170
+
171
+ # This method is called to process HTTP requests
172
+ def process_http_request
173
+ @logger.debug("http socket processing", {
174
+ :http_request_method => @http_request_method,
175
+ :http_request_uri => @http_request_uri
176
+ })
177
+ @response = EM::DelegatedHttpResponse.new(self)
178
+ @response.content_type "application/json"
179
+ endpoint = @endpoints[@http_request_uri]
180
+ if endpoint
181
+ @logger.debug("http socket endpoint found", {
182
+ :http_request_uri => @http_request_uri,
183
+ :accepted_methods => endpoint["methods"].keys
184
+ })
185
+ method_name = @http_request_method.upcase
186
+ method_handler = endpoint["methods"][method_name]
187
+ if method_handler
188
+ @logger.debug("http socket executing handler", {
189
+ :method_name => method_name,
190
+ :http_request_uri => @http_request_uri
191
+ })
192
+ method_handler.call
193
+ else
194
+ @logger.debug("http socket method is not allowed for endpoint", {
195
+ :method_name => method_name,
196
+ :http_request_uri => @http_request_uri
197
+ })
198
+ send_response(405, "Method Not Allowed", {
199
+ :response => "Valid methods for this endpoint: #{endpoint['methods'].keys}"
200
+ })
201
+ end
202
+ else
203
+ @logger.warn("http socket unknown endpoint requested", :http_request_uri => @http_request_uri)
204
+ help_response = {
205
+ :endpoints => {}
206
+ }
207
+ @endpoints.each do |key, value|
208
+ help_response[:endpoints][key] ||= Hash.new
209
+ help_response[:endpoints][key]["help"] = value["help"]
210
+ help_response[:endpoints][key]["methods"] = value["methods"].keys
211
+ end
212
+ send_response(404, "Not Found", help_response)
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end