fluentd 0.14.4-x64-mingw32 → 0.14.5-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +18 -0
  3. data/example/in_forward.conf +3 -0
  4. data/example/in_forward_client.conf +37 -0
  5. data/example/in_forward_shared_key.conf +15 -0
  6. data/example/in_forward_users.conf +24 -0
  7. data/example/out_forward.conf +13 -13
  8. data/example/out_forward_client.conf +109 -0
  9. data/example/out_forward_shared_key.conf +36 -0
  10. data/example/out_forward_users.conf +65 -0
  11. data/example/{out_buffered_null.conf → out_null.conf} +10 -6
  12. data/example/secondary_file.conf +41 -0
  13. data/lib/fluent/agent.rb +3 -1
  14. data/lib/fluent/plugin/buffer.rb +5 -1
  15. data/lib/fluent/plugin/in_forward.rb +300 -50
  16. data/lib/fluent/plugin/in_tail.rb +41 -85
  17. data/lib/fluent/plugin/multi_output.rb +4 -0
  18. data/lib/fluent/plugin/out_forward.rb +326 -209
  19. data/lib/fluent/plugin/out_null.rb +37 -0
  20. data/lib/fluent/plugin/out_secondary_file.rb +128 -0
  21. data/lib/fluent/plugin/out_stdout.rb +38 -2
  22. data/lib/fluent/plugin/output.rb +13 -5
  23. data/lib/fluent/root_agent.rb +1 -1
  24. data/lib/fluent/test/startup_shutdown.rb +33 -0
  25. data/lib/fluent/version.rb +1 -1
  26. data/test/plugin/test_in_forward.rb +906 -441
  27. data/test/plugin/test_in_monitor_agent.rb +4 -0
  28. data/test/plugin/test_in_tail.rb +681 -663
  29. data/test/plugin/test_out_forward.rb +150 -208
  30. data/test/plugin/test_out_null.rb +85 -9
  31. data/test/plugin/test_out_secondary_file.rb +432 -0
  32. data/test/plugin/test_out_stdout.rb +143 -45
  33. data/test/test_root_agent.rb +42 -0
  34. metadata +14 -9
  35. data/lib/fluent/plugin/out_buffered_null.rb +0 -59
  36. data/lib/fluent/plugin/out_buffered_stdout.rb +0 -70
  37. data/test/plugin/test_out_buffered_null.rb +0 -79
  38. data/test/plugin/test_out_buffered_stdout.rb +0 -122
data/lib/fluent/agent.rb CHANGED
@@ -63,6 +63,7 @@ module Fluent
63
63
  conf.elements('filter', 'match').each { |e|
64
64
  pattern = e.arg.empty? ? '**' : e.arg
65
65
  type = e['@type']
66
+ raise ConfigError, "Missing '@type' parameter on <#{e.name}> directive" unless type
66
67
  if e.name == 'filter'
67
68
  add_filter(type, pattern, e)
68
69
  else
@@ -134,7 +135,8 @@ module Fluent
134
135
  output.router = @event_router if output.respond_to?(:router=)
135
136
  output.configure(conf)
136
137
  @outputs << output
137
- if output.respond_to?(:outputs) && (output.is_a?(Fluent::Plugin::MultiOutput) || output.is_a?(Fluent::MultiOutput))
138
+ if output.respond_to?(:outputs) && (output.respond_to?(:multi_output?) && output.multi_output? || output.is_a?(Fluent::MultiOutput))
139
+ # TODO: ruby 2.3 or later: replace `output.respond_to?(:multi_output?) && output.multi_output?` with output&.multi_output?
138
140
  @outputs.push(*output.outputs)
139
141
  end
140
142
  @event_router.add_rule(pattern, output)
@@ -55,7 +55,11 @@ module Fluent
55
55
  # if chunk size (or records) is 95% or more after #write, then that chunk will be enqueued
56
56
  config_param :chunk_full_threshold, :float, default: DEFAULT_CHUNK_FULL_THRESHOLD
57
57
 
58
- Metadata = Struct.new(:timekey, :tag, :variables)
58
+ Metadata = Struct.new(:timekey, :tag, :variables) do
59
+ def empty?
60
+ timekey.nil? && tag.nil? && variables.nil?
61
+ end
62
+ end
59
63
 
60
64
  # for tests
61
65
  attr_accessor :stage_size, :queue_size
@@ -42,6 +42,8 @@ module Fluent
42
42
  config_param :linger_timeout, :integer, default: 0
43
43
  # This option is for Cool.io's loop wait timeout to avoid loop stuck at shutdown. Almost users don't need to change this value.
44
44
  config_param :blocking_timeout, :time, default: 0.5
45
+ desc 'Connections will be disconnected right after receiving first message if this value is true.'
46
+ config_param :deny_keepalive, :bool, default: false
45
47
 
46
48
  desc 'Log warning if received chunk size is larger than this value.'
47
49
  config_param :chunk_size_warn_limit, :size, default: nil
@@ -52,8 +54,77 @@ module Fluent
52
54
  desc "The field name of the client's hostname."
53
55
  config_param :source_hostname_key, :string, default: nil
54
56
 
57
+ config_section :security, required: false, multi: false do
58
+ desc 'The hostname'
59
+ config_param :self_hostname, :string
60
+ desc 'Shared key for authentication'
61
+ config_param :shared_key, :string
62
+ desc 'If true, use user based authentication'
63
+ config_param :user_auth, :bool, default: false
64
+ desc 'Allow anonymous source. <client> sections required if disabled.'
65
+ config_param :allow_anonymous_source, :bool, default: true
66
+
67
+ ### User based authentication
68
+ config_section :user, param_name: :users, required: false, multi: true do
69
+ desc 'The username for authentication'
70
+ config_param :username, :string
71
+ desc 'The password for authentication'
72
+ config_param :password, :string
73
+ end
74
+
75
+ ### Client ip/network authentication & per_host shared key
76
+ config_section :client, param_name: :clients, required: false, multi: true do
77
+ desc 'The IP address or host name of the client'
78
+ config_param :host, :string, default: nil
79
+ desc 'Network address specification'
80
+ config_param :network, :string, default: nil
81
+ desc 'Shared key per client'
82
+ config_param :shared_key, :string, default: nil
83
+ desc 'Array of username.'
84
+ config_param :users, :array, default: []
85
+ end
86
+ end
87
+
55
88
  def configure(conf)
56
89
  super
90
+
91
+ if @security
92
+ if @security.user_auth && @security.users.empty?
93
+ raise Fluent::ConfigError, "<user> sections required if user_auth enabled"
94
+ end
95
+ if !@security.allow_anonymous_source && @security.clients.empty?
96
+ raise Fluent::ConfigError, "<client> sections required if allow_anonymous_source disabled"
97
+ end
98
+
99
+ @nodes = []
100
+
101
+ @security.clients.each do |client|
102
+ if client.host && client.network
103
+ raise Fluent::ConfigError, "both of 'host' and 'network' are specified for client"
104
+ end
105
+ if !client.host && !client.network
106
+ raise Fluent::ConfigError, "Either of 'host' and 'network' must be specified for client"
107
+ end
108
+ source = nil
109
+ if client.host
110
+ begin
111
+ source = IPSocket.getaddress(client.host)
112
+ rescue SocketError => e
113
+ raise Fluent::ConfigError, "host '#{client.host}' cannot be resolved"
114
+ end
115
+ end
116
+ source_addr = begin
117
+ IPAddr.new(source || client.network)
118
+ rescue ArgumentError => e
119
+ raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
120
+ end
121
+ @nodes.push({
122
+ address: source_addr,
123
+ shared_key: (client.shared_key || @security.shared_key),
124
+ users: client.users
125
+ })
126
+ end
127
+ end
57
128
  end
58
129
 
59
130
  def start
@@ -100,7 +171,7 @@ module Fluent
100
171
  def listen(client)
101
172
  log.info "listening fluent socket on #{@bind}:#{@port}"
102
173
  sock = client.listen_tcp(@bind, @port)
103
- s = Coolio::TCPServer.new(sock, nil, Handler, @linger_timeout, log, method(:on_message))
174
+ s = Coolio::TCPServer.new(sock, nil, Handler, @linger_timeout, log, method(:handle_connection))
104
175
  s.listen(@backlog) unless @backlog.nil?
105
176
  s
106
177
  end
@@ -124,6 +195,99 @@ module Fluent
124
195
 
125
196
  private
126
197
 
198
+ def handle_connection(conn)
199
+ send_data = ->(serializer, data){ conn.write serializer.call(data) }
200
+
201
+ log.trace "connected fluent socket", address: conn.remote_addr, port: conn.remote_port
202
+ state = :established
203
+ nonce = nil
204
+ user_auth_salt = nil
205
+
206
+ if @security
207
+ # security enabled session MUST use MessagePack as serialization format
208
+ state = :helo
209
+ nonce = generate_salt
210
+ user_auth_salt = generate_salt
211
+ send_data.call(:to_msgpack.to_proc, generate_helo(nonce, user_auth_salt))
212
+ state = :pingpong
213
+ end
214
+
215
+ log.trace "accepted fluent socket", address: conn.remote_addr, port: conn.remote_port
216
+
217
+ read_messages(conn) do |msg, chunk_size, serializer|
218
+ case state
219
+ when :pingpong
220
+ success, reason_or_salt, shared_key = check_ping(msg, conn.remote_addr, user_auth_salt, nonce)
221
+ unless success
222
+ send_data.call(serializer, generate_pong(false, reason_or_salt, nonce, shared_key))
223
+ conn.close
224
+ next
225
+ end
226
+ send_data.call(serializer, generate_pong(true, reason_or_salt, nonce, shared_key))
227
+
228
+ log.debug "connection established", address: conn.remote_addr, port: conn.remote_port
229
+ state = :established
230
+ when :established
231
+ options = on_message(msg, chunk_size, conn.remote_addr)
232
+ if options && r = response(options)
233
+ send_data.call(serializer, r)
234
+ log.trace "sent response to fluent socket", address: conn.remote_addr, response: r
235
+ if @deny_keepalive
236
+ conn.on_write_complete do
237
+ conn.close
238
+ end
239
+ end
240
+ else
241
+ if @deny_keepalive
242
+ conn.close
243
+ end
244
+ end
245
+ else
246
+ raise "BUG: unknown session state: #{state}"
247
+ end
248
+ end
249
+ end
250
+
251
+ def read_messages(conn, &block)
252
+ feeder = nil
253
+ serializer = nil
254
+ bytes = 0
255
+ conn.on_data do |data|
256
+ # only for first call of callback
257
+ unless feeder
258
+ first = data[0]
259
+ if first == '{' || first == '[' # json
260
+ parser = Yajl::Parser.new
261
+ parser.on_parse_complete = ->(obj){
262
+ block.call(obj, bytes, serializer)
263
+ bytes = 0
264
+ }
265
+ serializer = :to_json.to_proc
266
+ feeder = ->(d){ parser << d }
267
+ else # msgpack
268
+ parser = Fluent::Engine.msgpack_factory.unpacker
269
+ serializer = :to_msgpack.to_proc
270
+ feeder = ->(d){
271
+ parser.feed_each(d){|obj|
272
+ block.call(obj, bytes, serializer)
273
+ bytes = 0
274
+ }
275
+ }
276
+ end
277
+ end
278
+
279
+ bytes += data.bytesize
280
+ feeder.call(data)
281
+ end
282
+ end
283
+
284
+ def response(option)
285
+ if option && option['chunk']
286
+ return { 'ack' => option['chunk'] }
287
+ end
288
+ nil
289
+ end
290
+
127
291
  # message Entry {
128
292
  # 1: long time
129
293
  # 2: object record
@@ -169,7 +333,8 @@ module Fluent
169
333
  log.warn "Input chunk size is larger than 'chunk_size_warn_limit':", tag: tag, source: source_message(peeraddr), limit: @chunk_size_warn_limit, size: chunk_size
170
334
  end
171
335
 
172
- if entries.class == String
336
+ case entries
337
+ when String
173
338
  # PackedForward
174
339
  option = msg[2]
175
340
  size = (option && option['size']) || 0
@@ -178,7 +343,7 @@ module Fluent
178
343
  es = add_source_host(es, peeraddr[2]) if @source_hostname_key
179
344
  router.emit_stream(tag, es)
180
345
 
181
- elsif entries.class == Array
346
+ when Array
182
347
  # Forward
183
348
  es = if @skip_invalid_event
184
349
  check_and_skip_invalid_event(tag, entries, peeraddr)
@@ -246,10 +411,105 @@ module Fluent
246
411
  "host: #{host}, addr: #{addr}, port: #{port}"
247
412
  end
248
413
 
414
+ def select_authenticate_users(node, username)
415
+ if node.nil? || node[:users].empty?
416
+ @security.users.select{|u| u.username == username}
417
+ else
418
+ @security.users.select{|u| node[:users].include?(u.username) && u.username == username}
419
+ end
420
+ end
421
+
422
+ def generate_salt
423
+ OpenSSL::Random.random_bytes(16)
424
+ end
425
+
426
+ def generate_helo(nonce, user_auth_salt)
427
+ log.debug "generating helo"
428
+ # ['HELO', options(hash)]
429
+ ['HELO', {'nonce' => nonce, 'auth' => (@security ? user_auth_salt : ''), 'keepalive' => !@deny_keepalive}]
430
+ end
431
+
432
+ ##### Authentication Handshake
433
+ #
434
+ # 1. (client) connect to server
435
+ # * Socket handshake, checks certificate and its significate (in client, if using SSL)
436
+ # 2. (server)
437
+ # * check network/domain acl (if enabled)
438
+ # * disconnect when failed
439
+ # 3. (server) send HELO
440
+ # * ['HELO', options(hash)]
441
+ # * options:
442
+ # * nonce: string (required)
443
+ # * auth: string or blank_string (string: authentication required, and its salt is this value)
444
+ # 4. (client) send PING
445
+ # * ['PING', selfhostname, sharedkey_salt, sha512_hex(sharedkey_salt + selfhostname + nonce + sharedkey), username || '', sha512_hex(auth_salt + username + password) || '']
446
+ # 5. (server) check PING
447
+ # * check sharedkey
448
+ # * check username / password (if required)
449
+ # * send PONG FAILURE if failed
450
+ # * ['PONG', false, 'reason of authentication failure', '', '']
451
+ # 6. (server) send PONG
452
+ # * ['PONG', bool(authentication result), 'reason if authentication failed', selfhostname, sha512_hex(salt + selfhostname + nonce + sharedkey)]
453
+ # 7. (client) check PONG
454
+ # * check sharedkey
455
+ # * disconnect when failed
456
+ # 8. connection established
457
+ # * send data from client
458
+
459
+ def check_ping(message, remote_addr, user_auth_salt, nonce)
460
+ log.debug "checking ping"
461
+ # ['PING', self_hostname, shared_key_salt, sha512_hex(shared_key_salt + self_hostname + nonce + shared_key), username || '', sha512_hex(auth_salt + username + password) || '']
462
+ unless message.size == 6 && message[0] == 'PING'
463
+ return false, 'invalid ping message'
464
+ end
465
+ _ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
466
+
467
+ node = @nodes.select{|n| n[:address].include?(remote_addr) rescue false }.first
468
+ if !node && !@security.allow_anonymous_source
469
+ log.warn "Anonymous client disallowed", address: remote_addr, hostname: hostname
470
+ return false, "anonymous source host '#{remote_addr}' denied", nil
471
+ end
472
+
473
+ shared_key = node ? node[:shared_key] : @security.shared_key
474
+ serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(nonce).update(shared_key).hexdigest
475
+ if shared_key_hexdigest != serverside
476
+ log.warn "Shared key mismatch", address: remote_addr, hostname: hostname
477
+ return false, 'shared_key mismatch', nil
478
+ end
479
+
480
+ if @security.user_auth
481
+ users = select_authenticate_users(node, username)
482
+ success = false
483
+ users.each do |user|
484
+ passhash = Digest::SHA512.new.update(user_auth_salt).update(username).update(user[:password]).hexdigest
485
+ success ||= (passhash == password_digest)
486
+ end
487
+ unless success
488
+ log.warn "Authentication failed", address: remote_addr, hostname: hostname, username: username
489
+ return false, 'username/password mismatch', nil
490
+ end
491
+ end
492
+
493
+ return true, shared_key_salt, shared_key
494
+ end
495
+
496
+ def generate_pong(auth_result, reason_or_salt, nonce, shared_key)
497
+ log.debug "generating pong"
498
+ # ['PONG', bool(authentication result), 'reason if authentication failed', self_hostname, sha512_hex(salt + self_hostname + nonce + sharedkey)]
499
+ unless auth_result
500
+ return ['PONG', false, reason_or_salt, '', '']
501
+ end
502
+
503
+ shared_key_digest_hex = Digest::SHA512.new.update(reason_or_salt).update(@security.self_hostname).update(nonce).update(shared_key).hexdigest
504
+ ['PONG', true, '', @security.self_hostname, shared_key_digest_hex]
505
+ end
506
+
249
507
  class Handler < Coolio::Socket
508
+ attr_reader :protocol, :remote_port, :remote_addr, :remote_host
509
+
250
510
  PEERADDR_FAILED = ["?", "?", "name resolusion failed", "?"]
251
511
 
252
- def initialize(io, linger_timeout, log, on_message)
512
+ def initialize(io, linger_timeout, log, on_connect_callback)
253
513
  super(io)
254
514
 
255
515
  @peeraddr = nil
@@ -259,8 +519,21 @@ module Fluent
259
519
  io.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
260
520
  end
261
521
 
522
+ ### TODO: disabling name rev resolv
523
+ proto, port, host, addr = ( io.peeraddr rescue PEERADDR_FAILED )
524
+ if addr == '?'
525
+ port, addr = *Socket.unpack_sockaddr_in(io.getpeername) rescue nil
526
+ end
527
+ @protocol = proto
528
+ @remote_port = port
529
+ @remote_addr = addr
530
+ @remote_host = host
531
+ @writing = false
532
+ @closing = false
533
+ @mutex = Mutex.new
534
+
262
535
  @chunk_counter = 0
263
- @on_message = on_message
536
+ @on_connect_callback = on_connect_callback
264
537
  @log = log
265
538
  @log.trace {
266
539
  begin
@@ -269,69 +542,46 @@ module Fluent
269
542
  remote_port = nil
270
543
  remote_addr = nil
271
544
  end
272
- "accepted fluent socket from '#{remote_addr}:#{remote_port}': object_id=#{self.object_id}"
545
+ [ "accepted fluent socket", {address: remote_addr, port: remote_port, instance: self.object_id} ]
273
546
  }
274
547
  end
275
548
 
276
549
  def on_connect
550
+ @on_connect_callback.call(self)
277
551
  end
278
552
 
279
- def on_read(data)
280
- first = data[0]
281
- if first == '{' || first == '['
282
- m = method(:on_read_json)
283
- @serializer = :to_json.to_proc
284
- @y = Yajl::Parser.new
285
- @y.on_parse_complete = lambda { |obj|
286
- option = @on_message.call(obj, @chunk_counter, @peeraddr)
287
- respond option
288
- @chunk_counter = 0
289
- }
290
- else
291
- m = method(:on_read_msgpack)
292
- @serializer = :to_msgpack.to_proc
293
- @u = Fluent::Engine.msgpack_factory.unpacker
294
- end
295
-
296
- (class << self; self; end).module_eval do
297
- define_method(:on_read, m)
298
- end
299
- m.call(data)
553
+ # API to register callback for data arrival
554
+ def on_data(&callback)
555
+ @on_read_callback = callback
300
556
  end
301
557
 
302
- def on_read_json(data)
303
- @chunk_counter += data.bytesize
304
- @y << data
558
+ def on_read(data)
559
+ @on_read_callback.call(data)
305
560
  rescue => e
306
- @log.error "forward error", error: e, error_class: e.class
561
+ @log.error "unexpected error on reading data from client", address: @remote_addr, error: e
307
562
  @log.error_backtrace
308
563
  close
309
564
  end
310
565
 
311
- def on_read_msgpack(data)
312
- @chunk_counter += data.bytesize
313
- @u.feed_each(data) do |obj|
314
- option = @on_message.call(obj, @chunk_counter, @peeraddr)
315
- respond option if option
316
- @chunk_counter = 0
566
+ def on_write_complete
567
+ closing = @mutex.synchronize {
568
+ @writing = false
569
+ @closing
570
+ }
571
+ if closing
572
+ close
317
573
  end
318
- rescue => e
319
- @log.error "forward error", error: e, error_class: e.class
320
- @log.error_backtrace
321
- close
322
574
  end
323
575
 
324
- def respond(option)
325
- if option && option['chunk']
326
- res = { 'ack' => option['chunk'] }
327
- write @serializer.call(res)
328
- @log.trace { "sent response to fluent socket" }
576
+ def close
577
+ writing = @mutex.synchronize {
578
+ @closing = true
579
+ @writing
580
+ }
581
+ unless writing
582
+ super
329
583
  end
330
584
  end
331
-
332
- def on_close
333
- @log.trace { "closed socket" }
334
- end
335
585
  end
336
586
 
337
587
  class HeartbeatRequestHandler < Coolio::IO