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
data/example/client.conf
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<source>
|
2
|
+
type forward
|
3
|
+
</source>
|
4
|
+
|
5
|
+
<match test.**>
|
6
|
+
type secure_forward
|
7
|
+
secure yes
|
8
|
+
self_hostname client
|
9
|
+
shared_key hogeposxxx0
|
10
|
+
keepalive 30
|
11
|
+
ca_cert_path /Users/tagomoris/github/fluent-plugin-secure-forward/test/tmp/cadir/ca_cert.pem
|
12
|
+
enable_strict_verification yes
|
13
|
+
<server>
|
14
|
+
host localhost
|
15
|
+
</server>
|
16
|
+
# <server>
|
17
|
+
# host localhost
|
18
|
+
# standby yes
|
19
|
+
# </server>
|
20
|
+
# <server>
|
21
|
+
# host localhost
|
22
|
+
# </server>
|
23
|
+
flush_interval 1s
|
24
|
+
</match>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<source>
|
2
|
+
type forward
|
3
|
+
</source>
|
4
|
+
|
5
|
+
<match test.**>
|
6
|
+
type secure_forward
|
7
|
+
secure yes
|
8
|
+
self_hostname client
|
9
|
+
shared_key hogeposxxx0
|
10
|
+
keepalive 30
|
11
|
+
ca_cert_path /Users/tagomoris/github/fluent-plugin-secure-forward/test/tmp/cadir/ca_cert.pem
|
12
|
+
enable_strict_verification yes
|
13
|
+
<server>
|
14
|
+
proxy_uri http://foo.foo.local:3128
|
15
|
+
host localhost
|
16
|
+
</server>
|
17
|
+
# <server>
|
18
|
+
# proxy_uri http://bar.bar.local:3128
|
19
|
+
# host localhost
|
20
|
+
# standby yes
|
21
|
+
# </server>
|
22
|
+
# <server>
|
23
|
+
# host localhost
|
24
|
+
# </server>
|
25
|
+
flush_interval 1s
|
26
|
+
</match>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<source>
|
2
|
+
type forward
|
3
|
+
</source>
|
4
|
+
|
5
|
+
<match test.**>
|
6
|
+
type secure_forward
|
7
|
+
secure no
|
8
|
+
self_hostname client
|
9
|
+
shared_key hogeposxxx0
|
10
|
+
keepalive 30
|
11
|
+
enable_strict_verification yes
|
12
|
+
<server>
|
13
|
+
host localhost
|
14
|
+
</server>
|
15
|
+
# <server>
|
16
|
+
# host localhost
|
17
|
+
# standby yes
|
18
|
+
# </server>
|
19
|
+
# <server>
|
20
|
+
# host localhost
|
21
|
+
# </server>
|
22
|
+
flush_interval 1s
|
23
|
+
</match>
|
data/example/server.conf
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
<source>
|
2
|
+
type secure_forward
|
3
|
+
secure yes
|
4
|
+
self_hostname localhost
|
5
|
+
shared_key hogeposxxx0
|
6
|
+
ca_cert_path /Users/tagomoris/github/fluent-plugin-secure-forward/test/tmp/cadir/ca_cert.pem
|
7
|
+
ca_private_key_path /Users/tagomoris/github/fluent-plugin-secure-forward/test/tmp/cadir/ca_key.pem
|
8
|
+
ca_private_key_passphrase testing secret phrase
|
9
|
+
</source>
|
10
|
+
|
11
|
+
<match test.**>
|
12
|
+
type stdout
|
13
|
+
</match>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |gem|
|
3
|
+
gem.name = "fluent-plugin-secure-forward-addproxy"
|
4
|
+
gem.version = "0.3.3dev2"
|
5
|
+
gem.authors = ["TAGOMORI Satoshi"]
|
6
|
+
gem.email = ["tagomoris@gmail.com"]
|
7
|
+
gem.summary = %q{Fluentd input/output plugin to forward over SSL with authentications + proxy}
|
8
|
+
gem.description = %q{fork from fluent-plugin-secure-forward 0.3.3dev2 }
|
9
|
+
gem.homepage = "https://github.com/tagomoris/fluent-plugin-secure-forward"
|
10
|
+
gem.license = "APLv2"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
|
17
|
+
gem.add_runtime_dependency "fluentd", ">= 0.10.46"
|
18
|
+
gem.add_runtime_dependency "fluent-mixin-config-placeholders", ">= 0.3.0"
|
19
|
+
gem.add_runtime_dependency "resolve-hostname"
|
20
|
+
gem.add_runtime_dependency "proxifier"
|
21
|
+
gem.add_development_dependency "test-unit"
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'fluent/mixin/config_placeholders'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
class SecureForwardInput < Input
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'input_session'
|
11
|
+
require_relative './secure_forward/cert_util'
|
12
|
+
|
13
|
+
module Fluent
|
14
|
+
class SecureForwardInput < Input
|
15
|
+
DEFAULT_SECURE_LISTEN_PORT = 24284
|
16
|
+
|
17
|
+
Fluent::Plugin.register_input('secure_forward', self)
|
18
|
+
|
19
|
+
config_param :secure, :bool # if secure, cert_path or ca_cert_path required
|
20
|
+
|
21
|
+
config_param :self_hostname, :string
|
22
|
+
include Fluent::Mixin::ConfigPlaceholders
|
23
|
+
|
24
|
+
config_param :shared_key, :string
|
25
|
+
|
26
|
+
config_param :bind, :string, default: '0.0.0.0'
|
27
|
+
config_param :port, :integer, default: DEFAULT_SECURE_LISTEN_PORT
|
28
|
+
config_param :allow_keepalive, :bool, default: true #TODO: implement
|
29
|
+
|
30
|
+
config_param :allow_anonymous_source, :bool, default: true
|
31
|
+
config_param :authentication, :bool, default: false
|
32
|
+
|
33
|
+
config_param :ssl_version, :string, default: 'TLSv1_2'
|
34
|
+
config_param :ssl_ciphers, :string, default: nil
|
35
|
+
|
36
|
+
# Cert signed by public CA
|
37
|
+
config_param :cert_path, :string, default: nil
|
38
|
+
config_param :private_key_path, :string, default: nil
|
39
|
+
config_param :private_key_passphrase, :string, default: nil
|
40
|
+
|
41
|
+
# Cert automatically generated and signed by private CA
|
42
|
+
config_param :ca_cert_path, :string, default: nil
|
43
|
+
config_param :ca_private_key_path, :string, default: nil
|
44
|
+
config_param :ca_private_key_passphrase, :string, default: nil
|
45
|
+
|
46
|
+
# Otherwise: Cert automatically generated and signed by itself (for without any verification)
|
47
|
+
|
48
|
+
config_param :generate_private_key_length, :integer, default: 2048
|
49
|
+
config_param :generate_cert_country, :string, default: 'US'
|
50
|
+
config_param :generate_cert_state, :string, default: 'CA'
|
51
|
+
config_param :generate_cert_locality, :string, default: 'Mountain View'
|
52
|
+
config_param :generate_cert_common_name, :string, default: nil
|
53
|
+
|
54
|
+
config_param :read_length, :size, default: 8*1024*1024 # 8MB
|
55
|
+
config_param :read_interval_msec, :integer, default: 50 # 50ms
|
56
|
+
config_param :socket_interval_msec, :integer, default: 200 # 200ms
|
57
|
+
|
58
|
+
attr_reader :read_interval, :socket_interval
|
59
|
+
|
60
|
+
config_section :user, param_name: :users do
|
61
|
+
config_param :username, :string
|
62
|
+
config_param :password, :string
|
63
|
+
end
|
64
|
+
|
65
|
+
config_section :client, param_name: :clients do
|
66
|
+
config_param :host, :string, default: nil
|
67
|
+
config_param :network, :string, default: nil
|
68
|
+
config_param :shared_key, :string, default: nil
|
69
|
+
config_param :users, :string, default: nil # comma separated username list
|
70
|
+
end
|
71
|
+
attr_reader :nodes
|
72
|
+
|
73
|
+
attr_reader :sessions # node/socket/thread list which has sslsocket instance keepaliving to client
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
super
|
77
|
+
require 'ipaddr'
|
78
|
+
require 'socket'
|
79
|
+
require 'openssl'
|
80
|
+
require 'digest'
|
81
|
+
require 'securerandom'
|
82
|
+
end
|
83
|
+
|
84
|
+
# Define `log` method for v0.10.42 or earlier
|
85
|
+
unless method_defined?(:log)
|
86
|
+
define_method("log") { $log }
|
87
|
+
end
|
88
|
+
|
89
|
+
def configure(conf)
|
90
|
+
super
|
91
|
+
|
92
|
+
if @secure
|
93
|
+
unless @cert_path || @ca_cert_path
|
94
|
+
raise Fluent::ConfigError, "cert_path or ca_cert_path required for secure communication"
|
95
|
+
end
|
96
|
+
if @cert_path
|
97
|
+
raise Fluent::ConfigError, "private_key_path required" unless @private_key_path
|
98
|
+
raise Fluent::ConfigError, "private_key_passphrase required" unless @private_key_passphrase
|
99
|
+
else # @ca_cert_path
|
100
|
+
raise Fluent::ConfigError, "ca_private_key_path required" unless @ca_private_key_path
|
101
|
+
raise Fluent::ConfigError, "ca_private_key_passphrase required" unless @ca_private_key_passphrase
|
102
|
+
end
|
103
|
+
else
|
104
|
+
log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks for clients (output plugins)."
|
105
|
+
end
|
106
|
+
|
107
|
+
@read_interval = @read_interval_msec / 1000.0
|
108
|
+
@socket_interval = @socket_interval_msec / 1000.0
|
109
|
+
|
110
|
+
@nodes = []
|
111
|
+
|
112
|
+
@clients.each do |client|
|
113
|
+
if client.host && client.network
|
114
|
+
raise Fluent::ConfigError, "both of 'host' and 'network' are specified for client"
|
115
|
+
end
|
116
|
+
if !client.host && !client.network
|
117
|
+
raise Fluent::ConfigError, "Either of 'host' and 'network' must be specified for client"
|
118
|
+
end
|
119
|
+
source = nil
|
120
|
+
if client.host
|
121
|
+
begin
|
122
|
+
source = IPSocket.getaddress(client.host)
|
123
|
+
rescue SocketError => e
|
124
|
+
raise Fluent::ConfigError, "host '#{client.host}' cannot be resolved"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
source_addr = begin
|
128
|
+
IPAddr.new(source || client.network)
|
129
|
+
rescue ArgumentError => e
|
130
|
+
raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
|
131
|
+
end
|
132
|
+
@nodes.push({
|
133
|
+
address: source_addr,
|
134
|
+
shared_key: (client.shared_key || @shared_key),
|
135
|
+
users: (client.users ? client.users.split(',') : nil)
|
136
|
+
})
|
137
|
+
end
|
138
|
+
|
139
|
+
@generate_cert_common_name ||= @self_hostname
|
140
|
+
|
141
|
+
# To check whether certificates are successfully generated/loaded at startup time
|
142
|
+
self.certificate
|
143
|
+
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def start
|
148
|
+
super
|
149
|
+
OpenSSL::Random.seed(SecureRandom.random_bytes(16))
|
150
|
+
@sessions = []
|
151
|
+
@sock = nil
|
152
|
+
@listener = Thread.new(&method(:run))
|
153
|
+
@listener.abort_on_exception
|
154
|
+
end
|
155
|
+
|
156
|
+
def shutdown
|
157
|
+
@listener.kill
|
158
|
+
@listener.join
|
159
|
+
@sessions.each{ |s| s.shutdown }
|
160
|
+
@sock.close
|
161
|
+
end
|
162
|
+
|
163
|
+
def select_authenticate_users(node, username)
|
164
|
+
if node.nil? || node[:users].nil?
|
165
|
+
@users.select{|u| u.username == username}
|
166
|
+
else
|
167
|
+
@users.select{|u| node[:users].include?(u.username) && u.username == username}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def certificate
|
172
|
+
return @cert, @key if @cert && @key
|
173
|
+
|
174
|
+
if @cert_path
|
175
|
+
@key = OpenSSL::PKey::RSA.new(File.read(@private_key_path), @private_key_passphrase)
|
176
|
+
@cert = OpenSSL::X509::Certificate.new(File.read(@cert_path))
|
177
|
+
elsif @ca_cert_path
|
178
|
+
opts = {
|
179
|
+
ca_cert_path: @ca_cert_path,
|
180
|
+
ca_key_path: @ca_private_key_path,
|
181
|
+
ca_key_passphrase: @ca_private_key_passphrase,
|
182
|
+
private_key_length: @generate_private_key_length,
|
183
|
+
country: @generate_cert_country,
|
184
|
+
state: @generate_cert_state,
|
185
|
+
locality: @generate_cert_locality,
|
186
|
+
common_name: @generate_cert_common_name,
|
187
|
+
}
|
188
|
+
@cert, @key = Fluent::SecureForward::CertUtil.generate_server_pair(opts)
|
189
|
+
else
|
190
|
+
opts = {
|
191
|
+
private_key_length: @generate_private_key_length,
|
192
|
+
country: @generate_cert_country,
|
193
|
+
state: @generate_cert_state,
|
194
|
+
locality: @generate_cert_locality,
|
195
|
+
common_name: @generate_cert_common_name,
|
196
|
+
}
|
197
|
+
@cert, @key = Fluent::SecureForward::CertUtil.generate_self_signed_server_pair(opts)
|
198
|
+
end
|
199
|
+
return @cert, @key
|
200
|
+
end
|
201
|
+
|
202
|
+
def run # sslsocket server thread
|
203
|
+
log.trace "setup for ssl sessions"
|
204
|
+
cert, key = self.certificate
|
205
|
+
|
206
|
+
ctx = OpenSSL::SSL::SSLContext.new(@ssl_version)
|
207
|
+
if @secure
|
208
|
+
# inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
209
|
+
# https://bugs.ruby-lang.org/issues/9424
|
210
|
+
ctx.set_params({})
|
211
|
+
|
212
|
+
if @ssl_ciphers
|
213
|
+
ctx.ciphers = @ssl_ciphers
|
214
|
+
else
|
215
|
+
### follow httpclient configuration by nahi
|
216
|
+
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
|
217
|
+
ctx.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
ctx.cert = cert
|
222
|
+
ctx.key = key
|
223
|
+
|
224
|
+
log.trace "start to listen", bind: @bind, port: @port
|
225
|
+
server = TCPServer.new(@bind, @port)
|
226
|
+
log.trace "starting SSL server", bind: @bind, port: @port
|
227
|
+
@sock = OpenSSL::SSL::SSLServer.new(server, ctx)
|
228
|
+
@sock.start_immediately = false
|
229
|
+
begin
|
230
|
+
log.trace "accepting sessions"
|
231
|
+
loop do
|
232
|
+
while socket = @sock.accept
|
233
|
+
log.trace "accept tcp connection (ssl session not established yet)"
|
234
|
+
@sessions.push Session.new(self, socket)
|
235
|
+
|
236
|
+
# cleanup closed session instance
|
237
|
+
@sessions.delete_if(&:closed?)
|
238
|
+
log.trace "session instances:", all: @sessions.size, closed: @sessions.select(&:closed?).size
|
239
|
+
end
|
240
|
+
end
|
241
|
+
rescue OpenSSL::SSL::SSLError => e
|
242
|
+
raise unless e.message.start_with?('SSL_accept SYSCALL') # signal trap on accept
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def on_message(msg)
|
247
|
+
# NOTE: copy&paste from Fluent::ForwardInput#on_message(msg)
|
248
|
+
|
249
|
+
# TODO: format error
|
250
|
+
tag = msg[0].to_s
|
251
|
+
entries = msg[1]
|
252
|
+
|
253
|
+
if entries.class == String
|
254
|
+
# PackedForward
|
255
|
+
es = MessagePackEventStream.new(entries, @cached_unpacker)
|
256
|
+
Fluent::Engine.emit_stream(tag, es)
|
257
|
+
|
258
|
+
elsif entries.class == Array
|
259
|
+
# Forward
|
260
|
+
es = Fluent::MultiEventStream.new
|
261
|
+
entries.each {|e|
|
262
|
+
time = e[0].to_i
|
263
|
+
time = (now ||= Fluent::Engine.now) if time == 0
|
264
|
+
record = e[1]
|
265
|
+
es.add(time, record)
|
266
|
+
}
|
267
|
+
Fluent::Engine.emit_stream(tag, es)
|
268
|
+
|
269
|
+
else
|
270
|
+
# Message
|
271
|
+
time = msg[1]
|
272
|
+
time = Fluent::Engine.now if time == 0
|
273
|
+
record = msg[2]
|
274
|
+
Fluent::Engine.emit(tag, time, record)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,219 @@
|
|
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 log
|
26
|
+
@receiver.log
|
27
|
+
end
|
28
|
+
|
29
|
+
def closed?
|
30
|
+
@state == :closed
|
31
|
+
end
|
32
|
+
|
33
|
+
def established?
|
34
|
+
@state == :established
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_salt
|
38
|
+
OpenSSL::Random.random_bytes(16)
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_node(ipaddress)
|
42
|
+
node = nil
|
43
|
+
@receiver.nodes.each do |n|
|
44
|
+
if n[:address].include?(ipaddress)
|
45
|
+
node = n
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
node
|
50
|
+
end
|
51
|
+
|
52
|
+
## not implemented yet
|
53
|
+
# def check_hostname_reverse_lookup(ipaddress)
|
54
|
+
# rev_name = Resolv.getname(ipaddress)
|
55
|
+
# proto, port, host, ipaddr, family_num, socktype_num, proto_num = Socket.getaddrinfo(rev_name, DUMMY_PORT)
|
56
|
+
# unless ipaddr == ipaddress
|
57
|
+
# return false
|
58
|
+
# end
|
59
|
+
# true
|
60
|
+
# end
|
61
|
+
|
62
|
+
def generate_helo
|
63
|
+
log.debug "generating helo"
|
64
|
+
# ['HELO', options(hash)]
|
65
|
+
[ 'HELO', {'nonce' => @shared_key_nonce, 'auth' => (@receiver.authentication ? @auth_key_salt : ''), 'keepalive' => @receiver.allow_keepalive } ]
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_ping(message)
|
69
|
+
log.debug "checking ping"
|
70
|
+
# ['PING', self_hostname, shared_key\_salt, sha512\_hex(shared_key\_salt + self_hostname + nonce + shared_key),
|
71
|
+
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
72
|
+
unless message.size == 6 && message[0] == 'PING'
|
73
|
+
return false, 'invalid ping message'
|
74
|
+
end
|
75
|
+
ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
|
76
|
+
|
77
|
+
shared_key = if @node && @node[:shared_key]
|
78
|
+
@node[:shared_key]
|
79
|
+
else
|
80
|
+
@receiver.shared_key
|
81
|
+
end
|
82
|
+
serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(@shared_key_nonce).update(shared_key).hexdigest
|
83
|
+
if shared_key_hexdigest != serverside
|
84
|
+
log.warn "Shared key mismatch from '#{hostname}'"
|
85
|
+
return false, 'shared_key mismatch'
|
86
|
+
end
|
87
|
+
|
88
|
+
if @receiver.authentication
|
89
|
+
users = @receiver.select_authenticate_users(@node, username)
|
90
|
+
success = false
|
91
|
+
users.each do |user|
|
92
|
+
passhash = Digest::SHA512.new.update(@auth_key_salt).update(username).update(user[:password]).hexdigest
|
93
|
+
success ||= (passhash == password_digest)
|
94
|
+
end
|
95
|
+
unless success
|
96
|
+
log.warn "Authentication failed from client '#{hostname}', username '#{username}'"
|
97
|
+
return false, 'username/password mismatch'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
return true, shared_key_salt
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_pong(auth_result, reason_or_salt)
|
105
|
+
log.debug "generating pong"
|
106
|
+
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
107
|
+
# self_hostname, sha512\_hex(salt + self_hostname + nonce + sharedkey)]
|
108
|
+
if not auth_result
|
109
|
+
return ['PONG', false, reason_or_salt, '', '']
|
110
|
+
end
|
111
|
+
|
112
|
+
shared_key = if @node && @node[:shared_key]
|
113
|
+
@node[:shared_key]
|
114
|
+
else
|
115
|
+
@receiver.shared_key
|
116
|
+
end
|
117
|
+
shared_key_hex = Digest::SHA512.new.update(reason_or_salt).update(@receiver.self_hostname).update(@shared_key_nonce).update(shared_key).hexdigest
|
118
|
+
[ 'PONG', true, '', @receiver.self_hostname, shared_key_hex ]
|
119
|
+
end
|
120
|
+
|
121
|
+
def on_read(data)
|
122
|
+
log.debug "on_read"
|
123
|
+
if self.established?
|
124
|
+
@receiver.on_message(data)
|
125
|
+
end
|
126
|
+
|
127
|
+
case @state
|
128
|
+
when :pingpong
|
129
|
+
success, reason_or_salt = self.check_ping(data)
|
130
|
+
if not success
|
131
|
+
send_data generate_pong(false, reason_or_salt)
|
132
|
+
self.shutdown
|
133
|
+
return
|
134
|
+
end
|
135
|
+
send_data generate_pong(true, reason_or_salt)
|
136
|
+
|
137
|
+
log.debug "connection established"
|
138
|
+
@state = :established
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def send_data(data)
|
143
|
+
# not nonblock because write data (response) needs sequence
|
144
|
+
@socket.write data.to_msgpack
|
145
|
+
end
|
146
|
+
|
147
|
+
def start
|
148
|
+
log.debug "starting server"
|
149
|
+
|
150
|
+
log.trace "accepting ssl session"
|
151
|
+
begin
|
152
|
+
@socket.accept
|
153
|
+
rescue OpenSSL::SSL::SSLError => e
|
154
|
+
log.debug "failed to establish ssl session", error_class: e.class, error: e
|
155
|
+
self.shutdown
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
proto, port, host, ipaddr = @socket.io.peeraddr
|
160
|
+
@node = check_node(ipaddr)
|
161
|
+
if @node.nil? && (! @receiver.allow_anonymous_source)
|
162
|
+
log.warn "Connection required from unknown host '#{host}' (#{ipaddr}), disconnecting..."
|
163
|
+
self.shutdown
|
164
|
+
return
|
165
|
+
end
|
166
|
+
|
167
|
+
@shared_key_nonce = generate_salt
|
168
|
+
@auth_key_salt = generate_salt
|
169
|
+
|
170
|
+
buf = ''
|
171
|
+
read_length = @receiver.read_length
|
172
|
+
read_interval = @receiver.read_interval
|
173
|
+
socket_interval = @receiver.socket_interval
|
174
|
+
|
175
|
+
send_data generate_helo()
|
176
|
+
@state = :pingpong
|
177
|
+
|
178
|
+
loop do
|
179
|
+
begin
|
180
|
+
while @socket.read_nonblock(read_length, buf)
|
181
|
+
if buf == ''
|
182
|
+
sleep read_interval
|
183
|
+
next
|
184
|
+
end
|
185
|
+
@unpacker.feed_each(buf, &method(:on_read))
|
186
|
+
buf = ''
|
187
|
+
end
|
188
|
+
rescue OpenSSL::SSL::SSLError => e
|
189
|
+
# to wait i/o restart
|
190
|
+
sleep socket_interval
|
191
|
+
rescue EOFError => e
|
192
|
+
log.debug "Connection closed from '#{host}'(#{ipaddr})"
|
193
|
+
break
|
194
|
+
end
|
195
|
+
end
|
196
|
+
rescue Errno::ECONNRESET => e
|
197
|
+
# disconnected from client
|
198
|
+
rescue => e
|
199
|
+
log.warn "unexpected error in in_secure_forward", error_class: e.class, error: e
|
200
|
+
ensure
|
201
|
+
self.shutdown
|
202
|
+
end
|
203
|
+
|
204
|
+
def shutdown
|
205
|
+
@state = :closed
|
206
|
+
if @thread == Thread.current
|
207
|
+
@socket.close
|
208
|
+
@thread.kill
|
209
|
+
else
|
210
|
+
if @thread
|
211
|
+
@thread.kill
|
212
|
+
@thread.join
|
213
|
+
end
|
214
|
+
@socket.close
|
215
|
+
end
|
216
|
+
rescue => e
|
217
|
+
log.debug "#{e.class}:#{e.message}"
|
218
|
+
end
|
219
|
+
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
|