fluent-plugin-secure-forward 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -2
- data/Rakefile +9 -0
- data/example/client.conf +6 -0
- data/fluent-plugin-secure-forward.gemspec +2 -1
- data/lib/fluent/plugin/in_secure_forward.rb +7 -206
- data/lib/fluent/plugin/input_session.rb +210 -0
- data/lib/fluent/plugin/openssl_util.rb +38 -0
- data/lib/fluent/plugin/out_secure_forward.rb +24 -266
- data/lib/fluent/plugin/output_node.rb +231 -0
- data/test/helper.rb +29 -0
- data/test/plugin/test_in_secure_forward.rb +10 -0
- data/test/plugin/test_out_secure_forward.rb +10 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b51f31fdd96e025c02cc760ff62119649c41822b
|
4
|
+
data.tar.gz: 8e6173fe782e3fe9f1cb5f919c8f6eac6f43c455
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90bd6309c2b69c62133efe58fd7732c816c1256086ed681198dcc66d949095d3d3bc86fb78fc4e76ad7392ca1fe591d301bab6488a493aa81750eba266d76d1
|
7
|
+
data.tar.gz: ab9cef4259d0071e81fefbe389ffa4840a80bcc5f621f7b1e82bc79781b1278728c124807df925bec0ebfc4fc3e0405d5a6f98cae8331b864247fd6830d9b022
|
data/README.md
CHANGED
@@ -126,7 +126,7 @@ Minimal configurations like this:
|
|
126
126
|
</server>
|
127
127
|
</match>
|
128
128
|
|
129
|
-
|
129
|
+
When specified 2 or more `<server>`, this plugin uses these nodes in simple round-robin order.
|
130
130
|
|
131
131
|
If server requires username/password, set `username` and `password` in `<server>` section:
|
132
132
|
|
@@ -135,10 +135,15 @@ If server requires username/password, set `username` and `password` in `<server>
|
|
135
135
|
shared_key secret_string
|
136
136
|
self_hostname client.fqdn.local
|
137
137
|
<server>
|
138
|
-
host
|
138
|
+
host first.fqdn.local
|
139
139
|
username repeatedly
|
140
140
|
password sushi
|
141
141
|
</server>
|
142
|
+
<server>
|
143
|
+
host second.fqdn.local
|
144
|
+
username sasatatsu
|
145
|
+
password karaage
|
146
|
+
</server>
|
142
147
|
</match>
|
143
148
|
|
144
149
|
## Senario (developer document)
|
data/Rakefile
CHANGED
data/example/client.conf
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
Gem::Specification.new do |gem|
|
3
3
|
gem.name = "fluent-plugin-secure-forward"
|
4
|
-
gem.version = "0.0.
|
4
|
+
gem.version = "0.0.4"
|
5
5
|
gem.authors = ["TAGOMORI Satoshi"]
|
6
6
|
gem.email = ["tagomoris@gmail.com"]
|
7
7
|
gem.summary = %q{Fluentd input/output plugin to forward over SSL with authentications}
|
@@ -16,4 +16,5 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.add_runtime_dependency "fluentd"
|
17
17
|
gem.add_runtime_dependency "fluent-mixin-config-placeholders"
|
18
18
|
gem.add_runtime_dependency "resolve-hostname"
|
19
|
+
gem.add_development_dependency "rake"
|
19
20
|
end
|
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'fluent/mixin/config_placeholders'
|
4
4
|
|
5
|
+
module Fluent
|
6
|
+
class SecureForwardInput < Input
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'input_session'
|
11
|
+
|
5
12
|
module Fluent
|
6
13
|
class SecureForwardInput < Input
|
7
14
|
DEFAULT_SECURE_LISTEN_PORT = 24284
|
@@ -57,7 +64,6 @@ module Fluent
|
|
57
64
|
|
58
65
|
def initialize
|
59
66
|
super
|
60
|
-
require 'resolv'
|
61
67
|
require 'socket'
|
62
68
|
require 'openssl'
|
63
69
|
require 'digest'
|
@@ -214,210 +220,5 @@ module Fluent
|
|
214
220
|
Fluent::Engine.emit(tag, time, record)
|
215
221
|
end
|
216
222
|
end
|
217
|
-
|
218
|
-
class Session # Fluent::SecureForwardInput::Session
|
219
|
-
attr_accessor :receiver
|
220
|
-
attr_accessor :state, :thread, :node, :socket, :unpacker, :auth_salt
|
221
|
-
|
222
|
-
def initialize(receiver, socket)
|
223
|
-
@receiver = receiver
|
224
|
-
|
225
|
-
@state = :helo
|
226
|
-
|
227
|
-
@socket = socket
|
228
|
-
@socket.sync = true
|
229
|
-
|
230
|
-
@ipaddress = nil
|
231
|
-
@node = nil
|
232
|
-
@unpacker = MessagePack::Unpacker.new
|
233
|
-
@thread = Thread.new(&method(:start))
|
234
|
-
end
|
235
|
-
|
236
|
-
def established?
|
237
|
-
@state == :established
|
238
|
-
end
|
239
|
-
|
240
|
-
def generate_salt
|
241
|
-
OpenSSL::Random.random_bytes(16)
|
242
|
-
end
|
243
|
-
|
244
|
-
def check_node(hostname, ipaddress, port, proto)
|
245
|
-
node = nil
|
246
|
-
family = Socket.const_get(proto)
|
247
|
-
@receiver.nodes.each do |n|
|
248
|
-
proto, port, host, ipaddr, family_num, socktype_num, proto_num = Socket.getaddrinfo(n[:host], port, family).first
|
249
|
-
if ipaddr == ipaddress
|
250
|
-
node = n
|
251
|
-
break
|
252
|
-
end
|
253
|
-
end
|
254
|
-
node
|
255
|
-
end
|
256
|
-
|
257
|
-
## not implemented yet
|
258
|
-
# def check_hostname_reverse_lookup(ipaddress)
|
259
|
-
# rev_name = Resolv.getname(ipaddress)
|
260
|
-
# proto, port, host, ipaddr, family_num, socktype_num, proto_num = Socket.getaddrinfo(rev_name, DUMMY_PORT)
|
261
|
-
# unless ipaddr == ipaddress
|
262
|
-
# return false
|
263
|
-
# end
|
264
|
-
# true
|
265
|
-
# end
|
266
|
-
|
267
|
-
def generate_helo
|
268
|
-
$log.debug "generating helo"
|
269
|
-
# ['HELO', options(hash)]
|
270
|
-
[ 'HELO', {'auth' => (@receiver.authentication ? @auth_key_salt : ''), 'keepalive' => @receiver.allow_keepalive } ]
|
271
|
-
end
|
272
|
-
|
273
|
-
def check_ping(message)
|
274
|
-
$log.debug "checking ping"
|
275
|
-
# ['PING', self_hostname, shared_key\_salt, sha512\_hex(shared_key\_salt + self_hostname + shared_key),
|
276
|
-
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
277
|
-
unless message.size == 6 && message[0] == 'PING'
|
278
|
-
return false, 'invalid ping message'
|
279
|
-
end
|
280
|
-
ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
|
281
|
-
|
282
|
-
shared_key = if @node && @node[:shared_key]
|
283
|
-
@node[:shared_key]
|
284
|
-
else
|
285
|
-
@receiver.shared_key
|
286
|
-
end
|
287
|
-
serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(shared_key).hexdigest
|
288
|
-
if shared_key_hexdigest != serverside
|
289
|
-
$log.warn "Shared key mismatch from '#{hostname}'"
|
290
|
-
return false, 'shared_key mismatch'
|
291
|
-
end
|
292
|
-
|
293
|
-
if @receiver.authentication
|
294
|
-
users = @receiver.select_authenticate_users(@node, username)
|
295
|
-
success = false
|
296
|
-
users.each do |user|
|
297
|
-
passhash = Digest::SHA512.new.update(@auth_key_salt).update(username).update(user[:password]).hexdigest
|
298
|
-
success ||= (passhash == password_digest)
|
299
|
-
end
|
300
|
-
unless success
|
301
|
-
$log.warn "Authentication failed from client '#{hostname}', username '#{username}'"
|
302
|
-
return false, 'username/password mismatch'
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
return true, shared_key_salt
|
307
|
-
end
|
308
|
-
|
309
|
-
def generate_pong(auth_result, reason_or_salt)
|
310
|
-
$log.debug "generating pong"
|
311
|
-
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
312
|
-
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
313
|
-
if not auth_result
|
314
|
-
return ['PONG', false, reason_or_salt, '', '']
|
315
|
-
end
|
316
|
-
|
317
|
-
shared_key = if @node && @node[:shared_key]
|
318
|
-
@node[:shared_key]
|
319
|
-
else
|
320
|
-
@receiver.shared_key
|
321
|
-
end
|
322
|
-
shared_key_hex = Digest::SHA512.new.update(reason_or_salt).update(@receiver.self_hostname).update(shared_key).hexdigest
|
323
|
-
[ 'PONG', true, '', @receiver.self_hostname, shared_key_hex ]
|
324
|
-
end
|
325
|
-
|
326
|
-
def on_read(data)
|
327
|
-
$log.debug "on_read"
|
328
|
-
if self.established?
|
329
|
-
@receiver.on_message(data)
|
330
|
-
end
|
331
|
-
|
332
|
-
case @state
|
333
|
-
when :pingpong
|
334
|
-
success, reason_or_salt = self.check_ping(data)
|
335
|
-
if not success
|
336
|
-
send_data generate_pong(false, reason_or_salt)
|
337
|
-
self.shutdown
|
338
|
-
return
|
339
|
-
end
|
340
|
-
send_data generate_pong(true, reason_or_salt)
|
341
|
-
|
342
|
-
$log.debug "connection established"
|
343
|
-
@state = :established
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
def send_data(data)
|
348
|
-
# not nonblock because write data (response) needs sequence
|
349
|
-
@socket.write data.to_msgpack
|
350
|
-
end
|
351
|
-
|
352
|
-
def start
|
353
|
-
$log.debug "starting server"
|
354
|
-
|
355
|
-
$log.trace "accepting ssl session"
|
356
|
-
begin
|
357
|
-
@socket.accept
|
358
|
-
rescue OpenSSL::SSL::SSLError => e
|
359
|
-
$log.debug "failed to establish ssl session"
|
360
|
-
self.shutdown
|
361
|
-
return
|
362
|
-
end
|
363
|
-
|
364
|
-
proto, port, host, ipaddr = @socket.io.addr
|
365
|
-
@node = check_node(host, ipaddr, port, proto)
|
366
|
-
if @node.nil? && (! @receiver.allow_anonymous_source)
|
367
|
-
$log.warn "Connection required from unknown host '#{host}' (#{ipaddr}), disconnecting..."
|
368
|
-
self.shutdown
|
369
|
-
return
|
370
|
-
end
|
371
|
-
|
372
|
-
@auth_key_salt = generate_salt
|
373
|
-
|
374
|
-
buf = ''
|
375
|
-
read_length = @receiver.read_length
|
376
|
-
read_interval = @receiver.read_interval
|
377
|
-
socket_interval = @receiver.socket_interval
|
378
|
-
|
379
|
-
send_data generate_helo()
|
380
|
-
@state = :pingpong
|
381
|
-
|
382
|
-
loop do
|
383
|
-
begin
|
384
|
-
while @socket.read_nonblock(read_length, buf)
|
385
|
-
if buf == ''
|
386
|
-
sleep read_interval
|
387
|
-
next
|
388
|
-
end
|
389
|
-
@unpacker.feed_each(buf, &method(:on_read))
|
390
|
-
buf = ''
|
391
|
-
end
|
392
|
-
rescue OpenSSL::SSL::SSLError => e
|
393
|
-
# to wait i/o restart
|
394
|
-
sleep socket_interval
|
395
|
-
rescue EOFError => e
|
396
|
-
$log.debug "Connection closed from '#{host}'(#{ipaddr})"
|
397
|
-
break
|
398
|
-
end
|
399
|
-
end
|
400
|
-
rescue => e
|
401
|
-
$log.warn e
|
402
|
-
ensure
|
403
|
-
self.shutdown
|
404
|
-
end
|
405
|
-
|
406
|
-
def shutdown
|
407
|
-
@state = :closed
|
408
|
-
if @thread == Thread.current
|
409
|
-
@socket.close
|
410
|
-
@thread.kill
|
411
|
-
else
|
412
|
-
if @thread
|
413
|
-
@thread.kill
|
414
|
-
@thread.join
|
415
|
-
end
|
416
|
-
@socket.close
|
417
|
-
end
|
418
|
-
rescue => e
|
419
|
-
$log.debug "#{e.class}:#{e.message}"
|
420
|
-
end
|
421
|
-
end
|
422
223
|
end
|
423
224
|
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# require 'msgpack'
|
2
|
+
# require 'socket'
|
3
|
+
# require 'openssl'
|
4
|
+
# require 'digest'
|
5
|
+
### require 'resolv'
|
6
|
+
|
7
|
+
class Fluent::SecureForwardInput::Session
|
8
|
+
attr_accessor :receiver
|
9
|
+
attr_accessor :state, :thread, :node, :socket, :unpacker, :auth_salt
|
10
|
+
|
11
|
+
def initialize(receiver, socket)
|
12
|
+
@receiver = receiver
|
13
|
+
|
14
|
+
@state = :helo
|
15
|
+
|
16
|
+
@socket = socket
|
17
|
+
@socket.sync = true
|
18
|
+
|
19
|
+
@ipaddress = nil
|
20
|
+
@node = nil
|
21
|
+
@unpacker = MessagePack::Unpacker.new
|
22
|
+
@thread = Thread.new(&method(:start))
|
23
|
+
end
|
24
|
+
|
25
|
+
def established?
|
26
|
+
@state == :established
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_salt
|
30
|
+
OpenSSL::Random.random_bytes(16)
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_node(hostname, ipaddress, port, proto)
|
34
|
+
node = nil
|
35
|
+
family = Socket.const_get(proto)
|
36
|
+
@receiver.nodes.each do |n|
|
37
|
+
proto, port, host, ipaddr, family_num, socktype_num, proto_num = Socket.getaddrinfo(n[:host], port, family).first
|
38
|
+
if ipaddr == ipaddress
|
39
|
+
node = n
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
node
|
44
|
+
end
|
45
|
+
|
46
|
+
## not implemented yet
|
47
|
+
# def check_hostname_reverse_lookup(ipaddress)
|
48
|
+
# rev_name = Resolv.getname(ipaddress)
|
49
|
+
# proto, port, host, ipaddr, family_num, socktype_num, proto_num = Socket.getaddrinfo(rev_name, DUMMY_PORT)
|
50
|
+
# unless ipaddr == ipaddress
|
51
|
+
# return false
|
52
|
+
# end
|
53
|
+
# true
|
54
|
+
# end
|
55
|
+
|
56
|
+
def generate_helo
|
57
|
+
$log.debug "generating helo"
|
58
|
+
# ['HELO', options(hash)]
|
59
|
+
[ 'HELO', {'auth' => (@receiver.authentication ? @auth_key_salt : ''), 'keepalive' => @receiver.allow_keepalive } ]
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_ping(message)
|
63
|
+
$log.debug "checking ping"
|
64
|
+
# ['PING', self_hostname, shared_key\_salt, sha512\_hex(shared_key\_salt + self_hostname + shared_key),
|
65
|
+
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
66
|
+
unless message.size == 6 && message[0] == 'PING'
|
67
|
+
return false, 'invalid ping message'
|
68
|
+
end
|
69
|
+
ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
|
70
|
+
|
71
|
+
shared_key = if @node && @node[:shared_key]
|
72
|
+
@node[:shared_key]
|
73
|
+
else
|
74
|
+
@receiver.shared_key
|
75
|
+
end
|
76
|
+
serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(shared_key).hexdigest
|
77
|
+
if shared_key_hexdigest != serverside
|
78
|
+
$log.warn "Shared key mismatch from '#{hostname}'"
|
79
|
+
return false, 'shared_key mismatch'
|
80
|
+
end
|
81
|
+
|
82
|
+
if @receiver.authentication
|
83
|
+
users = @receiver.select_authenticate_users(@node, username)
|
84
|
+
success = false
|
85
|
+
users.each do |user|
|
86
|
+
passhash = Digest::SHA512.new.update(@auth_key_salt).update(username).update(user[:password]).hexdigest
|
87
|
+
success ||= (passhash == password_digest)
|
88
|
+
end
|
89
|
+
unless success
|
90
|
+
$log.warn "Authentication failed from client '#{hostname}', username '#{username}'"
|
91
|
+
return false, 'username/password mismatch'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
return true, shared_key_salt
|
96
|
+
end
|
97
|
+
|
98
|
+
def generate_pong(auth_result, reason_or_salt)
|
99
|
+
$log.debug "generating pong"
|
100
|
+
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
101
|
+
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
102
|
+
if not auth_result
|
103
|
+
return ['PONG', false, reason_or_salt, '', '']
|
104
|
+
end
|
105
|
+
|
106
|
+
shared_key = if @node && @node[:shared_key]
|
107
|
+
@node[:shared_key]
|
108
|
+
else
|
109
|
+
@receiver.shared_key
|
110
|
+
end
|
111
|
+
shared_key_hex = Digest::SHA512.new.update(reason_or_salt).update(@receiver.self_hostname).update(shared_key).hexdigest
|
112
|
+
[ 'PONG', true, '', @receiver.self_hostname, shared_key_hex ]
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_read(data)
|
116
|
+
$log.debug "on_read"
|
117
|
+
if self.established?
|
118
|
+
@receiver.on_message(data)
|
119
|
+
end
|
120
|
+
|
121
|
+
case @state
|
122
|
+
when :pingpong
|
123
|
+
success, reason_or_salt = self.check_ping(data)
|
124
|
+
if not success
|
125
|
+
send_data generate_pong(false, reason_or_salt)
|
126
|
+
self.shutdown
|
127
|
+
return
|
128
|
+
end
|
129
|
+
send_data generate_pong(true, reason_or_salt)
|
130
|
+
|
131
|
+
$log.debug "connection established"
|
132
|
+
@state = :established
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def send_data(data)
|
137
|
+
# not nonblock because write data (response) needs sequence
|
138
|
+
@socket.write data.to_msgpack
|
139
|
+
end
|
140
|
+
|
141
|
+
def start
|
142
|
+
$log.debug "starting server"
|
143
|
+
|
144
|
+
$log.trace "accepting ssl session"
|
145
|
+
begin
|
146
|
+
@socket.accept
|
147
|
+
rescue OpenSSL::SSL::SSLError => e
|
148
|
+
$log.debug "failed to establish ssl session"
|
149
|
+
self.shutdown
|
150
|
+
return
|
151
|
+
end
|
152
|
+
|
153
|
+
proto, port, host, ipaddr = @socket.io.addr
|
154
|
+
@node = check_node(host, ipaddr, port, proto)
|
155
|
+
if @node.nil? && (! @receiver.allow_anonymous_source)
|
156
|
+
$log.warn "Connection required from unknown host '#{host}' (#{ipaddr}), disconnecting..."
|
157
|
+
self.shutdown
|
158
|
+
return
|
159
|
+
end
|
160
|
+
|
161
|
+
@auth_key_salt = generate_salt
|
162
|
+
|
163
|
+
buf = ''
|
164
|
+
read_length = @receiver.read_length
|
165
|
+
read_interval = @receiver.read_interval
|
166
|
+
socket_interval = @receiver.socket_interval
|
167
|
+
|
168
|
+
send_data generate_helo()
|
169
|
+
@state = :pingpong
|
170
|
+
|
171
|
+
loop do
|
172
|
+
begin
|
173
|
+
while @socket.read_nonblock(read_length, buf)
|
174
|
+
if buf == ''
|
175
|
+
sleep read_interval
|
176
|
+
next
|
177
|
+
end
|
178
|
+
@unpacker.feed_each(buf, &method(:on_read))
|
179
|
+
buf = ''
|
180
|
+
end
|
181
|
+
rescue OpenSSL::SSL::SSLError => e
|
182
|
+
# to wait i/o restart
|
183
|
+
sleep socket_interval
|
184
|
+
rescue EOFError => e
|
185
|
+
$log.debug "Connection closed from '#{host}'(#{ipaddr})"
|
186
|
+
break
|
187
|
+
end
|
188
|
+
end
|
189
|
+
rescue => e
|
190
|
+
$log.warn "unexpected error in in_secure_forward", :error_class => e.class, :error => e
|
191
|
+
ensure
|
192
|
+
self.shutdown
|
193
|
+
end
|
194
|
+
|
195
|
+
def shutdown
|
196
|
+
@state = :closed
|
197
|
+
if @thread == Thread.current
|
198
|
+
@socket.close
|
199
|
+
@thread.kill
|
200
|
+
else
|
201
|
+
if @thread
|
202
|
+
@thread.kill
|
203
|
+
@thread.join
|
204
|
+
end
|
205
|
+
@socket.close
|
206
|
+
end
|
207
|
+
rescue => e
|
208
|
+
$log.debug "#{e.class}:#{e.message}"
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Fluent::SecureForwardOutput::OpenSSLUtil
|
2
|
+
def self.verify_result_name(code)
|
3
|
+
case code
|
4
|
+
when OpenSSL::X509::V_OK then 'V_OK'
|
5
|
+
when OpenSSL::X509::V_ERR_AKID_SKID_MISMATCH then 'V_ERR_AKID_SKID_MISMATCH'
|
6
|
+
when OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION then 'V_ERR_APPLICATION_VERIFICATION'
|
7
|
+
when OpenSSL::X509::V_ERR_CERT_CHAIN_TOO_LONG then 'V_ERR_CERT_CHAIN_TOO_LONG'
|
8
|
+
when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then 'V_ERR_CERT_HAS_EXPIRED'
|
9
|
+
when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then 'V_ERR_CERT_NOT_YET_VALID'
|
10
|
+
when OpenSSL::X509::V_ERR_CERT_REJECTED then 'V_ERR_CERT_REJECTED'
|
11
|
+
when OpenSSL::X509::V_ERR_CERT_REVOKED then 'V_ERR_CERT_REVOKED'
|
12
|
+
when OpenSSL::X509::V_ERR_CERT_SIGNATURE_FAILURE then 'V_ERR_CERT_SIGNATURE_FAILURE'
|
13
|
+
when OpenSSL::X509::V_ERR_CERT_UNTRUSTED then 'V_ERR_CERT_UNTRUSTED'
|
14
|
+
when OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED then 'V_ERR_CRL_HAS_EXPIRED'
|
15
|
+
when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID then 'V_ERR_CRL_NOT_YET_VALID'
|
16
|
+
when OpenSSL::X509::V_ERR_CRL_SIGNATURE_FAILURE then 'V_ERR_CRL_SIGNATURE_FAILURE'
|
17
|
+
when OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT then 'V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT'
|
18
|
+
when OpenSSL::X509::V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD then 'V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD'
|
19
|
+
when OpenSSL::X509::V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD then 'V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD'
|
20
|
+
when OpenSSL::X509::V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD then 'V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD'
|
21
|
+
when OpenSSL::X509::V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD then 'V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD'
|
22
|
+
when OpenSSL::X509::V_ERR_INVALID_CA then 'V_ERR_INVALID_CA'
|
23
|
+
when OpenSSL::X509::V_ERR_INVALID_PURPOSE then 'V_ERR_INVALID_PURPOSE'
|
24
|
+
when OpenSSL::X509::V_ERR_KEYUSAGE_NO_CERTSIGN then 'V_ERR_KEYUSAGE_NO_CERTSIGN'
|
25
|
+
when OpenSSL::X509::V_ERR_OUT_OF_MEM then 'V_ERR_OUT_OF_MEM'
|
26
|
+
when OpenSSL::X509::V_ERR_PATH_LENGTH_EXCEEDED then 'V_ERR_PATH_LENGTH_EXCEEDED'
|
27
|
+
when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN then 'V_ERR_SELF_SIGNED_CERT_IN_CHAIN'
|
28
|
+
when OpenSSL::X509::V_ERR_SUBJECT_ISSUER_MISMATCH then 'V_ERR_SUBJECT_ISSUER_MISMATCH'
|
29
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY then 'V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY'
|
30
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE then 'V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY'
|
31
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE then 'V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE'
|
32
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL then 'V_ERR_UNABLE_TO_GET_CRL'
|
33
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT then 'V_ERR_UNABLE_TO_GET_ISSUER_CERT'
|
34
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY then 'V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
|
35
|
+
when OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then 'V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'fluent/mixin/config_placeholders'
|
4
4
|
|
5
|
+
module Fluent
|
6
|
+
class SecureForwardOutput < ObjectBufferedOutput
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'output_node'
|
11
|
+
|
5
12
|
module Fluent
|
6
13
|
class SecureForwardOutput < ObjectBufferedOutput
|
7
14
|
DEFAULT_SECURE_CONNECT_PORT = 24284
|
@@ -74,9 +81,8 @@ module Fluent
|
|
74
81
|
raise Fluent::ConfigError, "unknown config tag name #{element.name}"
|
75
82
|
end
|
76
83
|
end
|
77
|
-
|
78
|
-
|
79
|
-
end
|
84
|
+
@next_node = 0
|
85
|
+
@mutex = Mutex.new
|
80
86
|
|
81
87
|
@hostname_resolver = Resolve::Hostname.new(:system_resolver => true)
|
82
88
|
|
@@ -84,8 +90,21 @@ module Fluent
|
|
84
90
|
end
|
85
91
|
|
86
92
|
def select_node
|
87
|
-
|
88
|
-
@nodes.
|
93
|
+
tries = 0
|
94
|
+
nodes = @nodes.size
|
95
|
+
@mutex.synchronize {
|
96
|
+
n = nil
|
97
|
+
while tries <= nodes
|
98
|
+
n = @nodes[@next_node]
|
99
|
+
@next_node += 1
|
100
|
+
@next_node = 0 if @next_node >= nodes
|
101
|
+
|
102
|
+
return n if n && n.established?
|
103
|
+
|
104
|
+
tries += 1
|
105
|
+
end
|
106
|
+
nil
|
107
|
+
}
|
89
108
|
end
|
90
109
|
|
91
110
|
def start
|
@@ -168,266 +187,5 @@ module Fluent
|
|
168
187
|
# writeRawBody(packed_es)
|
169
188
|
es.write_to(ssl)
|
170
189
|
end
|
171
|
-
|
172
|
-
class Node # Fluent::SecureForwardOutput::Node
|
173
|
-
attr_accessor :host, :port, :hostlabel, :shared_key, :username, :password
|
174
|
-
attr_accessor :authentication, :keepalive
|
175
|
-
attr_accessor :socket, :sslsession, :unpacker, :shared_key_salt, :state
|
176
|
-
|
177
|
-
def initialize(sender, shared_key, conf)
|
178
|
-
@sender = sender
|
179
|
-
@shared_key = shared_key
|
180
|
-
|
181
|
-
@host = conf['host']
|
182
|
-
@port = (conf['port'] || DEFAULT_SECURE_CONNECT_PORT).to_i
|
183
|
-
@hostlabel = conf['hostlabel'] || conf['host']
|
184
|
-
@username = conf['username'] || ''
|
185
|
-
@password = conf['password'] || ''
|
186
|
-
|
187
|
-
@authentication = nil
|
188
|
-
@keepalive = nil
|
189
|
-
|
190
|
-
@socket = nil
|
191
|
-
@sslsession = nil
|
192
|
-
@unpacker = MessagePack::Unpacker.new
|
193
|
-
|
194
|
-
@shared_key_salt = generate_salt
|
195
|
-
@state = :helo
|
196
|
-
@thread = nil
|
197
|
-
end
|
198
|
-
|
199
|
-
def dup
|
200
|
-
Node.new(
|
201
|
-
@sender,
|
202
|
-
@shared_key,
|
203
|
-
{'host' => @host, 'port' => @port, 'hostlabel' => @hostlabel, 'username' => @username, 'password' => @password}
|
204
|
-
)
|
205
|
-
end
|
206
|
-
|
207
|
-
def start
|
208
|
-
@thread = Thread.new(&method(:connect))
|
209
|
-
end
|
210
|
-
|
211
|
-
def shutdown
|
212
|
-
$log.debug "shutting down node #{@host}"
|
213
|
-
@state = :closed
|
214
|
-
|
215
|
-
if @thread == Thread.current
|
216
|
-
@sslsession.close if @sslsession
|
217
|
-
@socket.close if @socket
|
218
|
-
@thread.kill
|
219
|
-
else
|
220
|
-
if @thread
|
221
|
-
@thread.kill
|
222
|
-
@thread.join
|
223
|
-
end
|
224
|
-
@sslsession.close if @sslsession
|
225
|
-
@socket.close if @socket
|
226
|
-
end
|
227
|
-
rescue => e
|
228
|
-
$log.debug "error on node shutdown #{e.class}:#{e.message}"
|
229
|
-
end
|
230
|
-
|
231
|
-
def verify_result_name(code)
|
232
|
-
case code
|
233
|
-
when OpenSSL::X509::V_OK then 'V_OK'
|
234
|
-
when OpenSSL::X509::V_ERR_AKID_SKID_MISMATCH then 'V_ERR_AKID_SKID_MISMATCH'
|
235
|
-
when OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION then 'V_ERR_APPLICATION_VERIFICATION'
|
236
|
-
when OpenSSL::X509::V_ERR_CERT_CHAIN_TOO_LONG then 'V_ERR_CERT_CHAIN_TOO_LONG'
|
237
|
-
when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then 'V_ERR_CERT_HAS_EXPIRED'
|
238
|
-
when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then 'V_ERR_CERT_NOT_YET_VALID'
|
239
|
-
when OpenSSL::X509::V_ERR_CERT_REJECTED then 'V_ERR_CERT_REJECTED'
|
240
|
-
when OpenSSL::X509::V_ERR_CERT_REVOKED then 'V_ERR_CERT_REVOKED'
|
241
|
-
when OpenSSL::X509::V_ERR_CERT_SIGNATURE_FAILURE then 'V_ERR_CERT_SIGNATURE_FAILURE'
|
242
|
-
when OpenSSL::X509::V_ERR_CERT_UNTRUSTED then 'V_ERR_CERT_UNTRUSTED'
|
243
|
-
when OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED then 'V_ERR_CRL_HAS_EXPIRED'
|
244
|
-
when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID then 'V_ERR_CRL_NOT_YET_VALID'
|
245
|
-
when OpenSSL::X509::V_ERR_CRL_SIGNATURE_FAILURE then 'V_ERR_CRL_SIGNATURE_FAILURE'
|
246
|
-
when OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT then 'V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT'
|
247
|
-
when OpenSSL::X509::V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD then 'V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD'
|
248
|
-
when OpenSSL::X509::V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD then 'V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD'
|
249
|
-
when OpenSSL::X509::V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD then 'V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD'
|
250
|
-
when OpenSSL::X509::V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD then 'V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD'
|
251
|
-
when OpenSSL::X509::V_ERR_INVALID_CA then 'V_ERR_INVALID_CA'
|
252
|
-
when OpenSSL::X509::V_ERR_INVALID_PURPOSE then 'V_ERR_INVALID_PURPOSE'
|
253
|
-
when OpenSSL::X509::V_ERR_KEYUSAGE_NO_CERTSIGN then 'V_ERR_KEYUSAGE_NO_CERTSIGN'
|
254
|
-
when OpenSSL::X509::V_ERR_OUT_OF_MEM then 'V_ERR_OUT_OF_MEM'
|
255
|
-
when OpenSSL::X509::V_ERR_PATH_LENGTH_EXCEEDED then 'V_ERR_PATH_LENGTH_EXCEEDED'
|
256
|
-
when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN then 'V_ERR_SELF_SIGNED_CERT_IN_CHAIN'
|
257
|
-
when OpenSSL::X509::V_ERR_SUBJECT_ISSUER_MISMATCH then 'V_ERR_SUBJECT_ISSUER_MISMATCH'
|
258
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY then 'V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY'
|
259
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE then 'V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY'
|
260
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE then 'V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE'
|
261
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL then 'V_ERR_UNABLE_TO_GET_CRL'
|
262
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT then 'V_ERR_UNABLE_TO_GET_ISSUER_CERT'
|
263
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY then 'V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
|
264
|
-
when OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then 'V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE'
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def established?
|
269
|
-
@state == :established
|
270
|
-
end
|
271
|
-
|
272
|
-
def generate_salt
|
273
|
-
OpenSSL::Random.random_bytes(16)
|
274
|
-
end
|
275
|
-
|
276
|
-
def check_helo(message)
|
277
|
-
$log.debug "checking helo"
|
278
|
-
# ['HELO', options(hash)]
|
279
|
-
unless message.size == 2 && message[0] == 'HELO'
|
280
|
-
return false
|
281
|
-
end
|
282
|
-
opts = message[1]
|
283
|
-
@authentication = opts['auth']
|
284
|
-
@keepalive = opts['keepalive']
|
285
|
-
true
|
286
|
-
end
|
287
|
-
|
288
|
-
def generate_ping
|
289
|
-
$log.debug "generating ping"
|
290
|
-
# ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + shared_key),
|
291
|
-
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
292
|
-
shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt).update(@sender.self_hostname).update(@shared_key).hexdigest
|
293
|
-
ping = ['PING', @sender.self_hostname, @shared_key_salt, shared_key_hexdigest]
|
294
|
-
if @authentication != ''
|
295
|
-
password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
|
296
|
-
ping.push(@username, password_hexdigest)
|
297
|
-
else
|
298
|
-
ping.push('','')
|
299
|
-
end
|
300
|
-
ping
|
301
|
-
end
|
302
|
-
|
303
|
-
def check_pong(message)
|
304
|
-
$log.debug "checking pong"
|
305
|
-
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
306
|
-
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
307
|
-
unless message.size == 5 && message[0] == 'PONG'
|
308
|
-
return false, 'invalid format for PONG message'
|
309
|
-
end
|
310
|
-
pong, auth_result, reason, hostname, shared_key_hexdigest = message
|
311
|
-
|
312
|
-
unless auth_result
|
313
|
-
return false, 'authentication failed: ' + reason
|
314
|
-
end
|
315
|
-
|
316
|
-
clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key).hexdigest
|
317
|
-
unless shared_key_hexdigest == clientside
|
318
|
-
return false, 'shared key mismatch'
|
319
|
-
end
|
320
|
-
|
321
|
-
return true, nil
|
322
|
-
end
|
323
|
-
|
324
|
-
def send_data(data)
|
325
|
-
@sslsession.write data.to_msgpack
|
326
|
-
end
|
327
|
-
|
328
|
-
def on_read(data)
|
329
|
-
$log.debug "on_read"
|
330
|
-
if self.established?
|
331
|
-
#TODO: ACK
|
332
|
-
$log.warn "unknown packets arrived..."
|
333
|
-
return
|
334
|
-
end
|
335
|
-
|
336
|
-
case @state
|
337
|
-
when :helo
|
338
|
-
# TODO: log debug
|
339
|
-
unless check_helo(data)
|
340
|
-
$log.warn "received invalid helo message from #{@host}"
|
341
|
-
self.shutdown
|
342
|
-
return
|
343
|
-
end
|
344
|
-
send_data generate_ping()
|
345
|
-
@state = :pingpong
|
346
|
-
when :pingpong
|
347
|
-
success, reason = check_pong(data)
|
348
|
-
unless success
|
349
|
-
$log.warn "connection refused to #{@host}:" + reason
|
350
|
-
self.shutdown
|
351
|
-
return
|
352
|
-
end
|
353
|
-
$log.info "connection established to #{@host}"
|
354
|
-
@state = :established
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
def connect
|
359
|
-
$log.debug "starting client"
|
360
|
-
|
361
|
-
addr = @sender.hostname_resolver.getaddress(@host)
|
362
|
-
$log.debug "create tcp socket to node", :host => @host, :address => addr, :port => @port
|
363
|
-
sock = TCPSocket.new(addr, @port)
|
364
|
-
|
365
|
-
$log.trace "changing socket options"
|
366
|
-
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
367
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
368
|
-
|
369
|
-
opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
370
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
371
|
-
|
372
|
-
# TODO: SSLContext constructer parameter (SSL/TLS protocol version)
|
373
|
-
$log.trace "initializing SSL contexts"
|
374
|
-
context = OpenSSL::SSL::SSLContext.new
|
375
|
-
# TODO: context.ca_file = (ca_file_path)
|
376
|
-
# TODO: context.ciphers = (SSL Shared key chiper protocols)
|
377
|
-
|
378
|
-
$log.debug "trying to connect ssl session", :host => @host, :ipaddr => addr, :port => @port
|
379
|
-
sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
|
380
|
-
# TODO: check connection failure
|
381
|
-
sslsession.connect
|
382
|
-
$log.debug "ssl session connected", :host => @host, :port => @port
|
383
|
-
|
384
|
-
begin
|
385
|
-
unless @sender.allow_self_signed_certificate
|
386
|
-
$log.debug "checking peer's certificate", :subject => sslsession.peer_cert.subject
|
387
|
-
sslsession.post_connection_check(@hostlabel)
|
388
|
-
verify = sslsession.verify_result
|
389
|
-
if verify != OpenSSL::X509::V_OK
|
390
|
-
err_name = verify_result_name(verify)
|
391
|
-
$log.warn "failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
|
392
|
-
$log.warn "verify_result: #{err_name}"
|
393
|
-
raise RuntimeError, "failed to verify certification while connecting host #{@host} as #{@hostlabel}"
|
394
|
-
end
|
395
|
-
end
|
396
|
-
rescue OpenSSL::SSL::SSLError => e
|
397
|
-
$log.warn "failed to verify certification while connecting ssl session", :host => @host, :hostlabel => @hostlabel
|
398
|
-
self.shutdown
|
399
|
-
raise
|
400
|
-
end
|
401
|
-
|
402
|
-
$log.debug "ssl sessison connected", :host => @host, :port => @port
|
403
|
-
@socket = sock
|
404
|
-
@sslsession = sslsession
|
405
|
-
|
406
|
-
buf = ''
|
407
|
-
read_length = @sender.read_length
|
408
|
-
read_interval = @sender.read_interval
|
409
|
-
socket_interval = @sender.socket_interval
|
410
|
-
|
411
|
-
loop do
|
412
|
-
begin
|
413
|
-
while @sslsession.read_nonblock(read_length, buf)
|
414
|
-
if buf == ''
|
415
|
-
sleep read_interval
|
416
|
-
next
|
417
|
-
end
|
418
|
-
@unpacker.feed_each(buf, &method(:on_read))
|
419
|
-
buf = ''
|
420
|
-
end
|
421
|
-
rescue OpenSSL::SSL::SSLError
|
422
|
-
# to wait i/o restart
|
423
|
-
sleep socket_interval
|
424
|
-
rescue EOFError
|
425
|
-
$log.warn "disconnected from #{@host}"
|
426
|
-
break
|
427
|
-
end
|
428
|
-
end
|
429
|
-
self.shutdown
|
430
|
-
end
|
431
|
-
end
|
432
190
|
end
|
433
191
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# require 'msgpack'
|
2
|
+
# require 'socket'
|
3
|
+
# require 'openssl'
|
4
|
+
# require 'digest'
|
5
|
+
# require 'resolve/hostname'
|
6
|
+
|
7
|
+
require_relative 'openssl_util'
|
8
|
+
|
9
|
+
class Fluent::SecureForwardOutput::Node
|
10
|
+
attr_accessor :host, :port, :hostlabel, :shared_key, :username, :password
|
11
|
+
attr_accessor :authentication, :keepalive
|
12
|
+
attr_accessor :socket, :sslsession, :unpacker, :shared_key_salt, :state
|
13
|
+
|
14
|
+
def initialize(sender, shared_key, conf)
|
15
|
+
@sender = sender
|
16
|
+
@shared_key = shared_key
|
17
|
+
|
18
|
+
@host = conf['host']
|
19
|
+
@port = (conf['port'] || Fluent::SecureForwardOutput::DEFAULT_SECURE_CONNECT_PORT).to_i
|
20
|
+
@hostlabel = conf['hostlabel'] || conf['host']
|
21
|
+
@username = conf['username'] || ''
|
22
|
+
@password = conf['password'] || ''
|
23
|
+
|
24
|
+
@authentication = nil
|
25
|
+
@keepalive = nil
|
26
|
+
|
27
|
+
@socket = nil
|
28
|
+
@sslsession = nil
|
29
|
+
@unpacker = MessagePack::Unpacker.new
|
30
|
+
|
31
|
+
@shared_key_salt = generate_salt
|
32
|
+
@state = :helo
|
33
|
+
@thread = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def dup
|
37
|
+
self.class.new(
|
38
|
+
@sender,
|
39
|
+
@shared_key,
|
40
|
+
{'host' => @host, 'port' => @port, 'hostlabel' => @hostlabel, 'username' => @username, 'password' => @password}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def start
|
45
|
+
@thread = Thread.new(&method(:connect))
|
46
|
+
end
|
47
|
+
|
48
|
+
def shutdown
|
49
|
+
$log.debug "shutting down node #{@host}"
|
50
|
+
@state = :closed
|
51
|
+
|
52
|
+
if @thread == Thread.current
|
53
|
+
@sslsession.close if @sslsession
|
54
|
+
@socket.close if @socket
|
55
|
+
@thread.kill
|
56
|
+
else
|
57
|
+
if @thread
|
58
|
+
@thread.kill
|
59
|
+
@thread.join
|
60
|
+
end
|
61
|
+
@sslsession.close if @sslsession
|
62
|
+
@socket.close if @socket
|
63
|
+
end
|
64
|
+
rescue => e
|
65
|
+
$log.debug "error on node shutdown #{e.class}:#{e.message}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def established?
|
69
|
+
@state == :established
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate_salt
|
73
|
+
OpenSSL::Random.random_bytes(16)
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_helo(message)
|
77
|
+
$log.debug "checking helo"
|
78
|
+
# ['HELO', options(hash)]
|
79
|
+
unless message.size == 2 && message[0] == 'HELO'
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
opts = message[1]
|
83
|
+
@authentication = opts['auth']
|
84
|
+
@keepalive = opts['keepalive']
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def generate_ping
|
89
|
+
$log.debug "generating ping"
|
90
|
+
# ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + shared_key),
|
91
|
+
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
92
|
+
shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt).update(@sender.self_hostname).update(@shared_key).hexdigest
|
93
|
+
ping = ['PING', @sender.self_hostname, @shared_key_salt, shared_key_hexdigest]
|
94
|
+
if @authentication != ''
|
95
|
+
password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
|
96
|
+
ping.push(@username, password_hexdigest)
|
97
|
+
else
|
98
|
+
ping.push('','')
|
99
|
+
end
|
100
|
+
ping
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_pong(message)
|
104
|
+
$log.debug "checking pong"
|
105
|
+
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
106
|
+
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
107
|
+
unless message.size == 5 && message[0] == 'PONG'
|
108
|
+
return false, 'invalid format for PONG message'
|
109
|
+
end
|
110
|
+
pong, auth_result, reason, hostname, shared_key_hexdigest = message
|
111
|
+
|
112
|
+
unless auth_result
|
113
|
+
return false, 'authentication failed: ' + reason
|
114
|
+
end
|
115
|
+
|
116
|
+
clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key).hexdigest
|
117
|
+
unless shared_key_hexdigest == clientside
|
118
|
+
return false, 'shared key mismatch'
|
119
|
+
end
|
120
|
+
|
121
|
+
return true, nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def send_data(data)
|
125
|
+
@sslsession.write data.to_msgpack
|
126
|
+
end
|
127
|
+
|
128
|
+
def on_read(data)
|
129
|
+
$log.debug "on_read"
|
130
|
+
if self.established?
|
131
|
+
#TODO: ACK
|
132
|
+
$log.warn "unknown packets arrived..."
|
133
|
+
return
|
134
|
+
end
|
135
|
+
|
136
|
+
case @state
|
137
|
+
when :helo
|
138
|
+
# TODO: log debug
|
139
|
+
unless check_helo(data)
|
140
|
+
$log.warn "received invalid helo message from #{@host}"
|
141
|
+
self.shutdown
|
142
|
+
return
|
143
|
+
end
|
144
|
+
send_data generate_ping()
|
145
|
+
@state = :pingpong
|
146
|
+
when :pingpong
|
147
|
+
success, reason = check_pong(data)
|
148
|
+
unless success
|
149
|
+
$log.warn "connection refused to #{@host}:" + reason
|
150
|
+
self.shutdown
|
151
|
+
return
|
152
|
+
end
|
153
|
+
$log.info "connection established to #{@host}"
|
154
|
+
@state = :established
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def connect
|
159
|
+
$log.debug "starting client"
|
160
|
+
|
161
|
+
addr = @sender.hostname_resolver.getaddress(@host)
|
162
|
+
$log.debug "create tcp socket to node", :host => @host, :address => addr, :port => @port
|
163
|
+
sock = TCPSocket.new(addr, @port)
|
164
|
+
|
165
|
+
$log.trace "changing socket options"
|
166
|
+
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
167
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
168
|
+
|
169
|
+
opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
170
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
171
|
+
|
172
|
+
# TODO: SSLContext constructer parameter (SSL/TLS protocol version)
|
173
|
+
$log.trace "initializing SSL contexts"
|
174
|
+
context = OpenSSL::SSL::SSLContext.new
|
175
|
+
# TODO: context.ca_file = (ca_file_path)
|
176
|
+
# TODO: context.ciphers = (SSL Shared key chiper protocols)
|
177
|
+
|
178
|
+
$log.debug "trying to connect ssl session", :host => @host, :ipaddr => addr, :port => @port
|
179
|
+
sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
|
180
|
+
# TODO: check connection failure
|
181
|
+
sslsession.connect
|
182
|
+
$log.debug "ssl session connected", :host => @host, :port => @port
|
183
|
+
|
184
|
+
begin
|
185
|
+
unless @sender.allow_self_signed_certificate
|
186
|
+
$log.debug "checking peer's certificate", :subject => sslsession.peer_cert.subject
|
187
|
+
sslsession.post_connection_check(@hostlabel)
|
188
|
+
verify = sslsession.verify_result
|
189
|
+
if verify != OpenSSL::X509::V_OK
|
190
|
+
err_name = Fluent::SecureForwardOutput::OpenSSLUtil.verify_result_name(verify)
|
191
|
+
$log.warn "failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
|
192
|
+
$log.warn "verify_result: #{err_name}"
|
193
|
+
raise RuntimeError, "failed to verify certification while connecting host #{@host} as #{@hostlabel}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
rescue OpenSSL::SSL::SSLError => e
|
197
|
+
$log.warn "failed to verify certification while connecting ssl session", :host => @host, :hostlabel => @hostlabel
|
198
|
+
self.shutdown
|
199
|
+
raise
|
200
|
+
end
|
201
|
+
|
202
|
+
$log.debug "ssl sessison connected", :host => @host, :port => @port
|
203
|
+
@socket = sock
|
204
|
+
@sslsession = sslsession
|
205
|
+
|
206
|
+
buf = ''
|
207
|
+
read_length = @sender.read_length
|
208
|
+
read_interval = @sender.read_interval
|
209
|
+
socket_interval = @sender.socket_interval
|
210
|
+
|
211
|
+
loop do
|
212
|
+
begin
|
213
|
+
while @sslsession.read_nonblock(read_length, buf)
|
214
|
+
if buf == ''
|
215
|
+
sleep read_interval
|
216
|
+
next
|
217
|
+
end
|
218
|
+
@unpacker.feed_each(buf, &method(:on_read))
|
219
|
+
buf = ''
|
220
|
+
end
|
221
|
+
rescue OpenSSL::SSL::SSLError
|
222
|
+
# to wait i/o restart
|
223
|
+
sleep socket_interval
|
224
|
+
rescue EOFError
|
225
|
+
$log.warn "disconnected from #{@host}"
|
226
|
+
break
|
227
|
+
end
|
228
|
+
end
|
229
|
+
self.shutdown
|
230
|
+
end
|
231
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
|
+
require 'fluent/test'
|
15
|
+
unless ENV.has_key?('VERBOSE')
|
16
|
+
nulllogger = Object.new
|
17
|
+
nulllogger.instance_eval {|obj|
|
18
|
+
def method_missing(method, *args)
|
19
|
+
# pass
|
20
|
+
end
|
21
|
+
}
|
22
|
+
$log = nulllogger
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'fluent/plugin/in_secure_forward'
|
26
|
+
require 'fluent/plugin/out_secure_forward'
|
27
|
+
|
28
|
+
class Test::Unit::TestCase
|
29
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-secure-forward
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TAGOMORI Satoshi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: This version is HIGHLY EXPERIMENTAL.
|
56
70
|
email:
|
57
71
|
- tagomoris@gmail.com
|
@@ -74,7 +88,11 @@ files:
|
|
74
88
|
- example/server.conf
|
75
89
|
- fluent-plugin-secure-forward.gemspec
|
76
90
|
- lib/fluent/plugin/in_secure_forward.rb
|
91
|
+
- lib/fluent/plugin/input_session.rb
|
92
|
+
- lib/fluent/plugin/openssl_util.rb
|
77
93
|
- lib/fluent/plugin/out_secure_forward.rb
|
94
|
+
- lib/fluent/plugin/output_node.rb
|
95
|
+
- test/helper.rb
|
78
96
|
- test/plugin/test_in_secure_forward.rb
|
79
97
|
- test/plugin/test_out_secure_forward.rb
|
80
98
|
homepage: https://github.com/tagomoris/fluent-plugin-secure-forward
|
@@ -101,6 +119,7 @@ signing_key:
|
|
101
119
|
specification_version: 4
|
102
120
|
summary: Fluentd input/output plugin to forward over SSL with authentications
|
103
121
|
test_files:
|
122
|
+
- test/helper.rb
|
104
123
|
- test/plugin/test_in_secure_forward.rb
|
105
124
|
- test/plugin/test_out_secure_forward.rb
|
106
125
|
has_rdoc:
|