fluent-plugin-secure-forward-addproxy 0.3.3dev2
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +444 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/secure-forward-ca-generate +34 -0
- data/bin/setup +7 -0
- data/example/auth_client.conf +19 -0
- data/example/auth_server.conf +30 -0
- data/example/cert_client.conf +21 -0
- data/example/cert_server.conf +35 -0
- data/example/certs/cert.pem +18 -0
- data/example/certs/key.pem +15 -0
- data/example/client.conf +24 -0
- data/example/client_proxy.conf +26 -0
- data/example/insecure_client.conf +23 -0
- data/example/insecure_server.conf +10 -0
- data/example/server.conf +13 -0
- data/fluent-plugin-secure-forward-addproxy.gemspec +23 -0
- data/lib/fluent/plugin/in_secure_forward.rb +278 -0
- data/lib/fluent/plugin/input_session.rb +219 -0
- data/lib/fluent/plugin/openssl_util.rb +38 -0
- data/lib/fluent/plugin/out_secure_forward.rb +280 -0
- data/lib/fluent/plugin/output_node.rb +348 -0
- data/lib/fluent/plugin/secure/forward/addproxy/version.rb +11 -0
- data/lib/fluent/plugin/secure/forward/addproxy.rb +13 -0
- data/lib/fluent/plugin/secure/forward/v033dev2/addproxy/version.rb +13 -0
- data/lib/fluent/plugin/secure/forward/v033dev2/addproxy.rb +15 -0
- data/lib/fluent/plugin/secure_forward/cert_util.rb +85 -0
- data/test/helper.rb +66 -0
- data/test/plugin/test_in_secure_forward.rb +237 -0
- data/test/plugin/test_input_session.rb +43 -0
- data/test/plugin/test_out_secure_forward.rb +147 -0
- metadata +169 -0
@@ -0,0 +1,280 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'fluent/mixin/config_placeholders'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
class SecureForwardOutput < ObjectBufferedOutput
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'output_node'
|
11
|
+
|
12
|
+
module Fluent
|
13
|
+
class SecureForwardOutput < ObjectBufferedOutput
|
14
|
+
DEFAULT_SECURE_CONNECT_PORT = 24284
|
15
|
+
|
16
|
+
Fluent::Plugin.register_output('secure_forward', self)
|
17
|
+
|
18
|
+
config_param :secure, :bool
|
19
|
+
|
20
|
+
config_param :self_hostname, :string
|
21
|
+
include Fluent::Mixin::ConfigPlaceholders
|
22
|
+
|
23
|
+
config_param :shared_key, :string
|
24
|
+
|
25
|
+
config_param :keepalive, :time, default: nil # nil/0 means disable keepalive expiration
|
26
|
+
|
27
|
+
config_param :send_timeout, :time, default: 60
|
28
|
+
# config_param :hard_timeout, :time, :default => 60
|
29
|
+
# config_param :expire_dns_cache, :time, :default => 0 # 0 means disable cache
|
30
|
+
|
31
|
+
config_param :ca_cert_path, :string, default: nil
|
32
|
+
|
33
|
+
config_param :enable_strict_verification, :bool, default: nil # FQDN check with hostlabel
|
34
|
+
config_param :ssl_version, :string, default: 'TLSv1_2'
|
35
|
+
config_param :ssl_ciphers, :string, default: nil
|
36
|
+
|
37
|
+
config_param :read_length, :size, default: 512 # 512bytes
|
38
|
+
config_param :read_interval_msec, :integer, default: 50 # 50ms
|
39
|
+
config_param :socket_interval_msec, :integer, default: 200 # 200ms
|
40
|
+
|
41
|
+
config_param :reconnect_interval, :time, default: 5
|
42
|
+
config_param :established_timeout, :time, default: 10
|
43
|
+
|
44
|
+
config_param :proxy_uri, :string, default: nil
|
45
|
+
|
46
|
+
attr_reader :read_interval, :socket_interval
|
47
|
+
|
48
|
+
config_section :server, param_name: :servers do
|
49
|
+
config_param :host, :string
|
50
|
+
config_param :hostlabel, :string, default: nil
|
51
|
+
config_param :port, :integer, default: DEFAULT_SECURE_CONNECT_PORT
|
52
|
+
config_param :shared_key, :string, default: nil
|
53
|
+
config_param :username, :string, default: ''
|
54
|
+
config_param :password, :string, default: ''
|
55
|
+
config_param :standby, :bool, default: false
|
56
|
+
config_param :proxy_uri, :string, default: nil
|
57
|
+
end
|
58
|
+
attr_reader :nodes
|
59
|
+
|
60
|
+
attr_reader :hostname_resolver
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
super
|
64
|
+
require 'socket'
|
65
|
+
require 'openssl'
|
66
|
+
require 'digest'
|
67
|
+
require 'resolve/hostname'
|
68
|
+
require 'securerandom'
|
69
|
+
end
|
70
|
+
|
71
|
+
# Define `log` method for v0.10.42 or earlier
|
72
|
+
unless method_defined?(:log)
|
73
|
+
define_method("log") { $log }
|
74
|
+
end
|
75
|
+
|
76
|
+
def configure(conf)
|
77
|
+
super
|
78
|
+
|
79
|
+
if @secure
|
80
|
+
if @ca_cert_path
|
81
|
+
raise Fluent::ConfigError, "CA cert file not found nor readable at '#{@ca_cert_path}'" unless File.readable?(@ca_cert_path)
|
82
|
+
begin
|
83
|
+
OpenSSL::X509::Certificate.new File.read(@ca_cert_path)
|
84
|
+
rescue OpenSSL::X509::CertificateError => e
|
85
|
+
raise Fluent::ConfigError, "failed to load CA cert file"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise Fluent::ConfigError, "FQDN verification required for certificates issued from public CA" unless @enable_strict_verification
|
89
|
+
log.info "secure connection with valid certificates issued from public CA"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks."
|
93
|
+
end
|
94
|
+
|
95
|
+
@read_interval = @read_interval_msec / 1000.0
|
96
|
+
@socket_interval = @socket_interval_msec / 1000.0
|
97
|
+
|
98
|
+
@nodes = []
|
99
|
+
@servers.each do |server|
|
100
|
+
node = Node.new(self, server)
|
101
|
+
node.first_session = true
|
102
|
+
@nodes.push node
|
103
|
+
end
|
104
|
+
|
105
|
+
if @num_threads > @nodes.select{|n| not n.standby}.size
|
106
|
+
log.warn "Too many num_threads for secure-forward: threads should be smaller or equal to non standby servers"
|
107
|
+
end
|
108
|
+
|
109
|
+
@next_node = 0
|
110
|
+
@mutex = Mutex.new
|
111
|
+
|
112
|
+
@hostname_resolver = Resolve::Hostname.new(system_resolver: true)
|
113
|
+
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
def select_node(permit_standby=false)
|
118
|
+
tries = 0
|
119
|
+
nodes = @nodes.size
|
120
|
+
@mutex.synchronize {
|
121
|
+
n = nil
|
122
|
+
while tries <= nodes
|
123
|
+
n = @nodes[@next_node]
|
124
|
+
@next_node += 1
|
125
|
+
@next_node = 0 if @next_node >= nodes
|
126
|
+
|
127
|
+
if n && n.established? && (! n.tained?) && (! n.detached?) && (!n.standby || permit_standby)
|
128
|
+
n.tain!
|
129
|
+
return n
|
130
|
+
end
|
131
|
+
|
132
|
+
tries += 1
|
133
|
+
end
|
134
|
+
nil
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def start
|
139
|
+
super
|
140
|
+
|
141
|
+
log.debug "starting secure-forward"
|
142
|
+
OpenSSL::Random.seed(SecureRandom.random_bytes(16))
|
143
|
+
log.debug "start to connect target nodes"
|
144
|
+
@nodes.each do |node|
|
145
|
+
log.debug "connecting node", host: node.host, port: node.port
|
146
|
+
node.start
|
147
|
+
end
|
148
|
+
@nodewatcher = Thread.new(&method(:node_watcher))
|
149
|
+
@nodewatcher.abort_on_exception = true
|
150
|
+
end
|
151
|
+
|
152
|
+
def node_watcher
|
153
|
+
reconnectings = Array.new(@nodes.size)
|
154
|
+
nodes_size = @nodes.size
|
155
|
+
|
156
|
+
loop do
|
157
|
+
sleep @reconnect_interval
|
158
|
+
|
159
|
+
log.trace "in node health watcher"
|
160
|
+
|
161
|
+
(0...nodes_size).each do |i|
|
162
|
+
log.trace "node health watcher for #{@nodes[i].host}"
|
163
|
+
|
164
|
+
next if @nodes[i].established? && ! @nodes[i].expired? && ! @nodes[i].detached?
|
165
|
+
|
166
|
+
next if reconnectings[i]
|
167
|
+
|
168
|
+
reason = :expired
|
169
|
+
|
170
|
+
unless @nodes[i].established?
|
171
|
+
log.warn "dead connection found: #{@nodes[i].host}, reconnecting..."
|
172
|
+
reason = :dead
|
173
|
+
end
|
174
|
+
|
175
|
+
node = @nodes[i]
|
176
|
+
log.debug "reconnecting to node", host: node.host, port: node.port, expire: node.expire, expired: node.expired?, detached: node.detached?
|
177
|
+
|
178
|
+
renewed = node.dup
|
179
|
+
renewed.start
|
180
|
+
|
181
|
+
Thread.pass # to connection thread
|
182
|
+
reconnectings[i] = { conn: renewed, at: Time.now, reason: reason }
|
183
|
+
end
|
184
|
+
|
185
|
+
(0...nodes_size).each do |i|
|
186
|
+
next unless reconnectings[i]
|
187
|
+
|
188
|
+
log.trace "checking reconnecting node #{reconnectings[i][:conn].host}"
|
189
|
+
|
190
|
+
if reconnectings[i][:conn].established?
|
191
|
+
log.debug "connection established for reconnecting node"
|
192
|
+
|
193
|
+
oldconn = @nodes[i]
|
194
|
+
@nodes[i] = reconnectings[i][:conn]
|
195
|
+
|
196
|
+
if reconnectings[i][:reason] == :dead
|
197
|
+
log.warn "recovered connection to dead node: #{nodes[i].host}"
|
198
|
+
end
|
199
|
+
|
200
|
+
log.trace "old connection shutting down"
|
201
|
+
oldconn.detach! if oldconn # connection object doesn't raise any exceptions
|
202
|
+
log.trace "old connection shutted down"
|
203
|
+
|
204
|
+
reconnectings[i] = nil
|
205
|
+
next
|
206
|
+
end
|
207
|
+
|
208
|
+
# not connected yet
|
209
|
+
|
210
|
+
next if reconnectings[i][:at] + @established_timeout > Time.now
|
211
|
+
|
212
|
+
# not connected yet, and timeout
|
213
|
+
timeout_conn = reconnectings[i][:conn]
|
214
|
+
log.debug "SSL connection is not established until timemout", host: timeout_conn.host, port: timeout_conn.port, timeout: @established_timeout
|
215
|
+
reconnectings[i] = nil
|
216
|
+
timeout_conn.detach! if timeout_conn # connection object doesn't raise any exceptions
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def shutdown
|
222
|
+
super
|
223
|
+
|
224
|
+
@nodewatcher.kill
|
225
|
+
@nodewatcher.join
|
226
|
+
|
227
|
+
@nodes.each do |node|
|
228
|
+
node.detach!
|
229
|
+
node.join
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def write_objects(tag, es)
|
234
|
+
node = select_node || select_node(true)
|
235
|
+
unless node
|
236
|
+
raise "no one nodes with valid ssl session"
|
237
|
+
end
|
238
|
+
log.trace "selected node", host: node.host, port: node.port, standby: node.standby
|
239
|
+
|
240
|
+
begin
|
241
|
+
send_data(node, tag, es)
|
242
|
+
node.release!
|
243
|
+
rescue Errno::EPIPE, IOError, OpenSSL::SSL::SSLError => e
|
244
|
+
log.warn "Failed to send messages to #{node.host}, parging.", error_class: e.class, error: e
|
245
|
+
node.release!
|
246
|
+
node.detach!
|
247
|
+
|
248
|
+
raise # to retry #write_objects
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# MessagePack FixArray length = 2
|
253
|
+
FORWARD_HEADER = [0x92].pack('C')
|
254
|
+
|
255
|
+
# to forward messages
|
256
|
+
def send_data(node, tag, es)
|
257
|
+
ssl = node.sslsession
|
258
|
+
# beginArray(2)
|
259
|
+
ssl.write FORWARD_HEADER
|
260
|
+
|
261
|
+
# writeRaw(tag)
|
262
|
+
ssl.write tag.to_msgpack
|
263
|
+
|
264
|
+
# beginRaw(size)
|
265
|
+
sz = es.size
|
266
|
+
# # FixRaw
|
267
|
+
# ssl.write [0xa0 | sz].pack('C')
|
268
|
+
#elsif sz < 65536
|
269
|
+
# # raw 16
|
270
|
+
# ssl.write [0xda, sz].pack('Cn')
|
271
|
+
#else
|
272
|
+
# raw 32
|
273
|
+
ssl.write [0xdb, sz].pack('CN')
|
274
|
+
#end
|
275
|
+
|
276
|
+
# writeRawBody(packed_es)
|
277
|
+
es.write_to(ssl)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,348 @@
|
|
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, :standby
|
11
|
+
|
12
|
+
attr_accessor :authentication, :keepalive
|
13
|
+
attr_accessor :socket, :sslsession, :unpacker, :shared_key_salt, :state
|
14
|
+
|
15
|
+
attr_accessor :first_session, :detach
|
16
|
+
|
17
|
+
attr_reader :expire
|
18
|
+
|
19
|
+
def initialize(sender, conf)
|
20
|
+
@sender = sender
|
21
|
+
@shared_key = conf.shared_key || sender.shared_key
|
22
|
+
|
23
|
+
@host = conf.host
|
24
|
+
@port = conf.port
|
25
|
+
@hostlabel = conf.hostlabel || conf.host
|
26
|
+
@username = conf.username
|
27
|
+
@password = conf.password
|
28
|
+
@standby = conf.standby
|
29
|
+
|
30
|
+
@proxy_uri = conf.proxy_uri
|
31
|
+
|
32
|
+
@keepalive = sender.keepalive
|
33
|
+
|
34
|
+
@authentication = nil
|
35
|
+
|
36
|
+
@writing = false
|
37
|
+
|
38
|
+
@expire = nil
|
39
|
+
@first_session = false
|
40
|
+
@detach = false
|
41
|
+
|
42
|
+
@socket = nil
|
43
|
+
@sslsession = nil
|
44
|
+
@unpacker = MessagePack::Unpacker.new
|
45
|
+
|
46
|
+
@shared_key_salt = generate_salt
|
47
|
+
@state = :helo
|
48
|
+
@thread = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def log
|
52
|
+
@sender.log
|
53
|
+
end
|
54
|
+
|
55
|
+
def dup
|
56
|
+
renewed = self.class.new(
|
57
|
+
@sender,
|
58
|
+
Fluent::Config::Section.new({host: @host, port: @port, hostlabel: @hostlabel, username: @username, password: @password, shared_key: @shared_key, standby: @standby, proxy_uri: @proxy_uri})
|
59
|
+
)
|
60
|
+
renewed
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
@thread = Thread.new(&method(:connect))
|
65
|
+
end
|
66
|
+
|
67
|
+
def detach!
|
68
|
+
@detach = true
|
69
|
+
end
|
70
|
+
|
71
|
+
def detached?
|
72
|
+
@detach
|
73
|
+
end
|
74
|
+
|
75
|
+
def tain!
|
76
|
+
raise RuntimeError, "BUG: taining detached node" if @detach
|
77
|
+
@writing = true
|
78
|
+
end
|
79
|
+
|
80
|
+
def tained?
|
81
|
+
@writing
|
82
|
+
end
|
83
|
+
|
84
|
+
def release!
|
85
|
+
@writing = false
|
86
|
+
end
|
87
|
+
|
88
|
+
def shutdown
|
89
|
+
log.debug "shutting down node #{@host}"
|
90
|
+
@state = :closed
|
91
|
+
|
92
|
+
if @thread == Thread.current
|
93
|
+
@sslsession.close if @sslsession
|
94
|
+
@socket.close if @socket
|
95
|
+
@thread.kill
|
96
|
+
else
|
97
|
+
if @thread
|
98
|
+
@thread.kill
|
99
|
+
@thread.join
|
100
|
+
end
|
101
|
+
@sslsession.close if @sslsession
|
102
|
+
@socket.close if @socket
|
103
|
+
end
|
104
|
+
rescue => e
|
105
|
+
log.debug "error on node shutdown #{e.class}:#{e.message}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def join
|
109
|
+
@thread && @thread.join
|
110
|
+
end
|
111
|
+
|
112
|
+
def established?
|
113
|
+
@state == :established
|
114
|
+
end
|
115
|
+
|
116
|
+
def expired?
|
117
|
+
if @keepalive.nil? || @keepalive == 0
|
118
|
+
false
|
119
|
+
else
|
120
|
+
@expire && @expire < Time.now
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def generate_salt
|
125
|
+
OpenSSL::Random.random_bytes(16)
|
126
|
+
end
|
127
|
+
|
128
|
+
def check_helo(message)
|
129
|
+
log.debug "checking helo"
|
130
|
+
# ['HELO', options(hash)]
|
131
|
+
unless message.size == 2 && message[0] == 'HELO'
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
opts = message[1]
|
135
|
+
@shared_key_nonce = opts['nonce'] || '' # make shared_key_check failed (instead of error) if protocol version mismatch exist
|
136
|
+
@authentication = opts['auth']
|
137
|
+
@allow_keepalive = opts['keepalive']
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate_ping
|
142
|
+
log.debug "generating ping"
|
143
|
+
# ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + nonce + shared_key),
|
144
|
+
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
145
|
+
shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt).update(@sender.self_hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
|
146
|
+
ping = ['PING', @sender.self_hostname, @shared_key_salt, shared_key_hexdigest]
|
147
|
+
if @authentication != ''
|
148
|
+
password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
|
149
|
+
ping.push(@username, password_hexdigest)
|
150
|
+
else
|
151
|
+
ping.push('','')
|
152
|
+
end
|
153
|
+
ping
|
154
|
+
end
|
155
|
+
|
156
|
+
def check_pong(message)
|
157
|
+
log.debug "checking pong"
|
158
|
+
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
159
|
+
# self_hostname, sha512\_hex(salt + self_hostname + nonce + sharedkey)]
|
160
|
+
unless message.size == 5 && message[0] == 'PONG'
|
161
|
+
return false, 'invalid format for PONG message'
|
162
|
+
end
|
163
|
+
pong, auth_result, reason, hostname, shared_key_hexdigest = message
|
164
|
+
|
165
|
+
unless auth_result
|
166
|
+
return false, 'authentication failed: ' + reason
|
167
|
+
end
|
168
|
+
|
169
|
+
if hostname == @sender.self_hostname
|
170
|
+
return false, 'same hostname between input and output: invalid configuration'
|
171
|
+
end
|
172
|
+
|
173
|
+
clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
|
174
|
+
unless shared_key_hexdigest == clientside
|
175
|
+
return false, 'shared key mismatch'
|
176
|
+
end
|
177
|
+
|
178
|
+
return true, nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def send_data(data)
|
182
|
+
@sslsession.write data.to_msgpack
|
183
|
+
end
|
184
|
+
|
185
|
+
def on_read(data)
|
186
|
+
log.debug "on_read"
|
187
|
+
if self.established?
|
188
|
+
#TODO: ACK
|
189
|
+
log.warn "unknown packets arrived..."
|
190
|
+
return
|
191
|
+
end
|
192
|
+
|
193
|
+
case @state
|
194
|
+
when :helo
|
195
|
+
unless check_helo(data)
|
196
|
+
log.warn "received invalid helo message from #{@host}"
|
197
|
+
self.shutdown
|
198
|
+
return
|
199
|
+
end
|
200
|
+
send_data generate_ping()
|
201
|
+
@state = :pingpong
|
202
|
+
when :pingpong
|
203
|
+
success, reason = check_pong(data)
|
204
|
+
unless success
|
205
|
+
log.warn "connection refused to #{@host}:" + reason
|
206
|
+
self.shutdown
|
207
|
+
return
|
208
|
+
end
|
209
|
+
log.info "connection established to #{@host}" if @first_session
|
210
|
+
@state = :established
|
211
|
+
@expire = Time.now + @keepalive if @keepalive && @keepalive > 0
|
212
|
+
log.debug "connection established", host: @host, port: @port, expire: @expire
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def connect
|
217
|
+
Thread.current.abort_on_exception = true
|
218
|
+
log.debug "starting client"
|
219
|
+
|
220
|
+
addr = @sender.hostname_resolver.getaddress(@host)
|
221
|
+
log.debug "create tcp socket to node", host: @host, address: addr, port: @port
|
222
|
+
|
223
|
+
begin
|
224
|
+
if @proxy_uri.nil? then
|
225
|
+
sock = TCPSocket.new(addr, @port)
|
226
|
+
else
|
227
|
+
proxy = Proxifier::Proxy(@proxy_uri)
|
228
|
+
sock = proxy.open(addr, @port)
|
229
|
+
end
|
230
|
+
rescue => e
|
231
|
+
log.warn "failed to connect for secure-forward", error_class: e.class, error: e, host: @host, address: addr, port: @port
|
232
|
+
@state = :failed
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
236
|
+
log.trace "changing socket options"
|
237
|
+
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
238
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
239
|
+
|
240
|
+
opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
241
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
242
|
+
|
243
|
+
log.trace "initializing SSL contexts"
|
244
|
+
|
245
|
+
context = OpenSSL::SSL::SSLContext.new(@sender.ssl_version)
|
246
|
+
|
247
|
+
log.trace "setting SSL verification options"
|
248
|
+
|
249
|
+
if @sender.secure
|
250
|
+
# inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
251
|
+
# https://bugs.ruby-lang.org/issues/9424
|
252
|
+
context.set_params({})
|
253
|
+
|
254
|
+
if @sender.ssl_ciphers
|
255
|
+
context.ciphers = @sender.ssl_ciphers
|
256
|
+
else
|
257
|
+
### follow httpclient configuration by nahi
|
258
|
+
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
|
259
|
+
context.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
|
260
|
+
end
|
261
|
+
|
262
|
+
log.trace "set verify_mode VERIFY_PEER"
|
263
|
+
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
264
|
+
if @sender.enable_strict_verification
|
265
|
+
context.cert_store = OpenSSL::X509::Store.new
|
266
|
+
begin
|
267
|
+
context.cert_store.set_default_paths
|
268
|
+
rescue OpenSSL::X509::StoreError => e
|
269
|
+
log.warn "faild to load system default certificates", error: e
|
270
|
+
end
|
271
|
+
end
|
272
|
+
if @sender.ca_cert_path
|
273
|
+
log.trace "set to use private CA", path: @sender.ca_cert_path
|
274
|
+
context.ca_file = @sender.ca_cert_path
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
log.debug "trying to connect ssl session", host: @host, address: addr, port: @port
|
279
|
+
begin
|
280
|
+
sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
|
281
|
+
log.trace "connecting...", host: @host, address: addr, port: @port
|
282
|
+
sslsession.connect
|
283
|
+
rescue => e
|
284
|
+
log.warn "failed to establish SSL connection", error_class: e.class, error: e, host: @host, address: addr, port: @port
|
285
|
+
@state = :failed
|
286
|
+
return
|
287
|
+
end
|
288
|
+
|
289
|
+
log.debug "ssl session connected", host: @host, port: @port
|
290
|
+
|
291
|
+
begin
|
292
|
+
if @sender.enable_strict_verification
|
293
|
+
log.debug "checking peer's certificate", subject: sslsession.peer_cert.subject
|
294
|
+
sslsession.post_connection_check(@hostlabel)
|
295
|
+
verify = sslsession.verify_result
|
296
|
+
if verify != OpenSSL::X509::V_OK
|
297
|
+
err_name = Fluent::SecureForwardOutput::OpenSSLUtil.verify_result_name(verify)
|
298
|
+
log.warn "BUG: failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
|
299
|
+
log.warn "BUG: verify_result: #{err_name}"
|
300
|
+
raise RuntimeError, "BUG: failed to verify certification and to handle it correctly while connecting host #{@host} as #{@hostlabel}"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
rescue OpenSSL::SSL::SSLError => e
|
304
|
+
log.warn "failed to verify certification while connecting ssl session", host: @host, hostlabel: @hostlabel
|
305
|
+
self.shutdown
|
306
|
+
raise
|
307
|
+
end
|
308
|
+
|
309
|
+
log.debug "ssl session connected", host: @host, port: @port
|
310
|
+
@socket = sock
|
311
|
+
@sslsession = sslsession
|
312
|
+
|
313
|
+
buf = ''
|
314
|
+
read_length = @sender.read_length
|
315
|
+
read_interval = @sender.read_interval
|
316
|
+
socket_interval = @sender.socket_interval
|
317
|
+
|
318
|
+
loop do
|
319
|
+
break if @detach
|
320
|
+
|
321
|
+
begin
|
322
|
+
while @sslsession.read_nonblock(read_length, buf)
|
323
|
+
if buf == ''
|
324
|
+
sleep read_interval
|
325
|
+
next
|
326
|
+
end
|
327
|
+
@unpacker.feed_each(buf, &method(:on_read))
|
328
|
+
buf = ''
|
329
|
+
end
|
330
|
+
rescue OpenSSL::SSL::SSLError
|
331
|
+
# to wait i/o restart
|
332
|
+
sleep socket_interval
|
333
|
+
rescue SystemCallError => e
|
334
|
+
log.warn "disconnected by Error", error_class: e.class, error: e, host: @host, port: @port
|
335
|
+
break
|
336
|
+
rescue EOFError
|
337
|
+
log.warn "disconnected", host: @host, port: @port
|
338
|
+
break
|
339
|
+
end
|
340
|
+
end
|
341
|
+
while @writing
|
342
|
+
break if @detach
|
343
|
+
|
344
|
+
sleep read_interval
|
345
|
+
end
|
346
|
+
self.shutdown
|
347
|
+
end
|
348
|
+
end
|