portertech-sensu 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +961 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +65 -0
- data/exe/sensu-api +10 -0
- data/exe/sensu-client +10 -0
- data/exe/sensu-install +195 -0
- data/exe/sensu-server +10 -0
- data/lib/sensu/api/http_handler.rb +434 -0
- data/lib/sensu/api/process.rb +79 -0
- data/lib/sensu/api/routes/aggregates.rb +196 -0
- data/lib/sensu/api/routes/checks.rb +44 -0
- data/lib/sensu/api/routes/clients.rb +171 -0
- data/lib/sensu/api/routes/events.rb +86 -0
- data/lib/sensu/api/routes/health.rb +45 -0
- data/lib/sensu/api/routes/info.rb +37 -0
- data/lib/sensu/api/routes/request.rb +44 -0
- data/lib/sensu/api/routes/resolve.rb +32 -0
- data/lib/sensu/api/routes/results.rb +153 -0
- data/lib/sensu/api/routes/settings.rb +23 -0
- data/lib/sensu/api/routes/silenced.rb +182 -0
- data/lib/sensu/api/routes/stashes.rb +107 -0
- data/lib/sensu/api/routes.rb +88 -0
- data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
- data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
- data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
- data/lib/sensu/api/utilities/resolve_event.rb +29 -0
- data/lib/sensu/api/utilities/servers_info.rb +43 -0
- data/lib/sensu/api/utilities/transport_info.rb +43 -0
- data/lib/sensu/api/validators/check.rb +55 -0
- data/lib/sensu/api/validators/client.rb +35 -0
- data/lib/sensu/api/validators/invalid.rb +8 -0
- data/lib/sensu/cli.rb +69 -0
- data/lib/sensu/client/http_socket.rb +217 -0
- data/lib/sensu/client/process.rb +655 -0
- data/lib/sensu/client/socket.rb +207 -0
- data/lib/sensu/client/utils.rb +53 -0
- data/lib/sensu/client/validators/check.rb +53 -0
- data/lib/sensu/constants.rb +17 -0
- data/lib/sensu/daemon.rb +396 -0
- data/lib/sensu/sandbox.rb +19 -0
- data/lib/sensu/server/filter.rb +227 -0
- data/lib/sensu/server/handle.rb +201 -0
- data/lib/sensu/server/mutate.rb +92 -0
- data/lib/sensu/server/process.rb +1646 -0
- data/lib/sensu/server/socket.rb +54 -0
- data/lib/sensu/server/tessen.rb +170 -0
- data/lib/sensu/utilities.rb +398 -0
- data/lib/sensu.rb +3 -0
- data/sensu.gemspec +36 -0
- 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
|
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
|