fluent-plugin-secure-forward 0.1.8 → 0.1.9.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|