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.
@@ -0,0 +1,85 @@
1
+ require 'openssl'
2
+
3
+ module Fluent
4
+ module SecureForward
5
+ module CertUtil
6
+ def self.generate_ca_pair(opts={})
7
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
8
+
9
+ issuer = subject = OpenSSL::X509::Name.new
10
+ subject.add_entry('C', opts[:cert_country])
11
+ subject.add_entry('ST', opts[:cert_state])
12
+ subject.add_entry('L', opts[:cert_locality])
13
+ subject.add_entry('CN', opts[:cert_common_name])
14
+
15
+ digest = OpenSSL::Digest::SHA1.new
16
+
17
+ cert = OpenSSL::X509::Certificate.new
18
+ cert.not_before = Time.at(0)
19
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
20
+ cert.public_key = key
21
+ cert.serial = 1
22
+ cert.issuer = issuer
23
+ cert.subject = subject
24
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)]))
25
+ cert.sign(key, digest)
26
+
27
+ return cert, key
28
+ end
29
+
30
+ def self.generate_server_pair(opts={})
31
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
32
+
33
+ ca_key = OpenSSL::PKey::RSA.new(File.read(opts[:ca_key_path]), opts[:ca_key_passphrase])
34
+ ca_cert = OpenSSL::X509::Certificate.new(File.read(opts[:ca_cert_path]))
35
+ issuer = ca_cert.issuer
36
+
37
+ subject = OpenSSL::X509::Name.new
38
+ subject.add_entry('C', opts[:country])
39
+ subject.add_entry('ST', opts[:state])
40
+ subject.add_entry('L', opts[:locality])
41
+ subject.add_entry('CN', opts[:common_name])
42
+
43
+ digest = OpenSSL::Digest::SHA1.new
44
+
45
+ cert = OpenSSL::X509::Certificate.new
46
+ cert.not_before = Time.at(0)
47
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
48
+ cert.public_key = key
49
+ cert.serial = 2
50
+ cert.issuer = issuer
51
+ cert.subject = subject
52
+
53
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)]))
54
+ cert.add_extension OpenSSL::X509::Extension.new('nsCertType', 'server')
55
+
56
+ cert.sign ca_key, digest
57
+
58
+ return cert, key
59
+ end
60
+
61
+ def self.generate_self_signed_server_pair(opts={})
62
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
63
+
64
+ issuer = subject = OpenSSL::X509::Name.new
65
+ subject.add_entry('C', opts[:country])
66
+ subject.add_entry('ST', opts[:state])
67
+ subject.add_entry('L', opts[:locality])
68
+ subject.add_entry('CN', opts[:common_name])
69
+
70
+ digest = OpenSSL::Digest::SHA1.new
71
+
72
+ cert = OpenSSL::X509::Certificate.new
73
+ cert.not_before = Time.at(0)
74
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
75
+ cert.public_key = key
76
+ cert.serial = 1
77
+ cert.issuer = issuer
78
+ cert.subject = subject
79
+ cert.sign(key, digest)
80
+
81
+ return cert, key
82
+ end
83
+ end
84
+ end
85
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+
16
+ $log = Fluent::Log.new(Fluent::Test::DummyLogDevice.new, Fluent::Log::LEVEL_INFO)
17
+
18
+ require 'fluent/plugin/in_secure_forward'
19
+ require 'fluent/plugin/out_secure_forward'
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
+
65
+ class Test::Unit::TestCase
66
+ end
@@ -0,0 +1,237 @@
1
+ require 'helper'
2
+
3
+ class SecureForwardInputTest < Test::Unit::TestCase
4
+ CONFIG = %[
5
+
6
+ ]
7
+
8
+ def setup
9
+ Fluent::Test.setup
10
+ end
11
+
12
+ def create_driver(conf=CONFIG,tag='test')
13
+ Fluent::Test::InputTestDriver.new(Fluent::SecureForwardInput).configure(conf)
14
+ end
15
+
16
+ def test_configure
17
+ p1 = nil
18
+ assert_nothing_raised { p1 = create_driver(<<CONFIG).instance }
19
+ type secure_forward
20
+ secure false
21
+ shared_key secret_string
22
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
23
+ CONFIG
24
+ assert_equal 'secret_string', p1.shared_key
25
+ assert_equal 'server.fqdn.local', p1.self_hostname
26
+
27
+ assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
28
+ type secure_forward
29
+ secure no
30
+ shared_key secret_string
31
+ self_hostname server.fqdn.local
32
+ authentication yes # Deny clients without valid username/password
33
+ <user>
34
+ username tagomoris
35
+ password foobar012
36
+ </user>
37
+ <user>
38
+ password yakiniku
39
+ </user>
40
+ CONFIG
41
+ assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
42
+ type secure_forward
43
+ secure no
44
+ shared_key secret_string
45
+ self_hostname server.fqdn.local
46
+ authentication yes # Deny clients without valid username/password
47
+ <user>
48
+ username tagomoris
49
+ password foobar012
50
+ </user>
51
+ <user>
52
+ username frsyuki
53
+ </user>
54
+ CONFIG
55
+
56
+ p2 = nil
57
+ assert_nothing_raised { p2 = create_driver(<<CONFIG).instance }
58
+ type secure_forward
59
+ secure no
60
+ shared_key secret_string
61
+ self_hostname server.fqdn.local
62
+ authentication yes # Deny clients without valid username/password
63
+ <user>
64
+ username tagomoris
65
+ password foobar012
66
+ </user>
67
+ <user>
68
+ username frsyuki
69
+ password yakiniku
70
+ </user>
71
+ CONFIG
72
+ assert_equal 2, p2.users.size
73
+ assert_equal 'tagomoris', p2.users[0].username
74
+ assert_equal 'foobar012', p2.users[0].password
75
+
76
+ assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
77
+ type secure_forward
78
+ secure no
79
+ shared_key secret_string
80
+ self_hostname server.fqdn.local
81
+ allow_anonymous_source no # Allow to accept from nodes of <client>
82
+ <client>
83
+ host 192.168.10.30
84
+ # network address (ex: 192.168.10.0/24) NOT Supported now
85
+ </client>
86
+ <client>
87
+ host localhost
88
+ network 192.168.1.1/32
89
+ </client>
90
+ <client>
91
+ network 192.168.16.0/24
92
+ </client>
93
+ CONFIG
94
+ assert_raise(Fluent::ConfigError){ create_driver(<<CONFIG) }
95
+ type secure_forward
96
+ secure no
97
+ shared_key secret_string
98
+ self_hostname server.fqdn.local
99
+ allow_anonymous_source no # Allow to accept from nodes of <client>
100
+ <client>
101
+ host 192.168.10.30
102
+ # network address (ex: 192.168.10.0/24) NOT Supported now
103
+ </client>
104
+ <client>
105
+ </client>
106
+ <client>
107
+ network 192.168.16.0/24
108
+ </client>
109
+ CONFIG
110
+
111
+ p3 = nil
112
+ assert_nothing_raised { p3 = create_driver(<<CONFIG).instance }
113
+ type secure_forward
114
+ secure no
115
+ shared_key secret_string
116
+ self_hostname server.fqdn.local
117
+ allow_anonymous_source no # Allow to accept from nodes of <client>
118
+ <client>
119
+ host 192.168.10.30
120
+ # network address (ex: 192.168.10.0/24) NOT Supported now
121
+ </client>
122
+ <client>
123
+ host localhost
124
+ # wildcard (ex: *.host.fqdn.local) NOT Supported now
125
+ </client>
126
+ <client>
127
+ network 192.168.16.0/24
128
+ </client>
129
+ CONFIG
130
+ assert (not p3.allow_anonymous_source)
131
+ assert_equal 3, p3.clients.size
132
+ assert_equal '192.168.16.0/24', p3.clients[2].network
133
+ assert_equal 3, p3.nodes.size
134
+ assert_equal IPAddr.new('192.168.10.30'), p3.nodes[0][:address]
135
+ assert_equal IPAddr.new('192.168.16.0/24'), p3.nodes[2][:address]
136
+
137
+ p4 = nil
138
+ assert_nothing_raised { p4 = create_driver(<<CONFIG).instance }
139
+ secure no
140
+ shared_key secret_string
141
+ self_hostname server.fqdn.local
142
+ cert_auto_generate yes
143
+ allow_anonymous_source no # Allow to accept from nodes of <client>
144
+ authentication yes # Deny clients without valid username/password
145
+ <user>
146
+ username tagomoris
147
+ password foobar012
148
+ </user>
149
+ <user>
150
+ username frsyuki
151
+ password sukiyaki
152
+ </user>
153
+ <user>
154
+ username repeatedly
155
+ password sushi
156
+ </user>
157
+ <client>
158
+ host 192.168.10.30 # allow all users to connect from 192.168.10.30
159
+ </client>
160
+ <client>
161
+ host 192.168.10.31
162
+ users tagomoris,frsyuki # deny repeatedly from 192.168.10.31
163
+ </client>
164
+ <client>
165
+ host 192.168.10.32
166
+ shared_key less_secret_string # limited shared_key for 192.168.10.32
167
+ users repeatedly # and repatedly only
168
+ </client>
169
+ CONFIG
170
+ assert_equal ['tagomoris','frsyuki'], p4.nodes[1][:users]
171
+ end
172
+
173
+ def test_configure_secure
174
+ p = nil
175
+ assert_raise(Fluent::ConfigError) { p = create_driver(<<CONFIG).instance }
176
+ type secure_forward
177
+ shared_key secret_string
178
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
179
+ CONFIG
180
+
181
+ assert_raise(Fluent::ConfigError) { p = create_driver(<<CONFIG).instance }
182
+ type secure_forward
183
+ secure true
184
+ shared_key secret_string
185
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
186
+ CONFIG
187
+
188
+ assert_raise(Fluent::ConfigError) { p = create_driver(<<CONFIG).instance }
189
+ type secure_forward
190
+ secure true
191
+ shared_key secret_string
192
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
193
+ ca_cert_path /anywhere/cert/file/does/not/exist
194
+ CONFIG
195
+
196
+ passphrase = "testing secret phrase"
197
+ ca_dir = File.join(Dir.pwd, "test", "tmp", "cadir")
198
+ unless File.exist?(File.join(ca_dir, 'ca_cert.pem'))
199
+ FileUtils.mkdir_p(ca_dir)
200
+ opt = {
201
+ private_key_length: 2048,
202
+ cert_country: 'US',
203
+ cert_state: 'CA',
204
+ cert_locality: 'Mountain View',
205
+ cert_common_name: 'SecureForward CA',
206
+ }
207
+ cert, key = Fluent::SecureForward::CertUtil.generate_ca_pair(opt)
208
+ key_data = key.export(OpenSSL::Cipher::Cipher.new('aes256'), passphrase)
209
+ File.open(File.join(ca_dir, 'ca_key.pem'), 'w') do |file|
210
+ file.write key_data
211
+ end
212
+ File.open(File.join(ca_dir, 'ca_cert.pem'), 'w') do |file|
213
+ file.write cert.to_pem
214
+ end
215
+ end
216
+
217
+ assert_raise(OpenSSL::PKey::RSAError) { p = create_driver(<<CONFIG).instance }
218
+ type secure_forward
219
+ secure true
220
+ shared_key secret_string
221
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
222
+ ca_cert_path #{ca_dir}/ca_cert.pem
223
+ ca_private_key_path #{ca_dir}/ca_key.pem
224
+ ca_private_key_passphrase wrong phrase
225
+ CONFIG
226
+
227
+ assert_nothing_raised { p = create_driver(<<CONFIG).instance }
228
+ type secure_forward
229
+ secure true
230
+ shared_key secret_string
231
+ self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
232
+ ca_cert_path #{ca_dir}/ca_cert.pem
233
+ ca_private_key_path #{ca_dir}/ca_key.pem
234
+ ca_private_key_passphrase testing secret phrase
235
+ CONFIG
236
+ end
237
+ 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
@@ -0,0 +1,147 @@
1
+ require 'helper'
2
+
3
+ class SecureForwardOutputTest < Test::Unit::TestCase
4
+ CONFIG = %[
5
+ ]
6
+
7
+ def setup
8
+ Fluent::Test.setup
9
+ end
10
+
11
+ def create_driver(conf=CONFIG,tag='test')
12
+ Fluent::Test::OutputTestDriver.new(Fluent::SecureForwardOutput, tag).configure(conf)
13
+ end
14
+
15
+ def test_configure_secondary
16
+ p1 = nil
17
+ assert_nothing_raised { p1 = create_driver(<<CONFIG).instance }
18
+ type secure_forward
19
+ secure no
20
+ shared_key secret_string
21
+ self_hostname client.fqdn.local
22
+ <server>
23
+ host server.fqdn.local # or IP
24
+ # port 24284
25
+ </server>
26
+ <secondary>
27
+ type forward
28
+ <server>
29
+ host localhost
30
+ </server>
31
+ </secondary>
32
+ CONFIG
33
+ end
34
+
35
+ def test_configure_standby_server
36
+ p1 = nil
37
+ assert_nothing_raised { p1 = create_driver(<<CONFIG).instance }
38
+ type secure_forward
39
+ secure no
40
+ shared_key secret_string
41
+ self_hostname client.fqdn.local
42
+ keepalive 1m
43
+ <server>
44
+ host server1.fqdn.local
45
+ </server>
46
+ <server>
47
+ host server2.fqdn.local
48
+ hostlabel server2
49
+ </server>
50
+ <server>
51
+ host server1.fqdn.local
52
+ hostlabel server1
53
+ port 24285
54
+ shared_key secret_string_more
55
+ standby
56
+ </server>
57
+ CONFIG
58
+ assert_equal 3, p1.servers.size
59
+ assert_equal 3, p1.nodes.size
60
+
61
+ assert_equal 'server1.fqdn.local', p1.nodes[0].host
62
+ assert_equal 'server1.fqdn.local', p1.nodes[0].hostlabel
63
+ assert_equal 24284, p1.nodes[0].port
64
+ assert_equal false, p1.nodes[0].standby
65
+ assert_equal 'secret_string', p1.nodes[0].shared_key
66
+ assert_equal 60, p1.nodes[0].keepalive
67
+
68
+ assert_equal 'server2.fqdn.local', p1.nodes[1].host
69
+ assert_equal 'server2', p1.nodes[1].hostlabel
70
+ assert_equal 24284, p1.nodes[1].port
71
+ assert_equal false, p1.nodes[1].standby
72
+ assert_equal 'secret_string', p1.nodes[1].shared_key
73
+ assert_equal 60, p1.nodes[1].keepalive
74
+
75
+ assert_equal 'server1.fqdn.local', p1.nodes[2].host
76
+ assert_equal 'server1', p1.nodes[2].hostlabel
77
+ assert_equal 24285, p1.nodes[2].port
78
+ assert_equal true, p1.nodes[2].standby
79
+ assert_equal 'secret_string_more', p1.nodes[2].shared_key
80
+ assert_equal 60, p1.nodes[2].keepalive
81
+ end
82
+
83
+ def test_configure_standby_server2
84
+ p1 = nil
85
+ assert_nothing_raised { p1 = create_driver(<<CONFIG).instance }
86
+ type secure_forward
87
+ secure no
88
+ shared_key secret_string
89
+ self_hostname client.fqdn.local
90
+ num_threads 3
91
+ <server>
92
+ host server1.fqdn.local
93
+ </server>
94
+ <server>
95
+ host server2.fqdn.local
96
+ </server>
97
+ <server>
98
+ host server3.fqdn.local
99
+ standby
100
+ </server>
101
+ CONFIG
102
+ assert_equal 3, p1.num_threads
103
+ assert_equal 1, p1.log.logs.select{|line| line =~ /\[warn\]: Too many num_threads for secure-forward:/}.size
104
+ end
105
+
106
+ def test_configure_with_ca_cert
107
+ ca_dir = File.join(Dir.pwd, "test", "tmp", "cadir")
108
+ unless File.exist?(File.join(ca_dir, 'ca_cert.pem'))
109
+ FileUtils.mkdir_p(ca_dir)
110
+ opt = {
111
+ private_key_length: 2048,
112
+ cert_country: 'US',
113
+ cert_state: 'CA',
114
+ cert_locality: 'Mountain View',
115
+ cert_common_name: 'SecureForward CA',
116
+ }
117
+ cert, key = Fluent::SecureForward::CertUtil.generate_ca_pair(opt)
118
+ key_data = key.export(OpenSSL::Cipher::Cipher.new('aes256'), passphrase)
119
+ File.open(File.join(ca_dir, 'ca_key.pem'), 'w') do |file|
120
+ file.write key_data
121
+ end
122
+ File.open(File.join(ca_dir, 'ca_cert.pem'), 'w') do |file|
123
+ file.write cert.to_pem
124
+ end
125
+ end
126
+
127
+ p = nil
128
+ assert_nothing_raised { p = create_driver(<<CONFIG).instance }
129
+ type secure_forward
130
+ secure yes
131
+ ca_cert_path #{ca_dir}/ca_cert.pem
132
+ shared_key secret_string
133
+ self_hostname client.fqdn.local
134
+ num_threads 3
135
+ <server>
136
+ host server1.fqdn.local
137
+ </server>
138
+ <server>
139
+ host server2.fqdn.local
140
+ </server>
141
+ <server>
142
+ host server3.fqdn.local
143
+ standby
144
+ </server>
145
+ CONFIG
146
+ end
147
+ end