sensu 0.21.0 → 0.22.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7241998f1b395c09ed021c94e1391de71b1a1b3
4
- data.tar.gz: 8ed7240bf31290a27826d17584a6e13f3ebe1e56
3
+ metadata.gz: cc83c24129d23bc39b4f06d48b7cdba8abf0f851
4
+ data.tar.gz: 6aca4babe99fb2e63e610204532322af7714f726
5
5
  SHA512:
6
- metadata.gz: 46e0e42fb84c441414534f9f40fd2074cc90ee66964ebef97c72184b2af6c034244686f89d914ef21b70c527638dac7163f2e2049d760f28ef807152d8400dea
7
- data.tar.gz: f9effb8a3907bf25952761c07a058e2df972c43454fe1256086e5c41e11266e4736e9c0a454d70934b00acb55499192632245096764314c968b6e4412f39ad97
6
+ metadata.gz: dae436f84c219b052231da1d79be00a829f312c823020fa9e6c535555c1995ed69fec6c051da467ec3f7a8633333f1365b068e13f68da007d82f34e56a43af93
7
+ data.tar.gz: a164bcfc4ce2673eb3fafd41276d7074659893864f634de39a2832435f01469a4a3e49424ccd25ab5dc5d82b57aed7d86ffe3d5deb4d11404a310ece5c7c43f5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
+ ## 0.22.0 - 2016-01-29
2
+
3
+ ### Features
4
+
5
+ Client registration events are optionally created and processed (handled,
6
+ etc.) when a client is first added to the client registry. To enable this
7
+ functionality, configure a "registration" handler definition on Sensu
8
+ server(s), or define a client specific registration handler in the client
9
+ definition, e.g. `{"client": "registration": {"handler": "debug"}}`.
10
+
11
+ Client auto de-registration on sensu-client process stop is now supported
12
+ by the Sensu package init script. Setting `CLIENT_DEREGISTER_ON_STOP=true`
13
+ and `CLIENT_DEREGISTER_HANDLER=example` in `/etc/default/sensu` will cause
14
+ the Sensu client to publish a check result to trigger the event handler
15
+ named "example", before its process stops.
16
+
17
+ Added support for Sensu client signatures, used to sign client keepalive
18
+ and check result transport messages, for the purposes of source
19
+ (publisher) verification. The client definition attribute "signature" is
20
+ used to set the client signature, e.g. `"signature": "6zvyb8lm7fxcs7yw"`.
21
+ A client signature can only be set once, the client must be deleted from
22
+ the registry before its signature can be changed or removed. Client
23
+ keepalives and check results that are not signed with the correct
24
+ signature are logged (warn) and discarded. This feature is NOT a
25
+ replacement for existing and proven security measures.
26
+
27
+ The Sensu plugin installation tool, `sensu-install`, will no longer
28
+ install a plugin if a or specified version has already been installed.
29
+
30
+ The Sensu client socket now supports UTF-8 encoding.
31
+
1
32
  ## 0.21.0 - 2015-11-13
2
33
 
34
+ ### Important
35
+
36
+ Using the Sensu embedded Ruby for Sensu checks, mutators, and handlers has
37
+ become a common practice. The Sensu 0.21 packages changed the default
38
+ value of `EMBEDDED_RUBY` from `false` to `true`, allowing Sensu plugins to
39
+ use the embedded Ruby by default. This change makes it easier to get
40
+ started with Sensu.
41
+
3
42
  ### Features
4
43
 
5
44
  Added a Sensu plugin installation tool, `sensu-install`, making it easier
data/exe/sensu-install CHANGED
@@ -36,36 +36,60 @@ module Sensu
36
36
  puts "[SENSU-INSTALL] #{message}"
37
37
  end
38
38
 
39
+ def plugin_gem_installed?(raw_gem, options={})
40
+ log "determining if Sensu plugin gem '#{raw_gem}' is already installed ..."
41
+ gem_name, gem_version = raw_gem.split(":")
42
+ gem_command = "gem list -i #{gem_name}"
43
+ gem_command << " --version '#{gem_version}'" if gem_version
44
+ log gem_command if options[:verbose]
45
+ if system(gem_command)
46
+ log "Sensu plugin gem '#{gem_name}' has already been installed"
47
+ true
48
+ else
49
+ log "Sensu plugin gem '#{gem_name}' has not been installed" if options[:verbose]
50
+ false
51
+ end
52
+ end
53
+
54
+ def install_plugin_gem(raw_gem, options={})
55
+ log "installing Sensu plugin gem '#{raw_gem}'"
56
+ gem_name, gem_version = raw_gem.split(":")
57
+ gem_command = "gem install #{gem_name}"
58
+ gem_command << " --version '#{gem_version}'" if gem_version
59
+ gem_command << " --no-ri --no-rdoc"
60
+ gem_command << " --verbose" if options[:verbose]
61
+ gem_command << " --source #{options[:source]}" if options[:source]
62
+ log gem_command if options[:verbose]
63
+ unless system(gem_command)
64
+ log "failed to install Sensu plugin gem '#{gem_name}'"
65
+ log "you can run the sensu-install command again with --verbose for more info" unless options[:verbose]
66
+ log "please take note of any failure messages above"
67
+ log "make sure you have build tools installed (e.g. gcc)"
68
+ log "trying to determine the Sensu plugin homepage for #{gem_name} ..."
69
+ system("gem specification #{gem_name} -r | grep homepage")
70
+ exit 2
71
+ end
72
+ end
73
+
39
74
  def install_plugins(plugins, options={})
40
75
  log "installing Sensu plugins ..."
41
76
  log "provided Sensu plugins: #{plugins}" if options[:verbose]
42
- gems = plugins.map do |plugin|
77
+ plugin_gems = plugins.map do |plugin|
43
78
  if plugin.start_with?("sensu-plugins-")
44
79
  plugin
45
80
  else
46
81
  "sensu-plugins-#{plugin}"
47
82
  end
48
83
  end
49
- log "Sensu plugin gems to be installed: #{gems}" if options[:verbose]
50
- gem_options = "--no-ri --no-rdoc"
51
- gem_options << " --verbose" if options[:verbose]
52
- gem_options << " --source #{options[:source]}" if options[:source]
53
- gems.each do |gem|
54
- log "installing Sensu plugin gem: #{gem}"
55
- gem_command = "gem install #{gem} #{gem_options}"
56
- log gem_command if options[:verbose]
57
- unless system(gem_command)
58
- log "failed to install Sensu plugin gem: #{gem}"
59
- log "you can run the sensu-install command again with --verbose for more info" unless options[:verbose]
60
- log "please take note of any failure messages above"
61
- log "make sure you have build tools installed (e.g. gcc)"
62
- log "trying to determine Sensu plugin homepage for #{gem} ..."
63
- clean_gem = gem.split(":").first
64
- system("gem specification #{clean_gem} -r | grep homepage")
65
- exit 2
66
- end
84
+ log "compiled Sensu plugin gems: #{plugin_gems}" if options[:verbose]
85
+ plugin_gems.reject! do |raw_gem|
86
+ plugin_gem_installed?(raw_gem, options)
87
+ end
88
+ log "Sensu plugin gems to be installed: #{plugin_gems}"
89
+ plugin_gems.each do |raw_gem|
90
+ install_plugin_gem(raw_gem, options)
67
91
  end
68
- log "successfully install Sensu plugins: #{plugins}"
92
+ log "successfully installed Sensu plugins: #{plugins}"
69
93
  end
70
94
 
71
95
  def run
@@ -449,6 +449,7 @@ module Sensu
449
449
  settings.logger.info("deleting client from registry", :client_name => client_name)
450
450
  settings.redis.srem("clients", client_name) do
451
451
  settings.redis.del("client:#{client_name}")
452
+ settings.redis.del("client:#{client_name}:signature")
452
453
  settings.redis.del("events:#{client_name}")
453
454
  settings.redis.smembers("result:#{client_name}") do |checks|
454
455
  checks.each do |check_name|
@@ -77,7 +77,9 @@ module Sensu
77
77
  # check result is composed of a client (name) and a check
78
78
  # definition, containing check `:output` and `:status`. JSON
79
79
  # serialization is used when publishing the check result payload
80
- # to the transport pipe. Transport errors are logged.
80
+ # to the transport pipe. The check result is signed with the
81
+ # client signature if configured, for source validation.
82
+ # Transport errors are logged.
81
83
  #
82
84
  # @param check [Hash]
83
85
  def publish_check_result(check)
@@ -85,6 +87,7 @@ module Sensu
85
87
  :client => @settings[:client][:name],
86
88
  :check => check
87
89
  }
90
+ payload[:signature] = @settings[:client][:signature] if @settings[:client][:signature]
88
91
  @logger.info("publishing check result", :payload => payload)
89
92
  @transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
90
93
  if info[:error]
@@ -141,9 +141,7 @@ module Sensu
141
141
  :client => @settings[:client][:name],
142
142
  :check => check.merge(:issued => Time.now.to_i)
143
143
  }
144
- @logger.info("publishing check result", {
145
- :payload => payload
146
- })
144
+ @logger.info("publishing check result", :payload => payload)
147
145
  @transport.publish(:direct, "results", MultiJson.dump(payload))
148
146
  end
149
147
 
@@ -186,16 +184,14 @@ module Sensu
186
184
  #
187
185
  # @param [String] data to be processed.
188
186
  def process_data(data)
189
- if data.bytes.find { |char| char > 0x80 }
190
- @logger.warn("socket received non-ascii characters")
191
- respond("invalid")
192
- elsif data.strip == "ping"
187
+ if data.strip == "ping"
193
188
  @logger.debug("socket received ping")
194
189
  respond("pong")
195
190
  else
196
- @logger.debug("socket received data", {
197
- :data => data
198
- })
191
+ @logger.debug("socket received data", :data => data)
192
+ unless valid_utf8?(data)
193
+ @logger.warn("data from socket is not a valid UTF-8 sequence, processing it anyways", :data => data)
194
+ end
199
195
  begin
200
196
  parse_check_result(data)
201
197
  rescue => error
@@ -208,6 +204,20 @@ module Sensu
208
204
  end
209
205
  end
210
206
 
207
+ # Tests if the argument (data) is a valid UTF-8 sequence.
208
+ #
209
+ # @param [String] data to be tested.
210
+ def valid_utf8?(data)
211
+ utf8_string_pattern = /\A([\x00-\x7f]|
212
+ [\xc2-\xdf][\x80-\xbf]|
213
+ \xe0[\xa0-\xbf][\x80-\xbf]|
214
+ [\xe1-\xef][\x80-\xbf]{2}|
215
+ \xf0[\x90-\xbf][\x80-\xbf]{2}|
216
+ [\xf1-\xf7][\x80-\xbf]{3})*\z/nx
217
+ data = data.force_encoding('BINARY') if data.respond_to?(:force_encoding)
218
+ return data =~ utf8_string_pattern
219
+ end
220
+
211
221
  # This method is called whenever data is received. For UDP, it
212
222
  # will only be called once, the original data length can be
213
223
  # expected. For TCP, this method may be called several times, data
@@ -1,7 +1,7 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
3
  # Sensu release version.
4
- VERSION = "0.21.0"
4
+ VERSION = "0.22.0"
5
5
 
6
6
  # Sensu check severities.
7
7
  SEVERITIES = %w[ok warning critical unknown]
data/lib/sensu/daemon.rb CHANGED
@@ -4,7 +4,7 @@ gem "multi_json", "1.11.2"
4
4
  gem "eventmachine", "1.0.8"
5
5
 
6
6
  gem "sensu-logger", "1.1.0"
7
- gem "sensu-settings", "3.1.0"
7
+ gem "sensu-settings", "3.3.0"
8
8
  gem "sensu-extension", "1.3.0"
9
9
  gem "sensu-extensions", "1.4.0"
10
10
  gem "sensu-transport", "3.3.0"
@@ -37,19 +37,102 @@ module Sensu
37
37
  @handling_event_count = 0
38
38
  end
39
39
 
40
+ # Create a registration check definition for a client. Client
41
+ # definitions may contain `:registration` configuration,
42
+ # containing custom attributes and handler information. By
43
+ # default, the registration check definition sets the `:handler`
44
+ # to `registration`. If the client provides its own
45
+ # `:registration` configuration, it's deep merged with the
46
+ # defaults. The check `:name`, `:output`, `:status`, `:issued`,
47
+ # and `:executed` values are always overridden to guard against
48
+ # an invalid definition.
49
+ def create_registration_check(client)
50
+ check = {:handler => "registration"}
51
+ if client.has_key?(:registration)
52
+ check = deep_merge(check, client[:registration])
53
+ end
54
+ timestamp = Time.now.to_i
55
+ overrides = {
56
+ :name => "registration",
57
+ :output => "new client registration",
58
+ :status => 1,
59
+ :issued => timestamp,
60
+ :executed => timestamp
61
+ }
62
+ check.merge(overrides)
63
+ end
64
+
65
+ # Create and process a client registration event. A registration
66
+ # event is created when a Sensu client is first added to the
67
+ # client registry. The `create_registration_check()` method is
68
+ # called to create a registration check definition for the
69
+ # client.
70
+ #
71
+ # @param client [Hash] definition.
72
+ def create_client_registration_event(client)
73
+ event = {
74
+ :id => random_uuid,
75
+ :client => client,
76
+ :check => create_registration_check(client),
77
+ :occurrences => 1,
78
+ :action => :create,
79
+ :timestamp => Time.now.to_i
80
+ }
81
+ process_event(event)
82
+ end
83
+
84
+ # Process an initial client registration, when it is first added
85
+ # to the client registry. If a registration handler is defined
86
+ # or the client specifies one, a client registration event is
87
+ # created and processed (handled, etc.) for the client
88
+ # (`create_client_registration_event()`).
89
+ #
90
+ # @param client [Hash] definition.
91
+ def process_client_registration(client)
92
+ if @settings.handler_exists?("registration") || client[:registration]
93
+ create_client_registration_event(client)
94
+ end
95
+ end
96
+
40
97
  # Update the Sensu client registry, stored in Redis. Sensu
41
98
  # client data is used to provide additional event context and
42
- # enable agent health monitoring. JSON serialization is used for
43
- # the client data.
99
+ # enable agent health monitoring. The client registry supports
100
+ # client signatures, unique string identifiers used for
101
+ # keepalive and result source verification. If a client has a
102
+ # signature, all further registry updates for the client must
103
+ # have the same signature. A client can begin to use a signature
104
+ # if one was not previously configured. JSON serialization is
105
+ # used for the stored client data.
44
106
  #
45
107
  # @param client [Hash]
46
- # @param callback [Proc] to call after the the client data has
47
- # been added to (or updated) the registry.
108
+ # @param callback [Proc] to call with the success status
109
+ # (true/false) indicating if the client data has been added to
110
+ # (or updated) the registry or discarded due to client signature
111
+ # mismatch.
48
112
  def update_client_registry(client, &callback)
49
113
  @logger.debug("updating client registry", :client => client)
50
- @redis.set("client:#{client[:name]}", MultiJson.dump(client)) do
51
- @redis.sadd("clients", client[:name]) do
52
- callback.call(client)
114
+ client_key = "client:#{client[:name]}"
115
+ signature_key = "#{client_key}:signature"
116
+ @redis.setnx(signature_key, client[:signature]) do |created|
117
+ process_client_registration(client) if created
118
+ @redis.get(signature_key) do |signature|
119
+ if signature.empty? && client[:signature]
120
+ @redis.set(signature_key, client[:signature])
121
+ end
122
+ if signature.empty? || (client[:signature] == signature)
123
+ @redis.set(client_key, MultiJson.dump(client)) do
124
+ @redis.sadd("clients", client[:name]) do
125
+ callback.call(true) if callback
126
+ end
127
+ end
128
+ else
129
+ @logger.warn("invalid client signature", {
130
+ :client => client,
131
+ :signature => signature
132
+ })
133
+ @logger.warn("not updating client in the registry", :client => client)
134
+ callback.call(false) if callback
135
+ end
53
136
  end
54
137
  end
55
138
  end
@@ -381,13 +464,17 @@ module Sensu
381
464
  :keepalives => false,
382
465
  :version => VERSION
383
466
  }
384
- update_client_registry(client, &callback)
467
+ update_client_registry(client) do
468
+ callback.call(client)
469
+ end
385
470
  end
386
471
 
387
472
  # Retrieve a client (data) from Redis if it exists. If a client
388
473
  # does not already exist, create one (a blank) using the
389
474
  # `client_key` as the client name. Dynamically create client
390
- # data can be updated using the API (POST /clients/:client).
475
+ # data can be updated using the API (POST /clients/:client). If
476
+ # a client does exist and it has a client signature, the check
477
+ # result must have a matching signature or it is discarded.
391
478
  #
392
479
  # @param result [Hash] data.
393
480
  # @param callback [Proc] to be called with client data, either
@@ -397,7 +484,19 @@ module Sensu
397
484
  @redis.get("client:#{client_key}") do |client_json|
398
485
  unless client_json.nil?
399
486
  client = MultiJson.load(client_json)
400
- callback.call(client)
487
+ if client[:signature]
488
+ if client[:signature] == result[:signature]
489
+ callback.call(client)
490
+ else
491
+ @logger.warn("invalid check result signature", {
492
+ :result => result,
493
+ :client => client
494
+ })
495
+ @logger.warn("not retrieving client from the registry", :result => result)
496
+ end
497
+ else
498
+ callback.call(client)
499
+ end
401
500
  else
402
501
  create_client(client_key, &callback)
403
502
  end
@@ -572,9 +671,11 @@ module Sensu
572
671
 
573
672
  # Publish a check result to the transport for processing. A
574
673
  # check result is composed of a client name and a check
575
- # definition, containing check `:output` and `:status`. JSON
576
- # serialization is used when publishing the check result payload
577
- # to the transport pipe. Transport errors are logged.
674
+ # definition, containing check `:output` and `:status`. A client
675
+ # signature is added to the check result payload if one is
676
+ # registered for the client. JSON serialization is used when
677
+ # publishing the check result payload to the transport pipe.
678
+ # Transport errors are logged.
578
679
  #
579
680
  # @param client_name [String]
580
681
  # @param check [Hash]
@@ -583,13 +684,16 @@ module Sensu
583
684
  :client => client_name,
584
685
  :check => check
585
686
  }
586
- @logger.debug("publishing check result", :payload => payload)
587
- @transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
588
- if info[:error]
589
- @logger.error("failed to publish check result", {
590
- :payload => payload,
591
- :error => info[:error].to_s
592
- })
687
+ @redis.get("client:#{client_name}:signature") do |signature|
688
+ payload[:signature] = signature if signature
689
+ @logger.debug("publishing check result", :payload => payload)
690
+ @transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
691
+ if info[:error]
692
+ @logger.error("failed to publish check result", {
693
+ :payload => payload,
694
+ :error => info[:error].to_s
695
+ })
696
+ end
593
697
  end
594
698
  end
595
699
  end
data/sensu.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.add_dependency "uuidtools", "2.1.5"
19
19
  s.add_dependency "eventmachine", "1.0.8"
20
20
  s.add_dependency "sensu-logger", "1.1.0"
21
- s.add_dependency "sensu-settings", "3.1.0"
21
+ s.add_dependency "sensu-settings", "3.3.0"
22
22
  s.add_dependency "sensu-extension", "1.3.0"
23
23
  s.add_dependency "sensu-extensions", "1.4.0"
24
24
  s.add_dependency "sensu-transport", "3.3.0"
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency "rake", "~> 10.3"
32
32
  s.add_development_dependency "rspec", "~> 3.0.0"
33
33
  s.add_development_dependency "em-http-request", "~> 1.1"
34
+ s.add_development_dependency "addressable", "2.3.8"
34
35
 
35
36
  s.files = Dir.glob("{exe,lib}/**/*") + %w[sensu.gemspec README.md CHANGELOG.md MIT-LICENSE.txt]
36
37
  s.executables = s.files.grep(%r{^exe/}) { |file| File.basename(file) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Porter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2015-11-13 00:00:00.000000000 Z
12
+ date: 2016-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -73,14 +73,14 @@ dependencies:
73
73
  requirements:
74
74
  - - '='
75
75
  - !ruby/object:Gem::Version
76
- version: 3.1.0
76
+ version: 3.3.0
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - '='
82
82
  - !ruby/object:Gem::Version
83
- version: 3.1.0
83
+ version: 3.3.0
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: sensu-extension
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -235,6 +235,20 @@ dependencies:
235
235
  - - "~>"
236
236
  - !ruby/object:Gem::Version
237
237
  version: '1.1'
238
+ - !ruby/object:Gem::Dependency
239
+ name: addressable
240
+ requirement: !ruby/object:Gem::Requirement
241
+ requirements:
242
+ - - '='
243
+ - !ruby/object:Gem::Version
244
+ version: 2.3.8
245
+ type: :development
246
+ prerelease: false
247
+ version_requirements: !ruby/object:Gem::Requirement
248
+ requirements:
249
+ - - '='
250
+ - !ruby/object:Gem::Version
251
+ version: 2.3.8
238
252
  description: A monitoring framework that aims to be simple, malleable, and scalable.
239
253
  email:
240
254
  - portertech@gmail.com