fluent-plugin-secure-forward 0.1.8 → 0.1.9.pre.rc1
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 +4 -4
- data/README.md +4 -2
- data/example/client.conf +1 -2
- data/fluent-plugin-secure-forward.gemspec +2 -2
- data/lib/fluent/plugin/in_secure_forward.rb +39 -34
- data/lib/fluent/plugin/input_session.rb +26 -24
- data/lib/fluent/plugin/out_secure_forward.rb +42 -5
- data/lib/fluent/plugin/output_node.rb +27 -23
- data/test/helper.rb +46 -9
- data/test/plugin/test_in_secure_forward.rb +159 -1
- data/test/plugin/test_input_session.rb +43 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fc54e54fa73f9f47d2edbc7c57727d88909718e
|
4
|
+
data.tar.gz: 17e46a3f3a58d57ab468d05972fa72da68f5d692
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df677feca1b1252f6b6b22c43c199654728d3e39b2569f20b1207d682075c75d4389b77977955992bb3d5756d620c5119e43d2d6c466986572aae5e09684d78c
|
7
|
+
data.tar.gz: e6bbbe1860763d53c4aea3534bfb5dde66b153d5580d4503c26f947cb278f6bdb64d810c6d4b04170ecd1997a25a63fdae6058e99b526af37ae5d1ecf2ad1280
|
data/README.md
CHANGED
@@ -75,12 +75,14 @@ To deny unknown source IP/hosts:
|
|
75
75
|
allow_anonymous_source no # Allow to accept from nodes of <client>
|
76
76
|
<client>
|
77
77
|
host 192.168.10.30
|
78
|
-
# network address (ex: 192.168.10.0/24) NOT Supported now
|
79
78
|
</client>
|
80
79
|
<client>
|
81
80
|
host your.host.fqdn.local
|
82
81
|
# wildcard (ex: *.host.fqdn.local) NOT Supported now
|
83
82
|
</client>
|
83
|
+
<client>
|
84
|
+
network 192.168.16.0/24 # network address specification
|
85
|
+
</client>
|
84
86
|
</source>
|
85
87
|
|
86
88
|
You can use both of username/password check and client check:
|
@@ -103,7 +105,7 @@ You can use both of username/password check and client check:
|
|
103
105
|
<user>
|
104
106
|
username repeatedly
|
105
107
|
password sushi
|
106
|
-
</user
|
108
|
+
</user>
|
107
109
|
<client>
|
108
110
|
host 192.168.10.30 # allow all users to connect from 192.168.10.30
|
109
111
|
</client>
|
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.1.
|
4
|
+
gem.version = "0.1.9-rc1"
|
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}
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
|
17
|
-
gem.add_runtime_dependency "fluentd"
|
17
|
+
gem.add_runtime_dependency "fluentd", ">= 0.10.46"
|
18
18
|
gem.add_runtime_dependency "fluent-mixin-config-placeholders"
|
19
19
|
gem.add_runtime_dependency "resolve-hostname"
|
20
20
|
gem.add_development_dependency "rake"
|
@@ -48,22 +48,24 @@ module Fluent
|
|
48
48
|
|
49
49
|
attr_reader :read_interval, :socket_interval
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
config_section :user, param_name: :users do
|
52
|
+
config_param :username, :string
|
53
|
+
config_param :password, :string
|
54
|
+
end
|
55
|
+
|
56
|
+
config_section :client, param_name: :clients do
|
57
|
+
config_param :host, :string, default: nil
|
58
|
+
config_param :network, :string, default: nil
|
59
|
+
config_param :shared_key, :string, default: nil
|
60
|
+
config_param :users, :string, default: nil # comma separated username list
|
61
|
+
end
|
62
|
+
attr_reader :nodes
|
62
63
|
|
63
64
|
attr_reader :sessions # node/socket/thread list which has sslsocket instance keepaliving to client
|
64
65
|
|
65
66
|
def initialize
|
66
67
|
super
|
68
|
+
require 'ipaddr'
|
67
69
|
require 'socket'
|
68
70
|
require 'openssl'
|
69
71
|
require 'digest'
|
@@ -84,30 +86,33 @@ module Fluent
|
|
84
86
|
@read_interval = @read_interval_msec / 1000.0
|
85
87
|
@socket_interval = @socket_interval_msec / 1000.0
|
86
88
|
|
87
|
-
@users = []
|
88
89
|
@nodes = []
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
90
|
+
|
91
|
+
@clients.each do |client|
|
92
|
+
if client.host && client.network
|
93
|
+
raise Fluent::ConfigError, "both of 'host' and 'network' are specified for client"
|
94
|
+
end
|
95
|
+
if !client.host && !client.network
|
96
|
+
raise Fluent::ConfigError, "Either of 'host' and 'network' must be specified for client"
|
97
|
+
end
|
98
|
+
source = nil
|
99
|
+
if client.host
|
100
|
+
begin
|
101
|
+
source = IPSocket.getaddress(client.host)
|
102
|
+
rescue SocketError => e
|
103
|
+
raise Fluent::ConfigError, "host '#{client.host}' cannot be resolved"
|
102
104
|
end
|
103
|
-
@nodes.push({
|
104
|
-
host: element['host'],
|
105
|
-
shared_key: (element['shared_key'] || @shared_key),
|
106
|
-
users: (element['users'] ? element['users'].split(',') : nil),
|
107
|
-
})
|
108
|
-
else
|
109
|
-
raise Fluent::ConfigError, "unknown config tag name"
|
110
105
|
end
|
106
|
+
source_addr = begin
|
107
|
+
IPAddr.new(source || client.network)
|
108
|
+
rescue ArgumentError => e
|
109
|
+
raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
|
110
|
+
end
|
111
|
+
@nodes.push({
|
112
|
+
address: source_addr,
|
113
|
+
shared_key: (client.shared_key || @shared_key),
|
114
|
+
users: (client.users ? client.users.split(',') : nil)
|
115
|
+
})
|
111
116
|
end
|
112
117
|
|
113
118
|
@generate_cert_common_name ||= @self_hostname
|
@@ -132,9 +137,9 @@ module Fluent
|
|
132
137
|
|
133
138
|
def select_authenticate_users(node, username)
|
134
139
|
if node.nil? || node[:users].nil?
|
135
|
-
@users.select{|u| u
|
140
|
+
@users.select{|u| u.username == username}
|
136
141
|
else
|
137
|
-
@users.select{|u| node[:users].include?(u
|
142
|
+
@users.select{|u| node[:users].include?(u.username) && u.username == username}
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require 'msgpack'
|
2
|
+
require 'socket'
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest'
|
5
|
+
# require 'resolv'
|
6
6
|
|
7
7
|
class Fluent::SecureForwardInput::Session
|
8
8
|
attr_accessor :receiver
|
@@ -22,6 +22,10 @@ class Fluent::SecureForwardInput::Session
|
|
22
22
|
@thread = Thread.new(&method(:start))
|
23
23
|
end
|
24
24
|
|
25
|
+
def log
|
26
|
+
@receiver.log
|
27
|
+
end
|
28
|
+
|
25
29
|
def established?
|
26
30
|
@state == :established
|
27
31
|
end
|
@@ -30,12 +34,10 @@ class Fluent::SecureForwardInput::Session
|
|
30
34
|
OpenSSL::Random.random_bytes(16)
|
31
35
|
end
|
32
36
|
|
33
|
-
def check_node(
|
37
|
+
def check_node(ipaddress)
|
34
38
|
node = nil
|
35
|
-
family = Socket.const_get(proto)
|
36
39
|
@receiver.nodes.each do |n|
|
37
|
-
|
38
|
-
if ipaddr == ipaddress
|
40
|
+
if n[:address].include?(ipaddress)
|
39
41
|
node = n
|
40
42
|
break
|
41
43
|
end
|
@@ -54,13 +56,13 @@ class Fluent::SecureForwardInput::Session
|
|
54
56
|
# end
|
55
57
|
|
56
58
|
def generate_helo
|
57
|
-
|
59
|
+
log.debug "generating helo"
|
58
60
|
# ['HELO', options(hash)]
|
59
61
|
[ 'HELO', {'auth' => (@receiver.authentication ? @auth_key_salt : ''), 'keepalive' => @receiver.allow_keepalive } ]
|
60
62
|
end
|
61
63
|
|
62
64
|
def check_ping(message)
|
63
|
-
|
65
|
+
log.debug "checking ping"
|
64
66
|
# ['PING', self_hostname, shared_key\_salt, sha512\_hex(shared_key\_salt + self_hostname + shared_key),
|
65
67
|
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
66
68
|
unless message.size == 6 && message[0] == 'PING'
|
@@ -75,7 +77,7 @@ class Fluent::SecureForwardInput::Session
|
|
75
77
|
end
|
76
78
|
serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(shared_key).hexdigest
|
77
79
|
if shared_key_hexdigest != serverside
|
78
|
-
|
80
|
+
log.warn "Shared key mismatch from '#{hostname}'"
|
79
81
|
return false, 'shared_key mismatch'
|
80
82
|
end
|
81
83
|
|
@@ -87,7 +89,7 @@ class Fluent::SecureForwardInput::Session
|
|
87
89
|
success ||= (passhash == password_digest)
|
88
90
|
end
|
89
91
|
unless success
|
90
|
-
|
92
|
+
log.warn "Authentication failed from client '#{hostname}', username '#{username}'"
|
91
93
|
return false, 'username/password mismatch'
|
92
94
|
end
|
93
95
|
end
|
@@ -96,7 +98,7 @@ class Fluent::SecureForwardInput::Session
|
|
96
98
|
end
|
97
99
|
|
98
100
|
def generate_pong(auth_result, reason_or_salt)
|
99
|
-
|
101
|
+
log.debug "generating pong"
|
100
102
|
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
101
103
|
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
102
104
|
if not auth_result
|
@@ -113,7 +115,7 @@ class Fluent::SecureForwardInput::Session
|
|
113
115
|
end
|
114
116
|
|
115
117
|
def on_read(data)
|
116
|
-
|
118
|
+
log.debug "on_read"
|
117
119
|
if self.established?
|
118
120
|
@receiver.on_message(data)
|
119
121
|
end
|
@@ -128,7 +130,7 @@ class Fluent::SecureForwardInput::Session
|
|
128
130
|
end
|
129
131
|
send_data generate_pong(true, reason_or_salt)
|
130
132
|
|
131
|
-
|
133
|
+
log.debug "connection established"
|
132
134
|
@state = :established
|
133
135
|
end
|
134
136
|
end
|
@@ -139,21 +141,21 @@ class Fluent::SecureForwardInput::Session
|
|
139
141
|
end
|
140
142
|
|
141
143
|
def start
|
142
|
-
|
144
|
+
log.debug "starting server"
|
143
145
|
|
144
|
-
|
146
|
+
log.trace "accepting ssl session"
|
145
147
|
begin
|
146
148
|
@socket.accept
|
147
149
|
rescue OpenSSL::SSL::SSLError => e
|
148
|
-
|
150
|
+
log.debug "failed to establish ssl session"
|
149
151
|
self.shutdown
|
150
152
|
return
|
151
153
|
end
|
152
154
|
|
153
155
|
proto, port, host, ipaddr = @socket.io.peeraddr
|
154
|
-
@node = check_node(
|
156
|
+
@node = check_node(ipaddr)
|
155
157
|
if @node.nil? && (! @receiver.allow_anonymous_source)
|
156
|
-
|
158
|
+
log.warn "Connection required from unknown host '#{host}' (#{ipaddr}), disconnecting..."
|
157
159
|
self.shutdown
|
158
160
|
return
|
159
161
|
end
|
@@ -182,14 +184,14 @@ class Fluent::SecureForwardInput::Session
|
|
182
184
|
# to wait i/o restart
|
183
185
|
sleep socket_interval
|
184
186
|
rescue EOFError => e
|
185
|
-
|
187
|
+
log.debug "Connection closed from '#{host}'(#{ipaddr})"
|
186
188
|
break
|
187
189
|
end
|
188
190
|
end
|
189
191
|
rescue Errno::ECONNRESET => e
|
190
192
|
# disconnected from client
|
191
193
|
rescue => e
|
192
|
-
|
194
|
+
log.warn "unexpected error in in_secure_forward", :error_class => e.class, :error => e
|
193
195
|
ensure
|
194
196
|
self.shutdown
|
195
197
|
end
|
@@ -207,6 +209,6 @@ class Fluent::SecureForwardInput::Session
|
|
207
209
|
@socket.close
|
208
210
|
end
|
209
211
|
rescue => e
|
210
|
-
|
212
|
+
log.debug "#{e.class}:#{e.message}"
|
211
213
|
end
|
212
214
|
end
|
@@ -20,7 +20,7 @@ module Fluent
|
|
20
20
|
|
21
21
|
config_param :shared_key, :string
|
22
22
|
|
23
|
-
config_param :keepalive, :time, :default => nil # nil/0 means disable keepalive
|
23
|
+
config_param :keepalive, :time, :default => nil # nil/0 means disable keepalive expiration
|
24
24
|
|
25
25
|
config_param :send_timeout, :time, :default => 60
|
26
26
|
# config_param :hard_timeout, :time, :default => 60
|
@@ -34,6 +34,7 @@ module Fluent
|
|
34
34
|
config_param :socket_interval_msec, :integer, :default => 200 # 200ms
|
35
35
|
|
36
36
|
config_param :reconnect_interval, :time, :default => 5
|
37
|
+
config_param :established_timeout, :time, :default => 10
|
37
38
|
|
38
39
|
attr_reader :read_interval, :socket_interval
|
39
40
|
|
@@ -129,6 +130,8 @@ module Fluent
|
|
129
130
|
end
|
130
131
|
|
131
132
|
def node_watcher
|
133
|
+
reconnectings = Array.new(@nodes.size)
|
134
|
+
|
132
135
|
loop do
|
133
136
|
sleep @reconnect_interval
|
134
137
|
|
@@ -139,17 +142,51 @@ module Fluent
|
|
139
142
|
|
140
143
|
next if @nodes[i].established? && ! @nodes[i].expired?
|
141
144
|
|
145
|
+
next if reconnectings[i]
|
146
|
+
|
142
147
|
log.info "dead connection found: #{@nodes[i].host}, reconnecting..." unless @nodes[i].established?
|
143
148
|
|
144
149
|
node = @nodes[i]
|
145
150
|
log.debug "reconnecting to node", :host => node.host, :port => node.port, :expire => node.expire, :expired => node.expired?
|
146
151
|
|
147
|
-
|
148
|
-
|
152
|
+
renewed = node.dup
|
153
|
+
begin
|
154
|
+
renewed.start
|
155
|
+
Thread.pass # to connection thread
|
156
|
+
reconnectings[i] = { :conn => renewed, :at => Time.now }
|
157
|
+
rescue => e
|
158
|
+
log.debug "Some error occured on start of renewed connection", :error_class => e2.class, :error => e2, :host => renewed.host, :port => renewed.port
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
(0...(reconnectings.size)).each do |i|
|
163
|
+
next unless reconnectings[i]
|
164
|
+
|
165
|
+
if reconnectings[i][:conn].established?
|
166
|
+
oldconn = @nodes[i]
|
167
|
+
@nodes[i] = reconnectings[i][:conn]
|
168
|
+
begin
|
169
|
+
oldconn.shutdown
|
170
|
+
rescue => e
|
171
|
+
log.debug "Some error occured on shutdown of expired connection", :error_class => e.class, :error => e, :host => renewed.host, :port => renewed.port
|
172
|
+
end
|
173
|
+
|
174
|
+
reconnectings[i] = nil
|
175
|
+
next
|
176
|
+
end
|
177
|
+
|
178
|
+
# not connected yet
|
179
|
+
|
180
|
+
next if reconnectings[i][:at] < Time.now + @established_timeout
|
181
|
+
|
182
|
+
# not connected yet, and timeout
|
149
183
|
begin
|
150
|
-
|
184
|
+
timeout_conn = reconnectings[i][:conn]
|
185
|
+
log.debug "SSL connection is not established until timemout", :host => timeout_conn.host, :port => timeout_conn.port, :timeout => @established_timeout
|
186
|
+
reconnectings[i] = nil
|
187
|
+
timeout_conn.shutdown
|
151
188
|
rescue => e
|
152
|
-
log.
|
189
|
+
log.debug "Some error occured on shutdown of timeout re-connection", :error_class => e.class, :error => e
|
153
190
|
end
|
154
191
|
end
|
155
192
|
end
|
@@ -43,6 +43,10 @@ class Fluent::SecureForwardOutput::Node
|
|
43
43
|
@thread = nil
|
44
44
|
end
|
45
45
|
|
46
|
+
def log
|
47
|
+
@sender.log
|
48
|
+
end
|
49
|
+
|
46
50
|
def dup
|
47
51
|
renewed = self.class.new(
|
48
52
|
@sender,
|
@@ -58,7 +62,7 @@ class Fluent::SecureForwardOutput::Node
|
|
58
62
|
end
|
59
63
|
|
60
64
|
def shutdown
|
61
|
-
|
65
|
+
log.debug "shutting down node #{@host}"
|
62
66
|
@state = :closed
|
63
67
|
|
64
68
|
if @thread == Thread.current
|
@@ -74,7 +78,7 @@ class Fluent::SecureForwardOutput::Node
|
|
74
78
|
@socket.close if @socket
|
75
79
|
end
|
76
80
|
rescue => e
|
77
|
-
|
81
|
+
log.debug "error on node shutdown #{e.class}:#{e.message}"
|
78
82
|
end
|
79
83
|
|
80
84
|
def join
|
@@ -98,7 +102,7 @@ class Fluent::SecureForwardOutput::Node
|
|
98
102
|
end
|
99
103
|
|
100
104
|
def check_helo(message)
|
101
|
-
|
105
|
+
log.debug "checking helo"
|
102
106
|
# ['HELO', options(hash)]
|
103
107
|
unless message.size == 2 && message[0] == 'HELO'
|
104
108
|
return false
|
@@ -110,7 +114,7 @@ class Fluent::SecureForwardOutput::Node
|
|
110
114
|
end
|
111
115
|
|
112
116
|
def generate_ping
|
113
|
-
|
117
|
+
log.debug "generating ping"
|
114
118
|
# ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + shared_key),
|
115
119
|
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
116
120
|
shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt).update(@sender.self_hostname).update(@shared_key).hexdigest
|
@@ -125,7 +129,7 @@ class Fluent::SecureForwardOutput::Node
|
|
125
129
|
end
|
126
130
|
|
127
131
|
def check_pong(message)
|
128
|
-
|
132
|
+
log.debug "checking pong"
|
129
133
|
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
130
134
|
# self_hostname, sha512\_hex(salt + self_hostname + sharedkey)]
|
131
135
|
unless message.size == 5 && message[0] == 'PONG'
|
@@ -150,17 +154,17 @@ class Fluent::SecureForwardOutput::Node
|
|
150
154
|
end
|
151
155
|
|
152
156
|
def on_read(data)
|
153
|
-
|
157
|
+
log.debug "on_read"
|
154
158
|
if self.established?
|
155
159
|
#TODO: ACK
|
156
|
-
|
160
|
+
log.warn "unknown packets arrived..."
|
157
161
|
return
|
158
162
|
end
|
159
163
|
|
160
164
|
case @state
|
161
165
|
when :helo
|
162
166
|
unless check_helo(data)
|
163
|
-
|
167
|
+
log.warn "received invalid helo message from #{@host}"
|
164
168
|
self.shutdown
|
165
169
|
return
|
166
170
|
end
|
@@ -169,25 +173,25 @@ class Fluent::SecureForwardOutput::Node
|
|
169
173
|
when :pingpong
|
170
174
|
success, reason = check_pong(data)
|
171
175
|
unless success
|
172
|
-
|
176
|
+
log.warn "connection refused to #{@host}:" + reason
|
173
177
|
self.shutdown
|
174
178
|
return
|
175
179
|
end
|
176
|
-
|
180
|
+
log.info "connection established to #{@host}" if @first_session
|
177
181
|
@state = :established
|
178
182
|
@expire = Time.now + @keepalive if @keepalive && @keepalive > 0
|
179
|
-
|
183
|
+
log.debug "connection established", :host => @host, :port => @port, :expire => @expire
|
180
184
|
end
|
181
185
|
end
|
182
186
|
|
183
187
|
def connect
|
184
|
-
|
188
|
+
log.debug "starting client"
|
185
189
|
|
186
190
|
addr = @sender.hostname_resolver.getaddress(@host)
|
187
|
-
|
191
|
+
log.debug "create tcp socket to node", :host => @host, :address => addr, :port => @port
|
188
192
|
sock = TCPSocket.new(addr, @port)
|
189
193
|
|
190
|
-
|
194
|
+
log.trace "changing socket options"
|
191
195
|
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
192
196
|
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
193
197
|
|
@@ -195,36 +199,36 @@ class Fluent::SecureForwardOutput::Node
|
|
195
199
|
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
196
200
|
|
197
201
|
# TODO: SSLContext constructer parameter (SSL/TLS protocol version)
|
198
|
-
|
202
|
+
log.trace "initializing SSL contexts"
|
199
203
|
context = OpenSSL::SSL::SSLContext.new
|
200
204
|
# TODO: context.ca_file = (ca_file_path)
|
201
205
|
# TODO: context.ciphers = (SSL Shared key chiper protocols)
|
202
206
|
|
203
|
-
|
207
|
+
log.debug "trying to connect ssl session", :host => @host, :ipaddr => addr, :port => @port
|
204
208
|
sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
|
205
209
|
# TODO: check connection failure
|
206
210
|
sslsession.connect
|
207
|
-
|
211
|
+
log.debug "ssl session connected", :host => @host, :port => @port
|
208
212
|
|
209
213
|
begin
|
210
214
|
unless @sender.allow_self_signed_certificate
|
211
|
-
|
215
|
+
log.debug "checking peer's certificate", :subject => sslsession.peer_cert.subject
|
212
216
|
sslsession.post_connection_check(@hostlabel)
|
213
217
|
verify = sslsession.verify_result
|
214
218
|
if verify != OpenSSL::X509::V_OK
|
215
219
|
err_name = Fluent::SecureForwardOutput::OpenSSLUtil.verify_result_name(verify)
|
216
|
-
|
217
|
-
|
220
|
+
log.warn "failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
|
221
|
+
log.warn "verify_result: #{err_name}"
|
218
222
|
raise RuntimeError, "failed to verify certification while connecting host #{@host} as #{@hostlabel}"
|
219
223
|
end
|
220
224
|
end
|
221
225
|
rescue OpenSSL::SSL::SSLError => e
|
222
|
-
|
226
|
+
log.warn "failed to verify certification while connecting ssl session", :host => @host, :hostlabel => @hostlabel
|
223
227
|
self.shutdown
|
224
228
|
raise
|
225
229
|
end
|
226
230
|
|
227
|
-
|
231
|
+
log.debug "ssl sessison connected", :host => @host, :port => @port
|
228
232
|
@socket = sock
|
229
233
|
@sslsession = sslsession
|
230
234
|
|
@@ -249,7 +253,7 @@ class Fluent::SecureForwardOutput::Node
|
|
249
253
|
# to wait i/o restart
|
250
254
|
sleep socket_interval
|
251
255
|
rescue EOFError
|
252
|
-
|
256
|
+
log.warn "disconnected from #{@host}"
|
253
257
|
break
|
254
258
|
end
|
255
259
|
end
|
data/test/helper.rb
CHANGED
@@ -12,18 +12,55 @@ require 'test/unit'
|
|
12
12
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
13
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
14
|
require 'fluent/test'
|
15
|
-
|
16
|
-
|
17
|
-
nulllogger.instance_eval {|obj|
|
18
|
-
def method_missing(method, *args)
|
19
|
-
# pass
|
20
|
-
end
|
21
|
-
}
|
22
|
-
$log = nulllogger
|
23
|
-
end
|
15
|
+
|
16
|
+
$log = Fluent::Log.new(Fluent::Test::DummyLogDevice.new, Fluent::Log::LEVEL_INFO)
|
24
17
|
|
25
18
|
require 'fluent/plugin/in_secure_forward'
|
26
19
|
require 'fluent/plugin/out_secure_forward'
|
27
20
|
|
21
|
+
class DummySocket
|
22
|
+
attr_accessor :sync
|
23
|
+
end
|
24
|
+
|
25
|
+
class DummyInputPlugin
|
26
|
+
attr_reader :log, :users, :nodes, :authentication, :allow_anonymous_source, :allow_keepalive
|
27
|
+
attr_reader :shared_key, :self_hostname
|
28
|
+
attr_reader :read_length, :read_interval, :socket_interval
|
29
|
+
|
30
|
+
attr_reader :data
|
31
|
+
|
32
|
+
def initialize(opts={})
|
33
|
+
@log = $log
|
34
|
+
@users = opts.fetch(:users, [])
|
35
|
+
@nodes = opts.fetch(:nodes, [])
|
36
|
+
@authentication = opts.fetch(:authentication, false)
|
37
|
+
@allow_anonymous_source = opts.fetch(:allow_anonymous_source, true)
|
38
|
+
@allow_keepalive = opts.fetch(:allow_keepalive, true)
|
39
|
+
@shared_key = opts.fetch(:shared_key, 'shared key')
|
40
|
+
@self_hostname = opts.fetch(:self_hostname, 'hostname.local')
|
41
|
+
@read_length = opts.fetch(:read_length, 8*1024*1024)
|
42
|
+
@read_interval = opts.fetch(:read_interval, 0.05)
|
43
|
+
@socket_interval = opts.fetch(:socket_interval, 0.2)
|
44
|
+
|
45
|
+
@data = []
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_authenticate_users(node, username)
|
49
|
+
if node.nil? || node[:users].nil?
|
50
|
+
self.users.select{|u| u[:username] == username}
|
51
|
+
else
|
52
|
+
self.users.select{|u| node[:users].include?(u[:username]) && u[:username] == username}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_message(data)
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class DummyOutputPlugin
|
62
|
+
end
|
63
|
+
|
64
|
+
|
28
65
|
class Test::Unit::TestCase
|
29
66
|
end
|
@@ -2,9 +2,167 @@ require 'helper'
|
|
2
2
|
|
3
3
|
class SecureForwardInputTest < Test::Unit::TestCase
|
4
4
|
CONFIG = %[
|
5
|
+
|
5
6
|
]
|
6
7
|
|
7
8
|
def create_driver(conf=CONFIG,tag='test')
|
8
|
-
Fluent::Test::InputTestDriver.new(Fluent::SecureForwardInput
|
9
|
+
Fluent::Test::InputTestDriver.new(Fluent::SecureForwardInput).configure(conf)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_configure
|
13
|
+
p1 = nil
|
14
|
+
assert_nothing_raised { p1 = create_driver(<<CONFIG).instance }
|
15
|
+
type secure_forward
|
16
|
+
shared_key secret_string
|
17
|
+
self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
|
18
|
+
cert_auto_generate yes # This parameter MUST be specified
|
19
|
+
CONFIG
|
20
|
+
assert_equal 'secret_string', p1.shared_key
|
21
|
+
assert_equal 'server.fqdn.local', p1.self_hostname
|
22
|
+
assert p1.cert_auto_generate
|
23
|
+
|
24
|
+
assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
|
25
|
+
type secure_forward
|
26
|
+
shared_key secret_string
|
27
|
+
self_hostname server.fqdn.local
|
28
|
+
cert_auto_generate yes
|
29
|
+
authentication yes # Deny clients without valid username/password
|
30
|
+
<user>
|
31
|
+
username tagomoris
|
32
|
+
password foobar012
|
33
|
+
</user>
|
34
|
+
<user>
|
35
|
+
password yakiniku
|
36
|
+
</user>
|
37
|
+
CONFIG
|
38
|
+
assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
|
39
|
+
type secure_forward
|
40
|
+
shared_key secret_string
|
41
|
+
self_hostname server.fqdn.local
|
42
|
+
cert_auto_generate yes
|
43
|
+
authentication yes # Deny clients without valid username/password
|
44
|
+
<user>
|
45
|
+
username tagomoris
|
46
|
+
password foobar012
|
47
|
+
</user>
|
48
|
+
<user>
|
49
|
+
username frsyuki
|
50
|
+
</user>
|
51
|
+
CONFIG
|
52
|
+
|
53
|
+
p2 = nil
|
54
|
+
assert_nothing_raised { p2 = create_driver(<<CONFIG).instance }
|
55
|
+
type secure_forward
|
56
|
+
shared_key secret_string
|
57
|
+
self_hostname server.fqdn.local
|
58
|
+
cert_auto_generate yes
|
59
|
+
authentication yes # Deny clients without valid username/password
|
60
|
+
<user>
|
61
|
+
username tagomoris
|
62
|
+
password foobar012
|
63
|
+
</user>
|
64
|
+
<user>
|
65
|
+
username frsyuki
|
66
|
+
password yakiniku
|
67
|
+
</user>
|
68
|
+
CONFIG
|
69
|
+
assert_equal 2, p2.users.size
|
70
|
+
assert_equal 'tagomoris', p2.users[0].username
|
71
|
+
assert_equal 'foobar012', p2.users[0].password
|
72
|
+
|
73
|
+
assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
|
74
|
+
type secure_forward
|
75
|
+
shared_key secret_string
|
76
|
+
self_hostname server.fqdn.local
|
77
|
+
cert_auto_generate yes
|
78
|
+
allow_anonymous_source no # Allow to accept from nodes of <client>
|
79
|
+
<client>
|
80
|
+
host 192.168.10.30
|
81
|
+
# network address (ex: 192.168.10.0/24) NOT Supported now
|
82
|
+
</client>
|
83
|
+
<client>
|
84
|
+
host localhost
|
85
|
+
network 192.168.1.1/32
|
86
|
+
</client>
|
87
|
+
<client>
|
88
|
+
network 192.168.16.0/24
|
89
|
+
</client>
|
90
|
+
CONFIG
|
91
|
+
assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
|
92
|
+
type secure_forward
|
93
|
+
shared_key secret_string
|
94
|
+
self_hostname server.fqdn.local
|
95
|
+
cert_auto_generate yes
|
96
|
+
allow_anonymous_source no # Allow to accept from nodes of <client>
|
97
|
+
<client>
|
98
|
+
host 192.168.10.30
|
99
|
+
# network address (ex: 192.168.10.0/24) NOT Supported now
|
100
|
+
</client>
|
101
|
+
<client>
|
102
|
+
</client>
|
103
|
+
<client>
|
104
|
+
network 192.168.16.0/24
|
105
|
+
</client>
|
106
|
+
CONFIG
|
107
|
+
|
108
|
+
p3 = nil
|
109
|
+
assert_nothing_raised { p3 = create_driver(<<CONFIG).instance }
|
110
|
+
type secure_forward
|
111
|
+
shared_key secret_string
|
112
|
+
self_hostname server.fqdn.local
|
113
|
+
cert_auto_generate yes
|
114
|
+
allow_anonymous_source no # Allow to accept from nodes of <client>
|
115
|
+
<client>
|
116
|
+
host 192.168.10.30
|
117
|
+
# network address (ex: 192.168.10.0/24) NOT Supported now
|
118
|
+
</client>
|
119
|
+
<client>
|
120
|
+
host localhost
|
121
|
+
# wildcard (ex: *.host.fqdn.local) NOT Supported now
|
122
|
+
</client>
|
123
|
+
<client>
|
124
|
+
network 192.168.16.0/24
|
125
|
+
</client>
|
126
|
+
CONFIG
|
127
|
+
assert (not p3.allow_anonymous_source)
|
128
|
+
assert_equal 3, p3.clients.size
|
129
|
+
assert_equal '192.168.16.0/24', p3.clients[2].network
|
130
|
+
assert_equal 3, p3.nodes.size
|
131
|
+
assert_equal IPAddr.new('192.168.10.30'), p3.nodes[0][:address]
|
132
|
+
assert_equal IPAddr.new('192.168.16.0/24'), p3.nodes[2][:address]
|
133
|
+
|
134
|
+
p4 = nil
|
135
|
+
assert_nothing_raised { p4 = create_driver(<<CONFIG).instance }
|
136
|
+
shared_key secret_string
|
137
|
+
self_hostname server.fqdn.local
|
138
|
+
cert_auto_generate yes
|
139
|
+
allow_anonymous_source no # Allow to accept from nodes of <client>
|
140
|
+
authentication yes # Deny clients without valid username/password
|
141
|
+
<user>
|
142
|
+
username tagomoris
|
143
|
+
password foobar012
|
144
|
+
</user>
|
145
|
+
<user>
|
146
|
+
username frsyuki
|
147
|
+
password sukiyaki
|
148
|
+
</user>
|
149
|
+
<user>
|
150
|
+
username repeatedly
|
151
|
+
password sushi
|
152
|
+
</user>
|
153
|
+
<client>
|
154
|
+
host 192.168.10.30 # allow all users to connect from 192.168.10.30
|
155
|
+
</client>
|
156
|
+
<client>
|
157
|
+
host 192.168.10.31
|
158
|
+
users tagomoris,frsyuki # deny repeatedly from 192.168.10.31
|
159
|
+
</client>
|
160
|
+
<client>
|
161
|
+
host 192.168.10.32
|
162
|
+
shared_key less_secret_string # limited shared_key for 192.168.10.32
|
163
|
+
users repeatedly # and repatedly only
|
164
|
+
</client>
|
165
|
+
CONFIG
|
166
|
+
assert_equal ['tagomoris','frsyuki'], p4.nodes[1][:users]
|
9
167
|
end
|
10
168
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
require 'fluent/plugin/input_session'
|
4
|
+
|
5
|
+
require 'ipaddr'
|
6
|
+
|
7
|
+
class InputSessionTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_check_node
|
10
|
+
# def check_node(hostname, ipaddress, port, proto)
|
11
|
+
nodes = [
|
12
|
+
{ address: IPAddr.new('127.0.0.1'), shared_key: 'shared_key', users: ['tagomoris', 'repeatedly'] },
|
13
|
+
{ address: IPAddr.new('2001:DB8::9'), shared_key: 'shared_key2', users: nil },
|
14
|
+
{ address: IPAddr.new('127.0.0.0/24'), shared_key: 'shared_key3', users: ['tagomoris', 'repeatedly'] },
|
15
|
+
]
|
16
|
+
p1 = DummyInputPlugin.new(nodes: nodes)
|
17
|
+
s1 = Fluent::SecureForwardInput::Session.new(p1, DummySocket.new)
|
18
|
+
|
19
|
+
assert s1.check_node('127.0.0.1')
|
20
|
+
assert_equal 'shared_key', s1.check_node('127.0.0.1')[:shared_key]
|
21
|
+
|
22
|
+
assert s1.check_node('127.0.0.127')
|
23
|
+
assert_equal 'shared_key3', s1.check_node('127.0.0.127')[:shared_key]
|
24
|
+
|
25
|
+
assert_nil s1.check_node('192.0.2.8')
|
26
|
+
assert_nil s1.check_node('2001:DB8::8')
|
27
|
+
|
28
|
+
assert s1.check_node('2001:DB8::9')
|
29
|
+
assert_equal 'shared_key2', s1.check_node('2001:DB8::9')[:shared_key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_generate_helo
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_check_ping
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_generate_pong
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_on_read
|
42
|
+
end
|
43
|
+
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.1.
|
4
|
+
version: 0.1.9.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TAGOMORI Satoshi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.10.46
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.10.46
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: fluent-mixin-config-placeholders
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- lib/fluent/plugin/output_node.rb
|
96
96
|
- test/helper.rb
|
97
97
|
- test/plugin/test_in_secure_forward.rb
|
98
|
+
- test/plugin/test_input_session.rb
|
98
99
|
- test/plugin/test_out_secure_forward.rb
|
99
100
|
homepage: https://github.com/tagomoris/fluent-plugin-secure-forward
|
100
101
|
licenses:
|
@@ -111,9 +112,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
112
|
version: '0'
|
112
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
114
|
requirements:
|
114
|
-
- - "
|
115
|
+
- - ">"
|
115
116
|
- !ruby/object:Gem::Version
|
116
|
-
version:
|
117
|
+
version: 1.3.1
|
117
118
|
requirements: []
|
118
119
|
rubyforge_project:
|
119
120
|
rubygems_version: 2.2.2
|
@@ -123,4 +124,5 @@ summary: Fluentd input/output plugin to forward over SSL with authentications
|
|
123
124
|
test_files:
|
124
125
|
- test/helper.rb
|
125
126
|
- test/plugin/test_in_secure_forward.rb
|
127
|
+
- test/plugin/test_input_session.rb
|
126
128
|
- test/plugin/test_out_secure_forward.rb
|