├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
└── secure-forward-ca-generate
├── example
├── auth_client.conf
├── auth_server.conf
├── cacerts1
│ ├── ca_cert.pem
│ └── ca_key.pem
├── cacerts2
│ ├── ca_cert.pem
│ └── ca_key.pem
├── cert_c.conf
├── cert_client.conf
├── cert_copy_client.conf
├── cert_copy_server_a.conf
├── cert_copy_server_b.conf
├── cert_i.conf
├── cert_server.conf
├── certs
│ ├── cert-with-intermediate.pem
│ ├── cert.pem
│ ├── key-for-with-intermediate.key
│ ├── key.pem
│ ├── root.pem
│ ├── testing-intermediate.pem
│ └── testing-server.pem
├── client.conf
├── client_proxy.conf
├── insecure_client.conf
├── insecure_server.conf
└── server.conf
├── fluent-plugin-secure-forward.gemspec
├── lib
└── fluent
│ └── plugin
│ ├── in_secure_forward.rb
│ ├── input_session.rb
│ ├── openssl_util.rb
│ ├── out_secure_forward.rb
│ ├── output_node.rb
│ └── secure_forward
│ └── cert_util.rb
└── test
├── helper.rb
└── plugin
├── test_in_secure_forward.rb
├── test_input_session.rb
└── test_out_secure_forward.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | # For TextMate, emacs, vim
19 | *.tmproj
20 | tmtags
21 | *~
22 | \#*
23 | .\#*
24 | *.swp
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.1.10
4 | - 2.2.5
5 | - 2.3.1
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in fluent-plugin-secure-forward.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012- TAGOMORI Satoshi
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fluent-plugin-secure-forward
2 |
3 | [Fluentd](http://fluentd.org) input/output plugin to forward fluentd messages over SSL with authentication.
4 |
5 | ## Plugin status
6 |
7 | **NOTE: This plugin will not be updated anymore.**
8 |
9 | [Fluentd v0.14.12](http://www.fluentd.org/blog/fluentd-v0.14.12-has-been-released) supports event forwarding via encrypted network communication. Use that feature instead of using this plugin.
10 |
11 | ## Overview
12 |
13 | This plugin makes you to be able to:
14 |
15 | * protect your data from others in transferring with SSL
16 | * with certificate signed and registered correctly/publicly
17 | * with private CA certificates generated by users
18 | * with automatically generated and self-signed certificates **in vulnerable way**
19 | * authenticate by shared\_key check from both of client(out\_secure\_forward) and server(in\_secure\_forward)
20 | * authenticate with username / password pairs
21 |
22 | ## Installation
23 | install with gem or fluent-gem command as:
24 |
25 | ```
26 | ### native gem
27 | $ gem install fluent-plugin-secure-forward
28 |
29 | ### fluentd gem
30 | $ fluent-gem install fluent-plugin-secure-forward
31 | ```
32 |
33 | ### Using SSL certificates issued from trusted CA
34 |
35 | To communicate over SSL with valid certificate issued from public CA, configure params below for input plugin:
36 |
37 | * `secure`: set `yes` or `true`
38 | * `cert_path`: set path of certificate file issued from CA
39 | * `private_key_path`: set path of private key file
40 | * `private_key_passphrase`: set passphrase of private key
41 |
42 | ```apache
43 |
44 | @type secure_forward
45 |
46 | # bind 0.0.0.0 # default
47 | # port 24284 # default
48 | self_hostname server.fqdn.example.com
49 | shared_key secret_string
50 |
51 | secure yes
52 |
53 | cert_path /path/for/certificate/cert.pem
54 | private_key_path /path/for/certificate/key.pem
55 | private_key_passphrase secret_foo_bar_baz
56 |
57 | ```
58 |
59 | For output plugin, specify just 2 options below:
60 |
61 | * `secure`: set `yes` or `true`
62 | * `enable_strict_verification`: specify `yes` or `true` to verify FQDN of servers (input plugin)
63 |
64 | ```apache
65 |
66 | @type secure_forward
67 |
68 | self_hostname client.fqdn.local
69 | shared_key secret_string
70 |
71 | secure yes
72 | enable_strict_verification yes
73 |
74 |
75 | host server.fqdn.example.com # or IP
76 | # port 24284
77 |
78 |
79 | host 203.0.113.8 # ip address to connect
80 | hostlabel server.fqdn.example.com # specify hostlabel for FQDN verification if ipaddress is used for host
81 |
82 |
83 | ```
84 |
85 | ### Using private CA file and key
86 |
87 | This plugin has a simple utility command to generate private CA cert/key files just for secure-forward.
88 |
89 | ```
90 | $ secure-forward-ca-generate /path/for/dir/of/certs "passphrase for private CA secret key"
91 | ```
92 |
93 | This command generates `ca_cert.pem` and `ca_key.pem` on `/path/for/dir/of/certs`. For SSL communication with private CA, users must deploy both files for input plugins, and also must deploy `ca_cert.pem` for output plugins.
94 | And then, configure Fluentd with these files and the passphrase. With this configuration, server certificates are automatically generated and issued by private CA.
95 |
96 | ```apache
97 |
98 | @type secure_forward
99 |
100 | # bind 0.0.0.0 # default
101 | # port 24284 # default
102 | self_hostname myserver.local
103 | shared_key secret_string
104 |
105 | secure yes
106 |
107 | ca_cert_path /path/for/certificate/ca_cert.pem
108 | ca_private_key_path /path/for/certificate/ca_key.pem
109 | ca_private_key_passphrase passphrase for private CA secret key
110 |
111 | ```
112 |
113 | For output plugin, specify just 2 options below:
114 |
115 | * `secure`: set `yes` or `true`
116 | * `enable_strict_verification`: specify `yes` or `true`
117 |
118 | ```apache
119 |
120 | @type secure_forward
121 |
122 | self_hostname myclient.local
123 | shared_key secret_string
124 |
125 | secure yes
126 | ca_cert_path /path/for/certificate/ca_cert.pem
127 | # enable_strict_verification yes
128 |
129 |
130 | host server.fqdn.example.com # or IP
131 | # port 24284
132 |
133 |
134 | host 203.0.113.8 # ip address to connect
135 | hostlabel server.fqdn.example.com # specify hostlabel for FQDN verification if ipaddress is used for host
136 |
137 |
138 | ```
139 |
140 | ### Using insecure self-signed certificates
141 |
142 | **This is very dangerous and vulnerable to man-in-the-middle attacks**
143 |
144 | For just testing or data center internal communications, this plugin has a feature to communicate without any verification of certificates. Turn `secure` option to `false` to use this feature.
145 |
146 | ```apache
147 |
148 | @type secure_forward
149 |
150 | self_hostname myserver.local
151 | shared_key secret_string
152 |
153 | secure no
154 |
155 | ```
156 |
157 | Configure output plugin just same way:
158 |
159 | ```apache
160 |
161 | @type secure_forward
162 |
163 | self_hostname myclient.local
164 | shared_key secret_string
165 |
166 | secure no
167 |
168 |
169 | host server.fqdn.example.com # or IP
170 |
171 |
172 | ```
173 |
174 | In this mode, output plugin cannot verify peer node of connections. Man-in-the-middle attackers can spoof messages from output plugins under many various situations.
175 |
176 | ## Configuration
177 |
178 | ### SecureForwardInput
179 |
180 | Default settings:
181 | * listen 0.0.0.0:24284
182 | * `bind 192.168.0.101`
183 | * `port 24284`
184 | * allow to accept from any sources
185 | * allow to connect without authentications
186 | * use certificate automatically generated
187 | * `generate_private_key_length 2048`
188 | * `generate_cert_country US`
189 | * `generate_cert_state CA`
190 | * `generate_cert_locality Mountain View`
191 | * `generate_cert_common_name SAME_WITH_SELF_HOSTNAME_PARAMETER`
192 | * use TLSv1.2
193 |
194 | Minimal configurations like below:
195 |
196 | ```apache
197 |
198 | @type secure_forward
199 | shared_key secret_string
200 | self_hostname server.fqdn.local # This fqdn is used as CN (Common Name) of certificates
201 |
202 | secure yes
203 | # and configurations for certs
204 |
205 | ```
206 |
207 | To check username/password from clients, like this:
208 |
209 | ```apache
210 |
211 | @type secure_forward
212 | shared_key secret_string
213 | self_hostname server.fqdn.local
214 |
215 | secure yes
216 | # and configurations for certs
217 |
218 | authentication yes # Deny clients without valid username/password
219 |
220 | username tagomoris
221 | password foobar012
222 |
223 |
224 | username frsyuki
225 | password yakiniku
226 |
227 |
228 | ```
229 |
230 | To deny unknown source IP/hosts:
231 |
232 | ```apache
233 |
234 | @type secure_forward
235 | shared_key secret_string
236 | self_hostname server.fqdn.local
237 |
238 | secure yes
239 | # and configurations for certs
240 |
241 | allow_anonymous_source no # Allow to accept from nodes of
242 |
243 | host 192.168.10.30
244 |
245 |
246 | host your.host.fqdn.local
247 | # wildcard (ex: *.host.fqdn.local) NOT Supported now
248 |
249 |
250 | network 192.168.16.0/24 # network address specification
251 |
252 |
253 | ```
254 |
255 | You can use both of username/password check and client check:
256 |
257 | ```apache
258 |
259 | @type secure_forward
260 | shared_key secret_string
261 | self_hostname server.fqdn.local
262 |
263 | secure yes
264 | # and configurations for certs
265 |
266 | allow_anonymous_source no # Allow to accept from nodes of
267 | authentication yes # Deny clients without valid username/password
268 |
269 | username tagomoris
270 | password foobar012
271 |
272 |
273 | username frsyuki
274 | password sukiyaki
275 |
276 |
277 | username repeatedly
278 | password sushi
279 |
280 |
281 | host 192.168.10.30 # allow all users to connect from 192.168.10.30
282 |
283 |
284 | host 192.168.10.31
285 | users tagomoris,frsyuki # deny repeatedly from 192.168.10.31
286 |
287 |
288 | host 192.168.10.32
289 | shared_key less_secret_string # limited shared_key for 192.168.10.32
290 | users repeatedly # and repatedly only
291 |
292 |
293 | ```
294 |
295 | ### SecureForwardOutput
296 |
297 | Minimal configurations like this:
298 |
299 | ```apache
300 |
301 | @type secure_forward
302 | shared_key secret_string
303 | self_hostname client.fqdn.local
304 |
305 | secure yes
306 | # and configurations for certs/verification
307 |
308 |
309 | host server.fqdn.local # or IP
310 | # port 24284
311 |
312 |
313 | ```
314 |
315 | Without hostname ACL (and it's not implemented yet), `self_hostname` is not checked in any state. `${hostname}` placeholder is available for such cases.
316 |
317 | ```apache
318 |
319 | @type secure_forward
320 | shared_key secret_string
321 | self_hostname ${hostname}
322 |
323 | secure yes
324 | # and configurations for certs/verification
325 |
326 |
327 | host server.fqdn.local # or IP
328 | # port 24284
329 |
330 |
331 | ```
332 |
333 | When specified 2 or more ``, this plugin uses these nodes in simple round-robin order. And servers with `standby yes` will be selected until all of non-standby servers goes down.
334 |
335 | If server requires username/password, set `username` and `password` in `` section:
336 |
337 | ```apache
338 |
339 | @type secure_forward
340 | shared_key secret_string
341 | self_hostname client.fqdn.local
342 |
343 | secure yes
344 | # and configurations for certs/verification
345 |
346 |
347 | host first.fqdn.local
348 | hostlabel server.fqdn.local
349 | username repeatedly
350 | password sushi
351 |
352 |
353 | host second.fqdn.local
354 | hostlabel server.fqdn.local
355 | username sasatatsu
356 | password karaage
357 |
358 |
359 | host standby.fqdn.local
360 | hostlabel server.fqdn.local
361 | username kzk
362 | password hawaii
363 | standby yes
364 |
365 |
366 | ```
367 |
368 | Specify `hostlabel` if server (`in_forward`) have different hostname (`self_host` configuration of `in_forward`) from DNS name (`first.fqdn.local`, `second.fqdn.local` or `standby.fqdn.local`). This configuration variable will be used to check common name (CN) of certifications.
369 |
370 | To specify keepalive timeouts, use `keepalive` configuration with seconds. SSL connection will be disconnected and re-connected for each 1 hour with configuration below. In Default (and with `keepalive 0`), connections will not be disconnected without any communication troubles. (This feature is for dns name updates, and SSL common key refreshing.)
371 |
372 | ```apache
373 |
374 | @type secure_forward
375 | shared_key secret_string
376 | self_hostname client.fqdn.local
377 |
378 | secure yes
379 | # and configurations for certs/verification
380 |
381 | keepalive 3600
382 |
383 | host server.fqdn.local # or IP
384 | # port 24284
385 |
386 |
387 | ```
388 |
389 |
390 | If you connect via Proxy,
391 | set for `proxy_uri` in `` section:
392 | ```apache
393 |
394 | @type secure_forward
395 | shared_key secret_string
396 | self_hostname client.fqdn.local
397 |
398 | secure yes
399 | # and configurations for certs/verification
400 |
401 |
402 | host server.fqdn.local # or IP
403 | # port 24284
404 | proxy_uri http://foo.bar.local:3128
405 |
406 |
407 | ```
408 |
409 | ## Scenario (developer document)
410 |
411 | * server
412 | * in\_secure\_forward
413 | * client
414 | * out\_secure\_forward
415 |
416 | ### Handshake
417 |
418 | 1. (client) connect to server
419 | * on SSL socket handshake, checks certificate and its significate (in client)
420 | 2. (server)
421 | * check network/domain acl (if enabled)
422 | * check client dns reverse lookup result (if enabled)
423 | * disconnect when failed
424 | 3. (server) send HELO
425 | * ['HELO', options(hash)]
426 | * options:
427 | * nonce: string as nonce: used for shared key digest (required, v0.3.2 or later)
428 | * auth: string or blank\_string (string: authentication required, and its salt is this value)
429 | * keepalive: bool (allowed or not)
430 | 4. (client) send PING
431 | * ['PING', selfhostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + selfhostname + nonce + sharedkey), username || '', sha512\_hex(auth\_salt + username + password) || '']
432 | 5. (server) check PING
433 | * check sharedkey
434 | * check username / password (if required)
435 | * send PONG FAILURE if failed
436 | * ['PONG', false, 'reason of authentication failure', '', '']
437 | 6. (server) send PONG
438 | * ['PONG', bool(authentication result), 'reason if authentication failed', selfhostname, sha512\_hex(salt + selfhostname + nonce + sharedkey)]
439 | 7. (client) check PONG
440 | * check sharedkey
441 | * disconnect when failed
442 | 8. connection established
443 | * send data from client (until keepalive expiration)
444 |
445 | ### Data transferring
446 |
447 | CONSIDER RETURN ACK OR NOT
448 |
449 | * Current version has no ACKs
450 | * only supports burst transferring (same as ForwardInput/Output)
451 | * ack for each message ?
452 | * pipeline mode and one-by-one mode ?
453 | * data sequence number in keepalive session ?
454 |
455 | ## TODO
456 |
457 | * ACK mode (protocol)
458 | * support disabling keepalive (input/output)
459 | * access control (input plugin)
460 | * network acl / domain acl
461 | * check connecting source ip and its dns reverse lookup result (for domaian acl)
462 | * access deny on accept (against DoS)
463 | * pluggable authentication database (input plugin)
464 | * RDBMS, LDAP, or ...
465 | * Authentication by clients certificate
466 | * TESTS!
467 |
468 | ## Copyright
469 |
470 | * Copyright (c) 2013- TAGOMORI Satoshi (tagomoris)
471 | * License
472 | * Apache License, Version 2.0
473 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | require "bundler/gem_tasks"
3 |
4 | require 'rake/testtask'
5 | Rake::TestTask.new(:test) do |test|
6 | test.libs << 'lib' << 'test'
7 | test.pattern = 'test/**/test_*.rb'
8 | test.verbose = true
9 | end
10 |
11 | task :default => :test
12 |
--------------------------------------------------------------------------------
/bin/secure-forward-ca-generate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'fileutils'
4 | require 'fluent/plugin/secure_forward/cert_util'
5 |
6 | ca_dir, passphrase = ARGV
7 |
8 | unless ca_dir && passphrase
9 | puts 'USAGE: secure-forward-ca-generate DIR_PATH PRIVATE_KEY_PASSPHRASE'
10 | puts ''
11 | exit 0
12 | end
13 |
14 | FileUtils.mkdir_p(ca_dir)
15 |
16 | opt = {
17 | private_key_length: 2048,
18 | cert_country: 'US',
19 | cert_state: 'CA',
20 | cert_locality: 'Mountain View',
21 | cert_common_name: 'SecureForward CA',
22 | }
23 | cert, key = Fluent::SecureForward::CertUtil.generate_ca_pair(opt)
24 |
25 | key_data = key.export(OpenSSL::Cipher.new('aes256'), passphrase)
26 | File.open(File.join(ca_dir, 'ca_key.pem'), 'w') do |file|
27 | file.write key_data
28 | end
29 | File.open(File.join(ca_dir, 'ca_cert.pem'), 'w') do |file|
30 | file.write cert.to_pem
31 | end
32 |
33 | puts "successfully generated: ca_key.pem, ca_cert.pem"
34 | puts "copy and use ca_cert.pem to client(out_secure_forward)"
35 |
--------------------------------------------------------------------------------
/example/auth_client.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | self_hostname auth-client.local
8 | secure no
9 | shared_key hogeposxxx0
10 |
11 | host localhost
12 | shared_key hogeposxxx1
13 | username tagomoris
14 | password 001122
15 |
16 | flush_interval 1s
17 |
18 |
--------------------------------------------------------------------------------
/example/auth_server.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | self_hostname server
4 | secure no
5 | shared_key hogeposxxx0
6 | cert_auto_generate yes
7 | allow_anonymous_source no
8 | authentication yes
9 |
10 | username tagomoris
11 | password 001122
12 |
13 |
14 | username sugomoris
15 | password 012345
16 |
17 |
18 | username tagomoris
19 | password XXYYZZ
20 |
21 |
22 | host 127.0.0.1
23 | users tagomoris
24 | shared_key hogeposxxx1
25 |
26 |
27 |
28 |
29 | @type stdout
30 |
31 |
--------------------------------------------------------------------------------
/example/cacerts1/ca_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDIDCCAggCAQEwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMxCzAJBgNV
3 | BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRkwFwYDVQQDDBBTZWN1cmVG
4 | b3J3YXJkIENBMB4XDTcwMDEwMTAwMDAwMFoXDTIxMDcyODA0MTczMVowTTELMAkG
5 | A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRkw
6 | FwYDVQQDDBBTZWN1cmVGb3J3YXJkIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
7 | MIIBCgKCAQEA6661Su72owkCqTcIBHI1dTSnUCdRduk/Mzu8x2D8nwQRGPVroRwJ
8 | 5ddqZsBpuKSfoZSZXLvL9d4VpLRxOzrM6+KhldxG5QNRIQTIE2Cw4xMop4nURLrP
9 | 7z1KxM6o1U/lqLSO0GDBfyZS0xhNg8xN7nMObP/YiZYKse5BfLD8kXmhH0DkhOBl
10 | +DPo7Vk8Yhs+930YLzrwOXLOi0w1bfSuTKjIUIxLH7jHiJ7NITH179r+BcyOraG8
11 | thv9QsRnPfgM0xOwdIEUPVbay7Q4wD6ZBqGHba+U49USdcq7lS86nYJa1Z5/s4SV
12 | lx+Jpnxf4IDKxpP7fh/Rofj8LV/CcHbfAQIDAQABoxAwDjAMBgNVHRMEBTADAQH/
13 | MA0GCSqGSIb3DQEBBQUAA4IBAQDSJRHzhPW4fLzb0PSbRZDdmECYiMjvtktUTZtE
14 | n0ATPOkQME2n6l/5m28rs+25wqhYrELhRVxE1SOBQQCmkUnxuSpI7+KgYJwetl7W
15 | IJZEWjC6R0NK05H44ZCNfDk/kNV1cq1Y78F3VtSfBm4ng6IOMf7NN8t8qyF1UEYT
16 | eZzasoFf1Njxnkg9ry1bCISGoU6swmZlE00h1JFV5xhg8rxDMzQCQ8j3PbH+8C40
17 | jQasBuBIb7z9XUfveeoRBWsPa0wlydYbJJo+i8HgF8Wg+qn6BG03A+1IuzzfgzLf
18 | o/aUGK98gi8JKPJ+GPVGBQqOk5UpT8RcyxdMGm7ZlE/TwXHd
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/example/cacerts1/ca_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: AES-256-CBC,B42AD5E6BF9AB7CCD39BC79D260E73BC
4 |
5 | PpbaxpBrgNeW85QjfZZaU8egw5bow3QX1eqZXeJW5+Kol7vRz9aciOeCVZ56IUfj
6 | 8hivC2g5rHwwCMpipFoX6+Q1H+hfQZCuEgL8Ea7h8VTnHd6fxSQjUiTJdcmIsuPI
7 | 3WaLPQm22mbkrEUOe4mwq5qu0YiKUdF9ExtZoiSKnaV+oHvFrOmOMGY3L8HlscpZ
8 | /qKEKp3bgJbsEPNHHW9VSU2ds8RUcWr9/MwAcAQJUTOpl7o4kAg5mCd/kx5tU2TW
9 | kkt2YBPxkUEZoww5aThjgMVyg4C6hF2jM1nNlaGpHMZ7SuZTJW7dw5T2aQv+s2G6
10 | 6/9LD54PE07/cF7x+RlZ22q0ibPyLzJiu7rKBb5KwNgdnwQCq/c7dJaQsk1M3c5t
11 | mzoTn6JqKmyaWWrELD1EJq6ttcpMxCSb7UTpZxB3zMqsReplPgaOHx3V8Fi0mFup
12 | kmN4p5fMOm8PCSo3eSTyQzOpRyrYtZ24AorLZ1Tu1xAT5xl0S8kLxiulovKBAzS9
13 | h8dfpoCWZfn90I9NigrfKkQ4WxPizZAjwteYuhZ2GYfILz9ctLEcWhYMFzj65ahM
14 | Vo1w8Bb8rQ/sdgJlfu6V8C64b0UVvyacWSbWRHObhcVEeMLId+8cdR1EzhrWNvAb
15 | rpZia9bFxKZTIHuRbGhn7eEelZ4FEXsq97dn3a71pooQPEOUIbTEEI1zd3KaKsu6
16 | AtPm3pMij8AMPfUQA6UGA/5v0xU18fz90UWfjx3EzlOcHXK1iswFXZYJNy3BR4ao
17 | de01Nino5C88YXjuUSFFf75jL1Kw7zgLLGwfPvFYz57R2P9ujZ/0QjkFAq1C2Mti
18 | MaUFbBdy6mqE0vcUnrARpjWKuDr+wTm34miWmbF3WIjZQC3j4Q4zqbIZ28O+5pfP
19 | l12n2bGN6c7lqP7ueaOYXXI+4av/R29A89/9xFJ/cMJlmbfVwYKzLHWI7yRYFmAC
20 | HhUDWqyY/2bX5NF/OQNgWXJOG5mEgq42ygPFpNyF6Z8BeuGPnfwIQDf4X6kz5vP3
21 | a6kigDgs2Ma4AU9ZMiWOGDnUgSBQF00gECEkNV9b5scGyiqAsuvY/QTWDw04v+6E
22 | VI4ctDRkrJHc8Q+rWbFTsr3Za6LxjDo3LDbnWPG7e6tSEjW9fdJLtTvmB8klIa3J
23 | 131YCkCXIKr5gs/AmyH7ccF7UCMuFA55TK1ZMIwwVIHmAmmwV0LZ63Syy8xWRrMk
24 | aieZTLovxJMybCW87X/AXcIlRFAHuy9+V35xrmeq3r1O7PG5aKlWXwSKGTAILQzl
25 | w0pIALqwAtHSXGnOIHtyQeu/AhZhVGEqb8uwa/7LGdchIxW/VOw+jzQFQTd/o/uO
26 | bNcFbs7iDTvOKmVOkAnKUG48ETe12mYiyn4HxHl8pDR2WGBN32VfbRkzKHVYhWcX
27 | xDwzMAfMH73pMGvdpTfZVI8GRcZEuPz0JvieQe4Esu6qHKgR8q9Onkl6RlqT9qVs
28 | 8don37z0MA6ZKja3L56ObeVwRA1C4t1GDNyjB0bL1bMDnShPJSqROvEEhxt8AnHc
29 | XrrAnePUWah2DXqYxKKwahfdjdQjPX+kU+vy++izEj5QdFF84KOZJAeTYtRxTmlf
30 | -----END RSA PRIVATE KEY-----
31 |
--------------------------------------------------------------------------------
/example/cacerts2/ca_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDIDCCAggCAQEwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMxCzAJBgNV
3 | BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRkwFwYDVQQDDBBTZWN1cmVG
4 | b3J3YXJkIENBMB4XDTcwMDEwMTAwMDAwMFoXDTIxMDcyODA0MzI1MlowTTELMAkG
5 | A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRkw
6 | FwYDVQQDDBBTZWN1cmVGb3J3YXJkIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
7 | MIIBCgKCAQEAtBX3hMotHdbUuIy9jnmHHUI2Pcn+hQnZzDw77ynEAtaFMchSTTeB
8 | etMl7FkaGYpEZSXEfCZmzZgbhTlh2Dq9TinqN3QqYij/zH0VN1jRjAWiHMSsz5E0
9 | cLvpLTm2oIrvJToMulAF2duH2hvPsnbBLV7Bm5sfzxSoMD6UM+yjkSyjSq9RlM6g
10 | QS3BsJc2+OOFzSpHw+h/H/xaqQPYscU9a4SWGsJKoP/il4dM8DZTiZUZW/3LD45C
11 | 0J/t/qjbrWUhAnHa1iCVN9UYiq+AhBq+luOR6ZXQ847YFsjF9IL3SNrkFSI1cjl5
12 | 6l3DxsuGSkWCMT+mUfr+W0BSnq1ShElOdQIDAQABoxAwDjAMBgNVHRMEBTADAQH/
13 | MA0GCSqGSIb3DQEBBQUAA4IBAQBr0Vt8xiic10D+zaJZebbGXn5zvkKPz9YgEJGj
14 | u37CPmlf46rk3Bpmm64QYzbliRUMfJ0uQc66k/0qvNgfokahHmV6QOsk7LpAFBKC
15 | pxyEH6w7iADP8IO+rMmnTrOGGIarjFOCkTNyR5TPHPQTIKY3Gf/yLIXguXEG7mKH
16 | 40EJbN7KhxywO/oKW8a02Quv2vVQQjXBRLejuxK+JJvDzxRQoTFsvYtL5uJMwR5m
17 | IkGAdtMOwoqz4pY+mFnifjwpKy1llIk47RkbLx3uVb0y+OrWxh+KmF9sHuvnFDh7
18 | 1vctdqOHQWc2RlprPO9Yxb7sBIVktWaOyU2JTjDmaS5BvXZu
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/example/cacerts2/ca_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: AES-256-CBC,B40E264210E818183F1296FDE1900ABA
4 |
5 | tPAdiPpZx0kEVsd1P668AH/lpGzc7YsLiy8hwUuleW39bi3aF+MC2f5oVhp8l3+B
6 | AT5LWsAsPooUX2lR2hGqcBsxr/u4bOtTTDRQjmqONWr1RdcTf10uM013mU6w/K4e
7 | qlj3+JiR1foBHmVyz47N+U93S5WhtSvfw5767QTnuNVP2N+ZYoVfloLz4ph+gcn4
8 | k9HPM0bsQMlXikFC3GU4P7Jw8vQPaiv7Jp34GOfX8kSiYvZJ/IhHe8RCytgPHDTr
9 | 1lB7NNBC3ez0EkqGswPwRIYVbn2cR2bOzOdj12IROszdn8xK4iqOlZ7J/n30+Wq6
10 | KTBPF7NC04ie6cIfyr3gpYU5QiM5W+No5vYb8XjqryJSfyXtV0cf2vKlHChwBM/1
11 | HKe4p8JB3tGZ6oY3PWPl5z3h34t5H/c33DAhaCbAtJow1HUqgyGsscDKBnyBRTbx
12 | 16bEZh533E0R2fvlAQNkAi2fQhJ12HV1mlZT3iY9cV2mfTQMxwfryqm8NmZpDaWB
13 | 5qqJxlAg5fhRCLNbPz4ln3AAwyafR9e+C04kr1CqvxXKDQkPMBvlEMpMVYrl/ZVc
14 | wSqRuYChpMENk9tPEpqP9tnF4nIuWo4lJ2SxuYQa9Rf/RkTzulwMP1m8Vfn4F6EZ
15 | N66cf9iAnqlgaMWn/m2LnZO4OXkNUfQSvcZT0YjfP5RwOVs8reeSCVNjZyJyCHGS
16 | ROXAATGoJiSSaJYQgc1qmIQzJskMwAqZFKjM++gmD7Ba3gSmosmjKG2BJZmQcfBR
17 | ostQmS3yebsNE7RZRtI1oxI50QdZ5X6a8MEXRZTa0sHGppCXcSQ45IO7Ag1snNsa
18 | SfFeFOHwVv8RzKRLDSQBYK706onY2UuBTAuM+qpcOaO24H5sWB3MWICqGZde62Ff
19 | vBpo7CJfDbyR/jH/JtxbRL2qTDLRMccQlcEt5HIrnfkhE9boh6USSqtiINNe8R8R
20 | xtVYkCDuq1S7o2saGF40PHYPQMICV3e4I2/r44YGFAasbsAn2eBibpkWErFFaPfl
21 | lLUGTTKfFJCDTV1qikxq1StKBMOimPQLhn/KP+YNUzDeP15KABYhcgvRBhpcxZfw
22 | CLnTC+BTOddTd1A2imRn6q5BaF7EvE3bAnmhq14wK/c7ykMKT//R3vvFD47qhXLF
23 | vvv9PdTyxEGeNM4Mu5uwapUWo5gV1+aDqg3UoR0hszEWK60dkC4sTI5DaZbmhRh2
24 | JezoQATQnZ19XeEKQTt6XMi9goTzQ5c63TDGnYlCds/KV3fV5i0cQmSJCdsBtlnW
25 | 8wPY0f/ejgdVU2AaxeWgPi7ivuT1/FU5F/TRNZRBigUH8vyXRRHjFC1S6Zl7d5bA
26 | Fp/Axpr2KxFlVFKzz0lmoYw9pO2mOMd1MQSTmHt81GZleyMpt6Rb0/QCa5dElRv6
27 | 5YSY0jlEVfFomqO/gkuD2FduYrG5pvnFycELwEoLgkdQJUOKGKFWnAe8ZOnMfflZ
28 | 4zODkvxAm0wbAw8PCPRL0l1/hHntt7f5cTKw1NiHwrvjJD8Umi/W4/7AsRZyHw0o
29 | 9OBMQgliw0fKqo9ZY6y+tj+R7SomzWO7+8j6cGjDAJTxwbUwp/jQ9OG0XtxRHt/D
30 | -----END RSA PRIVATE KEY-----
31 |
--------------------------------------------------------------------------------
/example/cert_c.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | secure yes
8 | enable_strict_verification yes
9 | self_hostname client
10 | shared_key norikra2
11 | ca_cert_path "#{Dir.pwd}/example/root.pem"
12 |
13 | host 127.0.0.1
14 | hostlabel testing.fluentd.org
15 |
16 | flush_interval 1s
17 |
18 |
--------------------------------------------------------------------------------
/example/cert_client.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | secure yes
8 | self_hostname client
9 | shared_key hogeposxxx0
10 | enable_strict_verification yes
11 |
12 | host 127.0.0.1
13 | hostlabel tagomoris
14 | shared_key hogeposxxx1
15 | username tagomoris
16 | password 001122
17 |
18 | flush_interval 1s
19 |
20 |
--------------------------------------------------------------------------------
/example/cert_copy_client.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type copy
7 |
8 | @type secure_forward
9 | secure yes
10 | self_hostname client
11 | shared_key hogeposxxx0
12 | ca_cert_path "#{Dir.pwd}/example/cacerts1/ca_cert.pem"
13 | enable_strict_verification yes
14 |
15 | host localhost
16 | port 24284
17 | hostlabel server_a.local
18 |
19 | flush_interval 1s
20 |
21 |
22 | @type secure_forward
23 | secure yes
24 | self_hostname client
25 | shared_key hogeposxxx0
26 | ca_cert_path "#{Dir.pwd}/example/cacerts2/ca_cert.pem"
27 | enable_strict_verification yes
28 |
29 | host localhost
30 | port 24285
31 | hostlabel server_a.local
32 |
33 | flush_interval 1s
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/cert_copy_server_a.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | port 24284
4 | secure yes
5 | self_hostname server_a.local
6 | shared_key hogeposxxx0
7 | ca_cert_path "#{Dir.pwd}/example/cacerts1/ca_cert.pem"
8 | ca_private_key_path "#{Dir.pwd}/example/cacerts1/ca_key.pem"
9 | ca_private_key_passphrase "my secret"
10 | allow_anonymous_source yes
11 | authentication no
12 |
13 |
14 |
15 | @type stdout
16 |
17 |
--------------------------------------------------------------------------------
/example/cert_copy_server_b.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | port 24285
4 | secure yes
5 | self_hostname server_a.local
6 | shared_key hogeposxxx0
7 | ca_cert_path "#{Dir.pwd}/example/cacerts2/ca_cert.pem"
8 | ca_private_key_path "#{Dir.pwd}/example/cacerts2/ca_key.pem"
9 | ca_private_key_passphrase "my secret 2"
10 | allow_anonymous_source yes
11 | authentication no
12 |
13 |
14 |
15 | @type stdout
16 |
17 |
--------------------------------------------------------------------------------
/example/cert_i.conf:
--------------------------------------------------------------------------------
1 |
2 | # To check SSL certificate informations
3 | # openssl s_client -connect testing.fluentd.org:24284 -showcerts
4 |
5 |
6 | @type secure_forward
7 | secure yes
8 | self_hostname testing.fluentd.org
9 | shared_key norikra2
10 | cert_path "#{Dir.pwd}/example/certs/cert-with-intermediate.pem"
11 | private_key_path "#{Dir.pwd}/example/certs/key-for-with-intermediate.key"
12 | private_key_passphrase norikra2
13 | authentication no
14 |
15 |
16 |
17 | @type stdout
18 |
19 |
--------------------------------------------------------------------------------
/example/cert_server.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | secure yes
4 | self_hostname tagomoris
5 | shared_key hogeposxxx0
6 | cert_path "#{Dir.pwd}/example/certs/cert.pem"
7 | private_key_path "#{Dir.pwd}/example/certs/key.pem"
8 | private_key_passphrase # blank passphrase
9 | allow_anonymous_source no
10 | authentication yes
11 |
12 | username tagomoris
13 | password 001122
14 |
15 |
16 | username sugomoris
17 | password 012345
18 |
19 |
20 | username tagomoris
21 | password XXYYZZ
22 |
23 |
24 | host 127.0.0.1
25 | users tagomoris
26 | shared_key hogeposxxx1
27 |
28 |
29 |
30 |
31 | @type stdout
32 |
33 |
--------------------------------------------------------------------------------
/example/certs/cert-with-intermediate.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFRTCCBC2gAwIBAgIQVMhYFMdnctqh7wAXLx1HjzANBgkqhkiG9w0BAQsFADCBvDELMAkGA1UE
3 | BhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQ
4 | dXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjE7MDkGA1UECxMyVGVybXMgb2YgdXNlIGF0IGh0
5 | dHBzOi8vd3d3LnN5bWF1dGguY29tL2Nwcy90ZXN0Y2ExIDAeBgNVBAMTF1RyaWFsIFNTTCBKYXBh
6 | biBDQSAtIEcyMB4XDTE1MTEyNDAwMDAwMFoXDTE1MTIwODIzNTk1OVowgdAxCzAJBgNVBAYTAkpQ
7 | MQ4wDAYDVQQIDAVUb2t5bzETMBEGA1UEBwwKQ2hpeW9kYS1LdTEcMBoGA1UECgwTVHJlYXN1cmUg
8 | RGF0YSwgSy5LLjEYMBYGA1UECwwPT3BlblNvdXJjZSBUZWFtMSwwKgYDVQQLDCNodHRwczovL3d3
9 | dy5zeW1hbnRlYy5jb20vY3BzL3Rlc3RjYTEYMBYGA1UECwwPcjQ1MDExNTExNTE4MzgyMRwwGgYD
10 | VQQDDBN0ZXN0aW5nLmZsdWVudGQub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
11 | pz2pcz/cGXSvEYMCXZLyLj8BXQvPRPfQIITPzCWpr+jbr5oVI2D3FqysJLedLRZHWY3wAmp/AHqS
12 | WIHfaGUr6TmLVaKM2weFfxjKtwHboVYSHrhL5bth6fm8gMzJrB6UbFyfUXRPlZw6z/WxcMezIoHp
13 | 6KSS8Ao9ixeql1AFKF4Yc8H6xUH0fLLNOBLP7UxIbg7xZNxqcjlULOgKcMojK2YioI98Yq9nJ5To
14 | VopopIgtPEqXzN7W9EdrtaHelky9fsXLY3YnZ599ujEmHMMOzKbrTg7OMOyKwyhFdyERsGfNOHJ1
15 | /WdgCZZXRlfc0s2SxEWttaJ8v9D18Va1w4cR8wIDAQABo4IBKzCCAScwCQYDVR0TBAIwADAOBgNV
16 | HQ8BAf8EBAMCBaAwXgYDVR0fBFcwVTBToFGgT4ZNaHR0cDovL29uc2l0ZWNybC5zeW1hdXRoLmpw
17 | L1N5bWFudGVjSmFwYW5JbmNUcmlhbFNTTEphcGFuQ0FzaGEyL0xhdGVzdENSTC5jcmwwHgYDVR0R
18 | BBcwFYITdGVzdGluZy5mbHVlbnRkLm9yZzBKBgNVHSAEQzBBMD8GCmCGSAGG+EUBBxUwMTAvBggr
19 | BgEFBQcCARYjaHR0cHM6Ly93d3cuc3ltYW50ZWMuY29tL2Nwcy90ZXN0Y2EwHQYDVR0lBBYwFAYI
20 | KwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFN0fIWQuRcLgQDlIvuPLhICXwySKMA0GCSqG
21 | SIb3DQEBCwUAA4IBAQAU6rXvUcybWiyHfIGSlzy4MYO6HBeaF0tc2Pt0T4ByX+smXvKoVVgZbX8E
22 | ahlZURfjSwZyXHCpMvcH17/IyW7c+HrX/jheMZ7iyYdvCQnZ2GrT8Zr3GRMcNYf8e8wnXwkBFAPj
23 | yek5XHF5ShwLv64bOWDjAfdIAUCHEo1PpDz6JPQgJEb+SHqrxWx+O0zmELYUgSvRWKBuEC5TrxGU
24 | HEib235vmz6/TgDUqfci7RPf16cwqVyEilzD5tLXTuMp4+tkPRjEA5d+EzH5O2Lx/ef5RTnYYMZ0
25 | zsVBXssRtwcFKO2vStIGitRK9G9mbvzMNKuItaHcmw8GOhyXnMjTpl8u
26 | -----END CERTIFICATE-----
27 | -----BEGIN CERTIFICATE-----
28 | MIIFRjCCBC6gAwIBAgIQdV9MvP3+ENU3kdfSg2vm/DANBgkqhkiG9w0BAQsFADCB
29 | hTELMAkGA1UEBhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8w
30 | LQYDVQQLEyZGb3IgVGVzdCBQdXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjEm
31 | MCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBKYXBhbiBSb290IC0gRzUwHhcNMTUwMjE4
32 | MDAwMDAwWhcNMjUwMjE3MjM1OTU5WjCBvDELMAkGA1UEBhMCSlAxHTAbBgNVBAoT
33 | FFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQdXJwb3Nl
34 | cyBPbmx5LiBObyBhc3N1cmFuY2VzLjE7MDkGA1UECxMyVGVybXMgb2YgdXNlIGF0
35 | IGh0dHBzOi8vd3d3LnN5bWF1dGguY29tL2Nwcy90ZXN0Y2ExIDAeBgNVBAMTF1Ry
36 | aWFsIFNTTCBKYXBhbiBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
37 | CgKCAQEA20T0ajk8CG7rIu90Zyh4bDAo32nO6RdBN1xjFD054LbjIR9R7dqAXbLx
38 | sYj5+V/7v5tN4ogqyHiK2CKLe3z/rtrFjeei/Xyr5NysNWuY2v/OIRSLpeKYYivr
39 | Ax76ssHbPRdqT4OobxYfRaOEZKwM5etUD55jLI1/0lcSeNqv7Gps8tKt44T4vDx2
40 | E8J0zT9Z102e649si0rUoGbP3flEBwvnkrmLTQDV1Rf9p3QbaAaZgsmEaXshgufJ
41 | Bti/BpOy1Sdg4ZTCcKPb/qhSphn2LZNbBFOH/rQiu4UoWHWWMOcsJm22pARSsELI
42 | ivNJIcXSE8PLTkLWisqRkVODaRaL+wIDAQABo4IBdzCCAXMwEgYDVR0TAQH/BAgw
43 | BgEB/wIBADB7BgNVHSAEdDByMHAGCmCGSAGG+EUBBxUwYjAuBggrBgEFBQcCARYi
44 | aHR0cHM6Ly93d3cuc3ltYXV0aC5jb20vY3BzL3Rlc3RjYTAwBggrBgEFBQcCAjAk
45 | GiJodHRwczovL3d3dy5zeW1hdXRoLmNvbS9jcHMvdGVzdGNhMGUGA1UdHwReMFww
46 | WqBYoFaGVGh0dHA6Ly9vbnNpdGVjcmwuc3ltYXV0aC5qcC9BUkwvU3ltYW50ZWNK
47 | YXBhbkluY1RyaWFsQ2xhc3MzSmFwYW5Sb290RzUvTGF0ZXN0QVJMLmNybDAOBgNV
48 | HQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGUzLTIw
49 | NDgtMTc5MB0GA1UdDgQWBBTdHyFkLkXC4EA5SL7jy4SAl8MkijAfBgNVHSMEGDAW
50 | gBRivYsKGrwW4qUaX5DjNlpcExbkETANBgkqhkiG9w0BAQsFAAOCAQEAZbKcrNvS
51 | q/b4KNd4Y41uNEqaCQlL6Uvqs5q6b0HQbK/Hjolt3sJttqT/jtSxGIITZVPH8PRt
52 | 6fo3aWII1eLIiCVzuQc0eVA0671pPhUHgd8mPHgUmhsxLURWgEE3lMx9dKQhKL1V
53 | Fcp/2bQy+dLVZELQl2L3qtRCVEYubLFmoAgBG7nyiwjt+AsvG6AX2N7MLzsF+C8L
54 | vRbWEqglqDCFAt1es1Vfb2a7zFj0/BUYDDo6eQE/BpYiYmbHtoWz6SzBNZsehMb5
55 | OlnF9xqndWK7x2rtaNyi+Z287AzaDe0VFTUaGd1YnzKlEHphRr2nMHd/iNRZFNLy
56 | poe4IWfm9maFow==
57 | -----END CERTIFICATE-----
58 | -----BEGIN CERTIFICATE-----
59 | MIID2DCCAsCgAwIBAgIQGOtH8axDhLeZoyevey+LezANBgkqhkiG9w0BAQUFADCB
60 | hTELMAkGA1UEBhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8w
61 | LQYDVQQLEyZGb3IgVGVzdCBQdXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjEm
62 | MCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBKYXBhbiBSb290IC0gRzUwHhcNMTUwMjE4
63 | MDAwMDAwWhcNMzUwMjE3MjM1OTU5WjCBhTELMAkGA1UEBhMCSlAxHTAbBgNVBAoT
64 | FFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQdXJwb3Nl
65 | cyBPbmx5LiBObyBhc3N1cmFuY2VzLjEmMCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBK
66 | YXBhbiBSb290IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF
67 | xdnFVPR3Azt7ar9rYymmrSxRYinWPrNByp8jSWhWudfgbRi0FGh848rsAgvg51c/
68 | xilEYouj3iRFV0Tt90jWgYQuH6+HRdTz39JGQ8cCGdjo6u0gBUZZMAMJven4nGRk
69 | zF4u4KPh288sZZWL0VZqHIoKQZTgwnr+QFDlthRCQBKLStl1KTQ9WKw8z8VxK19+
70 | v7b/lURmBVANhZgf+cxnvwstO1goxj34B6y5eoJ9DTIwWSFGkARhdiAMrOzSAU8u
71 | h/G1Xcp3JoVX8NNEd5LhHkJKS1idK4PXTP7uXXqOFI5NoL+Q0gNAyJYNtXe8KQ4q
72 | 8L9O9pNaC418OCiiS/4rAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
73 | AQH/BAQDAgEGMB0GA1UdDgQWBBRivYsKGrwW4qUaX5DjNlpcExbkETANBgkqhkiG
74 | 9w0BAQUFAAOCAQEAqB1+wsyBShUOzkAdoFh55x9e1Od7nrnIPv6KKbcycMDv8kf3
75 | 25D5KlgVIW+OWrMKOK7wlRUX7kvF4cENqSCn1W3VVLzCvKTbx34pRR/V8cuUD+2D
76 | 7k1Q1331qFiulycGOl0IlqJCJRi3UPa2fuCDyHikOcbnIApK3Hk2/wGWhool5yzj
77 | 0wUbGcyDg12o1U96bB0tO5ZFIFdYB31skviyiHJojcq4+uRVKA1DrsKoK8ZQx0xf
78 | p6l5H/z5jmFqlBTW8EIW+tdB1NOKCRI6JPwR+NSC8UtZS2M39oyGkGyReoIKjT8K
79 | XFfTm7TDhB9Tf65TF+mw0Rkr101KsoUT5Sm8Dw==
80 | -----END CERTIFICATE-----
81 |
--------------------------------------------------------------------------------
/example/certs/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9TCCAl6gAwIBAgIJAPZkY4lTv8EcMA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
3 | BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEQMA4GA1UEBxMHU2hpYnV5YTEWMBQGA1UE
4 | ChMNRmx1ZW50ZCBKYXBhbjESMBAGA1UEAxMJdGFnb21vcmlzMB4XDTEzMDIxNDA4
5 | MzQ0OVoXDTIzMDIxMjA4MzQ0OVowWzELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRv
6 | a3lvMRAwDgYDVQQHEwdTaGlidXlhMRYwFAYDVQQKEw1GbHVlbnRkIEphcGFuMRIw
7 | EAYDVQQDEwl0YWdvbW9yaXMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPli
8 | bZUddJEJDaPza0dQElKYefGcWyN5f6FHBrv0MU29PW4+9fape3/u6Kal2knXhz7c
9 | ujkyoQgK7pqCOuwpTCi0Fyg2peSLVJm4lw2TS5HP/7qRbKXhx2g3FaHrs/Ug/pbQ
10 | 6xPSy894w2QaXgkeuDLb/bhu8MHulglm/iXg9wHrAgMBAAGjgcAwgb0wHQYDVR0O
11 | BBYEFNWgnetVbxQlGX6euMDea7WGgWO+MIGNBgNVHSMEgYUwgYKAFNWgnetVbxQl
12 | GX6euMDea7WGgWO+oV+kXTBbMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8x
13 | EDAOBgNVBAcTB1NoaWJ1eWExFjAUBgNVBAoTDUZsdWVudGQgSmFwYW4xEjAQBgNV
14 | BAMTCXRhZ29tb3Jpc4IJAPZkY4lTv8EcMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
15 | AQEFBQADgYEAai2UAUa5WAahfUp/UV/7zX7+r/QdUP0fwrrmLzodk+FS3+yS6oqQ
16 | tBs0K81cD3XKfoYjAqzJ1Hul6orR63wD+yrPq3FApuWKd+CJDBxJmY8MtIA0xHHn
17 | nfotL/TzTAEIcFVLYb8yaBA27VMstBHvE4TsbL7mA0avF3FFzxG5GqE=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/example/certs/key-for-with-intermediate.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: DES-EDE3-CBC,FCB585C311CF8314
4 |
5 | l58lb4gVU02qpt3Ejb0SktlphW/ZbZCTquYnY7vRWimBIlXQjSyx7ZJM9b+4zyW7
6 | oQCWUkY4bM6GbdLJ3hf4j2tazY5qTZIEsxf89qro3yjfr9hIMONbfgF4JVI05LP/
7 | vNCyOskHJSkfEnAfL8pAd7kilhwrUa2M5rbOTGoZTDGUShhIRsKx4q/HIcGAQQD5
8 | VNl57idQz1mCQQaDsuFIhVk+A4QifG8QfjjbmbT3J+ZxqWo2UL+c0G56udcJSSSe
9 | XgSjJfclykl0mBQwXyKCe/APntOJm31fZHJ1UCEaShHoMXLjvaCRosIbVnIakXtu
10 | zSkB+XnQqNfYvp7gTIEUzOyvE/uXGk0h6NbYBJpOYEIzlUzXlgq5us3uMpGzzWx9
11 | OG0IE9ePR1sRCuW176TtwgP4TwO0U/oZz8KI2vGSEcNcHNTWqc8LfswXySqEvpYG
12 | ZigqKx/js+7DK+8z15D2oez9GoeXPsUnKNrkJ+kKgo/oDQQSJpcDyRG/fOrH/6Ke
13 | /n74cXtLo5gMxGOezc7W845JABtgtT26RxB2fVw22OThs8Ap1ushJF9hYmdnW4iz
14 | ubWvUd7bXcsDAzig9CEzlGRXms9VNCK+U7aU+rLmI2ipY8dCmJUMkBydFY0Rn9Es
15 | mwGqcT+gabwIuxGRCLJyyQBQclNetrPOUUNSj4Y0KgzZBo6VodthAqb5Au5xGCk5
16 | 9nDke11zjPCu3oDYNOVXTJCgt7Tj+xtGR7xKpdj8rRR8EEy/Yvd4ebupWLJ0zr/v
17 | lhzBdOJ/wuzNX+zd4vw/xCVxx+W3i0hO8NWvHEAhYd3NeOacIM8vXgT6hCGuphu4
18 | M+ggAcsIPwFy52ol50kF0uSfKAUWbaldMFalc4BYt7ZjDAbYe1tY1mefiWF+drSg
19 | PUZ86pZOWc4s3/MCA9bfGYuFzKCQhRDChVt0pMBYfFmxXsvwIVpzgEh5MHE0/5zU
20 | 0fiEXLzn/gkpTDwo1jlYKZ4yK6UiWnqMVCz2cTIa/4wHE2HgBV8APzF4tO5uJZu1
21 | EiKu+qx6jS3c//E32oRmjsCbQBi/D4DNY7uAsNGXfCGvAceDp1Z6Sp5difaelNOM
22 | Af/EsJaO8xSwLvM5Hj3Q8NC+kYT0Kr8oDCTSU4w2x/596QJdtzNB2r3YlWqjWh4D
23 | dMy8jzmVnyAjD3tvzAf/XqMs8e3YHNEh+q1MQaXa2y7Drw6MaoIRVSmXYHwHeJzg
24 | BNU47QOjDYwX0fJgzpq65N4aGzG3wdRR3hKXl0S9Z3CBC6xWaw4Ps2tlspFk4ekN
25 | TODB2jOh+aQQR4npy4RtUKBwIQc2O/VqP0V4D0OdVZr01W4CfBGiiSh5rsve4XxY
26 | KRvyz6LwHVdKKlgyVJCCMcAQv4OI4omPopvpZwBqYAkZQmQQJIh/O0o+BLb8pqOL
27 | /ntJ8y3FF4FIWjtrd58iSOFUpmBw/rLjUNgm86cuOz4RKW8kBtYTWyBym3+DdCzF
28 | mlOobTEN1TqDVA24qQFdFH4F0E917JY0FyWg7JpE2ekLAixBSCm800fgx7Tqy4/m
29 | s0twidyXlnNKA3ejkbDwa1jtqkGfvsD5ELmbNyY5hZFeJsZwpLCJA8AY0hHvquYR
30 | -----END RSA PRIVATE KEY-----
31 |
--------------------------------------------------------------------------------
/example/certs/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXQIBAAKBgQD5Ym2VHXSRCQ2j82tHUBJSmHnxnFsjeX+hRwa79DFNvT1uPvX2
3 | qXt/7uimpdpJ14c+3Lo5MqEICu6agjrsKUwotBcoNqXki1SZuJcNk0uRz/+6kWyl
4 | 4cdoNxWh67P1IP6W0OsT0svPeMNkGl4JHrgy2/24bvDB7pYJZv4l4PcB6wIDAQAB
5 | AoGBAIGvxu7Rl4nI3HgTIQm/wReExX144whKqa2UAxOBBJa5v5VyVnSEZH3+Hqxy
6 | +VaHJ4TwQkN2abmF/dkJulyPiVNmsAEXeYKmNOOnOuvGVYlYgRHGJ0P13oszvtKC
7 | mIFsL4D01FYOHMeblxGhfPQgh4UTcQtIG9gB+yPJ/JJNH7whAkEA/XPV5rxkz/8i
8 | BMgUHxXxv1o4CJf0exJiMjqNViydgnWyOSEGpoABbbxsN/XV2pwaG0Sythz/4AcF
9 | phgCJssNUQJBAPvkIALt96XTB/mlcXap1LC+bleEdiwANpgBlwxp0HlxhBrgyDyJ
10 | iV65FGixi6xIOOjwQbFaLupDC383L8kW3HsCQEjHcX3PTVeY2Kjs1zJR99hNzNdS
11 | 4yZQEhiATcOYDia/K01SWXmIOmDLgXvUQPOEbc60vGilDSjEe2/FZyDCn/ECQQCY
12 | pfLQU64UjAL1Q1Gze9AtG/p6hwemOqrbC3uiRi3UqvpH35j5NtBM2xSHLbFbQpla
13 | cN8ev2xXAzJgce0/i98pAkACvTTdRqRIp/7X24tzXJlageBxXX2vBQF8PZcjdx7C
14 | nVOmUTBuw5JrB34ehYnoWEwMqeyU3CNgUIIgslhcAsVl
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/example/certs/root.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIID2DCCAsCgAwIBAgIQGOtH8axDhLeZoyevey+LezANBgkqhkiG9w0BAQUFADCB
3 | hTELMAkGA1UEBhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8w
4 | LQYDVQQLEyZGb3IgVGVzdCBQdXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjEm
5 | MCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBKYXBhbiBSb290IC0gRzUwHhcNMTUwMjE4
6 | MDAwMDAwWhcNMzUwMjE3MjM1OTU5WjCBhTELMAkGA1UEBhMCSlAxHTAbBgNVBAoT
7 | FFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQdXJwb3Nl
8 | cyBPbmx5LiBObyBhc3N1cmFuY2VzLjEmMCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBK
9 | YXBhbiBSb290IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF
10 | xdnFVPR3Azt7ar9rYymmrSxRYinWPrNByp8jSWhWudfgbRi0FGh848rsAgvg51c/
11 | xilEYouj3iRFV0Tt90jWgYQuH6+HRdTz39JGQ8cCGdjo6u0gBUZZMAMJven4nGRk
12 | zF4u4KPh288sZZWL0VZqHIoKQZTgwnr+QFDlthRCQBKLStl1KTQ9WKw8z8VxK19+
13 | v7b/lURmBVANhZgf+cxnvwstO1goxj34B6y5eoJ9DTIwWSFGkARhdiAMrOzSAU8u
14 | h/G1Xcp3JoVX8NNEd5LhHkJKS1idK4PXTP7uXXqOFI5NoL+Q0gNAyJYNtXe8KQ4q
15 | 8L9O9pNaC418OCiiS/4rAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
16 | AQH/BAQDAgEGMB0GA1UdDgQWBBRivYsKGrwW4qUaX5DjNlpcExbkETANBgkqhkiG
17 | 9w0BAQUFAAOCAQEAqB1+wsyBShUOzkAdoFh55x9e1Od7nrnIPv6KKbcycMDv8kf3
18 | 25D5KlgVIW+OWrMKOK7wlRUX7kvF4cENqSCn1W3VVLzCvKTbx34pRR/V8cuUD+2D
19 | 7k1Q1331qFiulycGOl0IlqJCJRi3UPa2fuCDyHikOcbnIApK3Hk2/wGWhool5yzj
20 | 0wUbGcyDg12o1U96bB0tO5ZFIFdYB31skviyiHJojcq4+uRVKA1DrsKoK8ZQx0xf
21 | p6l5H/z5jmFqlBTW8EIW+tdB1NOKCRI6JPwR+NSC8UtZS2M39oyGkGyReoIKjT8K
22 | XFfTm7TDhB9Tf65TF+mw0Rkr101KsoUT5Sm8Dw==
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/example/certs/testing-intermediate.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFRjCCBC6gAwIBAgIQdV9MvP3+ENU3kdfSg2vm/DANBgkqhkiG9w0BAQsFADCB
3 | hTELMAkGA1UEBhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8w
4 | LQYDVQQLEyZGb3IgVGVzdCBQdXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjEm
5 | MCQGA1UEAxMdVHJpYWwgQ2xhc3MgMyBKYXBhbiBSb290IC0gRzUwHhcNMTUwMjE4
6 | MDAwMDAwWhcNMjUwMjE3MjM1OTU5WjCBvDELMAkGA1UEBhMCSlAxHTAbBgNVBAoT
7 | FFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQdXJwb3Nl
8 | cyBPbmx5LiBObyBhc3N1cmFuY2VzLjE7MDkGA1UECxMyVGVybXMgb2YgdXNlIGF0
9 | IGh0dHBzOi8vd3d3LnN5bWF1dGguY29tL2Nwcy90ZXN0Y2ExIDAeBgNVBAMTF1Ry
10 | aWFsIFNTTCBKYXBhbiBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
11 | CgKCAQEA20T0ajk8CG7rIu90Zyh4bDAo32nO6RdBN1xjFD054LbjIR9R7dqAXbLx
12 | sYj5+V/7v5tN4ogqyHiK2CKLe3z/rtrFjeei/Xyr5NysNWuY2v/OIRSLpeKYYivr
13 | Ax76ssHbPRdqT4OobxYfRaOEZKwM5etUD55jLI1/0lcSeNqv7Gps8tKt44T4vDx2
14 | E8J0zT9Z102e649si0rUoGbP3flEBwvnkrmLTQDV1Rf9p3QbaAaZgsmEaXshgufJ
15 | Bti/BpOy1Sdg4ZTCcKPb/qhSphn2LZNbBFOH/rQiu4UoWHWWMOcsJm22pARSsELI
16 | ivNJIcXSE8PLTkLWisqRkVODaRaL+wIDAQABo4IBdzCCAXMwEgYDVR0TAQH/BAgw
17 | BgEB/wIBADB7BgNVHSAEdDByMHAGCmCGSAGG+EUBBxUwYjAuBggrBgEFBQcCARYi
18 | aHR0cHM6Ly93d3cuc3ltYXV0aC5jb20vY3BzL3Rlc3RjYTAwBggrBgEFBQcCAjAk
19 | GiJodHRwczovL3d3dy5zeW1hdXRoLmNvbS9jcHMvdGVzdGNhMGUGA1UdHwReMFww
20 | WqBYoFaGVGh0dHA6Ly9vbnNpdGVjcmwuc3ltYXV0aC5qcC9BUkwvU3ltYW50ZWNK
21 | YXBhbkluY1RyaWFsQ2xhc3MzSmFwYW5Sb290RzUvTGF0ZXN0QVJMLmNybDAOBgNV
22 | HQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGUzLTIw
23 | NDgtMTc5MB0GA1UdDgQWBBTdHyFkLkXC4EA5SL7jy4SAl8MkijAfBgNVHSMEGDAW
24 | gBRivYsKGrwW4qUaX5DjNlpcExbkETANBgkqhkiG9w0BAQsFAAOCAQEAZbKcrNvS
25 | q/b4KNd4Y41uNEqaCQlL6Uvqs5q6b0HQbK/Hjolt3sJttqT/jtSxGIITZVPH8PRt
26 | 6fo3aWII1eLIiCVzuQc0eVA0671pPhUHgd8mPHgUmhsxLURWgEE3lMx9dKQhKL1V
27 | Fcp/2bQy+dLVZELQl2L3qtRCVEYubLFmoAgBG7nyiwjt+AsvG6AX2N7MLzsF+C8L
28 | vRbWEqglqDCFAt1es1Vfb2a7zFj0/BUYDDo6eQE/BpYiYmbHtoWz6SzBNZsehMb5
29 | OlnF9xqndWK7x2rtaNyi+Z287AzaDe0VFTUaGd1YnzKlEHphRr2nMHd/iNRZFNLy
30 | poe4IWfm9maFow==
31 | -----END CERTIFICATE-----
32 |
--------------------------------------------------------------------------------
/example/certs/testing-server.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFRTCCBC2gAwIBAgIQVMhYFMdnctqh7wAXLx1HjzANBgkqhkiG9w0BAQsFADCBvDELMAkGA1UE
3 | BhMCSlAxHTAbBgNVBAoTFFN5bWFudGVjIEphcGFuLCBJbmMuMS8wLQYDVQQLEyZGb3IgVGVzdCBQ
4 | dXJwb3NlcyBPbmx5LiBObyBhc3N1cmFuY2VzLjE7MDkGA1UECxMyVGVybXMgb2YgdXNlIGF0IGh0
5 | dHBzOi8vd3d3LnN5bWF1dGguY29tL2Nwcy90ZXN0Y2ExIDAeBgNVBAMTF1RyaWFsIFNTTCBKYXBh
6 | biBDQSAtIEcyMB4XDTE1MTEyNDAwMDAwMFoXDTE1MTIwODIzNTk1OVowgdAxCzAJBgNVBAYTAkpQ
7 | MQ4wDAYDVQQIDAVUb2t5bzETMBEGA1UEBwwKQ2hpeW9kYS1LdTEcMBoGA1UECgwTVHJlYXN1cmUg
8 | RGF0YSwgSy5LLjEYMBYGA1UECwwPT3BlblNvdXJjZSBUZWFtMSwwKgYDVQQLDCNodHRwczovL3d3
9 | dy5zeW1hbnRlYy5jb20vY3BzL3Rlc3RjYTEYMBYGA1UECwwPcjQ1MDExNTExNTE4MzgyMRwwGgYD
10 | VQQDDBN0ZXN0aW5nLmZsdWVudGQub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
11 | pz2pcz/cGXSvEYMCXZLyLj8BXQvPRPfQIITPzCWpr+jbr5oVI2D3FqysJLedLRZHWY3wAmp/AHqS
12 | WIHfaGUr6TmLVaKM2weFfxjKtwHboVYSHrhL5bth6fm8gMzJrB6UbFyfUXRPlZw6z/WxcMezIoHp
13 | 6KSS8Ao9ixeql1AFKF4Yc8H6xUH0fLLNOBLP7UxIbg7xZNxqcjlULOgKcMojK2YioI98Yq9nJ5To
14 | VopopIgtPEqXzN7W9EdrtaHelky9fsXLY3YnZ599ujEmHMMOzKbrTg7OMOyKwyhFdyERsGfNOHJ1
15 | /WdgCZZXRlfc0s2SxEWttaJ8v9D18Va1w4cR8wIDAQABo4IBKzCCAScwCQYDVR0TBAIwADAOBgNV
16 | HQ8BAf8EBAMCBaAwXgYDVR0fBFcwVTBToFGgT4ZNaHR0cDovL29uc2l0ZWNybC5zeW1hdXRoLmpw
17 | L1N5bWFudGVjSmFwYW5JbmNUcmlhbFNTTEphcGFuQ0FzaGEyL0xhdGVzdENSTC5jcmwwHgYDVR0R
18 | BBcwFYITdGVzdGluZy5mbHVlbnRkLm9yZzBKBgNVHSAEQzBBMD8GCmCGSAGG+EUBBxUwMTAvBggr
19 | BgEFBQcCARYjaHR0cHM6Ly93d3cuc3ltYW50ZWMuY29tL2Nwcy90ZXN0Y2EwHQYDVR0lBBYwFAYI
20 | KwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFN0fIWQuRcLgQDlIvuPLhICXwySKMA0GCSqG
21 | SIb3DQEBCwUAA4IBAQAU6rXvUcybWiyHfIGSlzy4MYO6HBeaF0tc2Pt0T4ByX+smXvKoVVgZbX8E
22 | ahlZURfjSwZyXHCpMvcH17/IyW7c+HrX/jheMZ7iyYdvCQnZ2GrT8Zr3GRMcNYf8e8wnXwkBFAPj
23 | yek5XHF5ShwLv64bOWDjAfdIAUCHEo1PpDz6JPQgJEb+SHqrxWx+O0zmELYUgSvRWKBuEC5TrxGU
24 | HEib235vmz6/TgDUqfci7RPf16cwqVyEilzD5tLXTuMp4+tkPRjEA5d+EzH5O2Lx/ef5RTnYYMZ0
25 | zsVBXssRtwcFKO2vStIGitRK9G9mbvzMNKuItaHcmw8GOhyXnMjTpl8u
26 | -----END CERTIFICATE-----
27 |
28 |
--------------------------------------------------------------------------------
/example/client.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | secure yes
8 | self_hostname client
9 | shared_key hogeposxxx0
10 | keepalive 30
11 | ca_cert_path "#{Dir.pwd}/test/tmp/cadir/ca_cert.pem"
12 | enable_strict_verification yes
13 |
14 | host localhost
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/client_proxy.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | secure yes
8 | self_hostname client
9 | shared_key hogeposxxx0
10 | keepalive 30
11 | ca_cert_path "#{Dir.pwd}/test/tmp/cadir/ca_cert.pem"
12 | enable_strict_verification yes
13 |
14 | proxy_uri http://foo.foo.local:3128
15 | host localhost
16 |
17 | flush_interval 1s
18 |
19 |
--------------------------------------------------------------------------------
/example/insecure_client.conf:
--------------------------------------------------------------------------------
1 |
2 | @type forward
3 |
4 |
5 |
6 | @type secure_forward
7 | secure no
8 | self_hostname client
9 | shared_key hogeposxxx0
10 | keepalive 30
11 | enable_strict_verification yes
12 |
13 | host localhost
14 |
15 | flush_interval 1s
16 |
17 |
--------------------------------------------------------------------------------
/example/insecure_server.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | secure no
4 | self_hostname localhost
5 | shared_key hogeposxxx0
6 |
7 |
8 |
9 | @type stdout
10 |
11 |
--------------------------------------------------------------------------------
/example/server.conf:
--------------------------------------------------------------------------------
1 |
2 | @type secure_forward
3 | secure yes
4 | self_hostname localhost
5 | shared_key hogeposxxx0
6 | ca_cert_path "#{Dir.pwd}/test/tmp/cadir/ca_cert.pem"
7 | ca_private_key_path "#{Dir.pwd}/test/tmp/cadir/ca_key.pem"
8 | ca_private_key_passphrase testing secret phrase
9 |
10 |
11 |
12 | @type stdout
13 |
14 |
--------------------------------------------------------------------------------
/fluent-plugin-secure-forward.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | Gem::Specification.new do |gem|
3 | gem.name = "fluent-plugin-secure-forward"
4 | gem.version = "0.4.5"
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}
8 | gem.description = %q{Message forwarding over SSL with authentication}
9 | gem.homepage = "https://github.com/tagomoris/fluent-plugin-secure-forward"
10 | gem.license = "Apache-2.0"
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 "resolve-hostname"
19 | gem.add_runtime_dependency "proxifier"
20 | gem.add_development_dependency "test-unit"
21 | gem.add_development_dependency "rake"
22 | end
23 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/in_secure_forward.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | require 'fluent/input'
4 |
5 | require 'ipaddr'
6 | require 'socket'
7 | require 'openssl'
8 | require 'digest'
9 | require 'securerandom'
10 |
11 | module Fluent
12 | class SecureForwardInput < Input
13 | end
14 | end
15 |
16 | require_relative 'input_session'
17 | require_relative './secure_forward/cert_util'
18 |
19 | module Fluent
20 | class SecureForwardInput < Input
21 | DEFAULT_SECURE_LISTEN_PORT = 24284
22 |
23 | Fluent::Plugin.register_input('secure_forward', self)
24 |
25 | config_param :secure, :bool # if secure, cert_path or ca_cert_path required
26 |
27 | config_param :hostname, :string, default: nil # This is evaluated after rewriting conf in fact.
28 | config_param :self_hostname, :string
29 |
30 | config_param :shared_key, :string, secret: true
31 |
32 | config_param :bind, :string, default: '0.0.0.0'
33 | config_param :port, :integer, default: DEFAULT_SECURE_LISTEN_PORT
34 | config_param :allow_keepalive, :bool, default: true #TODO: implement
35 |
36 | config_param :allow_anonymous_source, :bool, default: true
37 | config_param :authentication, :bool, default: false
38 |
39 | config_param :ssl_version, :string, default: 'TLSv1_2'
40 | config_param :ssl_ciphers, :string, default: nil
41 |
42 | # Cert signed by public CA
43 | config_param :cert_path, :string, default: nil
44 | config_param :private_key_path, :string, default: nil
45 | config_param :private_key_passphrase, :string, default: nil, secret: true
46 |
47 | # Cert automatically generated and signed by private CA
48 | config_param :ca_cert_path, :string, default: nil
49 | config_param :ca_private_key_path, :string, default: nil
50 | config_param :ca_private_key_passphrase, :string, default: nil, secret: true
51 |
52 | # Otherwise: Cert automatically generated and signed by itself (for without any verification)
53 |
54 | config_param :generate_private_key_length, :integer, default: 2048
55 | config_param :generate_cert_country, :string, default: 'US'
56 | config_param :generate_cert_state, :string, default: 'CA'
57 | config_param :generate_cert_locality, :string, default: 'Mountain View'
58 | config_param :generate_cert_common_name, :string, default: nil
59 |
60 | config_param :read_length, :size, default: 8*1024*1024 # 8MB
61 | config_param :read_interval_msec, :integer, default: 50 # 50ms
62 | config_param :socket_interval_msec, :integer, default: 200 # 200ms
63 |
64 | attr_reader :read_interval, :socket_interval
65 |
66 | config_section :user, param_name: :users do
67 | config_param :username, :string
68 | config_param :password, :string, secret: true
69 | end
70 |
71 | config_section :client, param_name: :clients do
72 | config_param :host, :string, default: nil
73 | config_param :network, :string, default: nil
74 | config_param :shared_key, :string, default: nil, secret: true
75 | config_param :users, :string, default: nil # comma separated username list
76 | end
77 | attr_reader :nodes
78 |
79 | attr_reader :sessions # node/socket/thread list which has sslsocket instance keepaliving to client
80 |
81 | # Define `log` method for v0.10.42 or earlier
82 | unless method_defined?(:log)
83 | define_method("log") { $log }
84 | end
85 |
86 | # Define `router` method of v0.12 to support v0.10
87 | unless method_defined?(:router)
88 | define_method("router") { Fluent::Engine }
89 | end
90 |
91 | def initialize
92 | super
93 | @cert = nil
94 | end
95 |
96 | HOSTNAME_PLACEHOLDERS = [ '__HOSTNAME__', '${hostname}' ]
97 |
98 | def replace_hostname_placeholder(conf, hostname)
99 | replace_element = ->(c) {
100 | c.keys.each do |key|
101 | v = c[key]
102 | if v && v.respond_to?(:include?) && v.respond_to?(:gsub)
103 | if HOSTNAME_PLACEHOLDERS.any?{|ph| v.include?(ph) }
104 | c[key] = HOSTNAME_PLACEHOLDERS.inject(v){|r, ph| r.gsub(ph, hostname) }
105 | end
106 | end
107 | end
108 | c.elements.each{|e| replace_element.call(e) }
109 | }
110 | replace_element.call(conf)
111 | end
112 |
113 | def configure(conf)
114 | hostname = conf.has_key?('hostname') ? conf['hostname'].to_s : Socket.gethostname
115 | replace_hostname_placeholder(conf, hostname)
116 |
117 | super
118 |
119 | if @secure
120 | unless @cert_path || @ca_cert_path
121 | raise Fluent::ConfigError, "cert_path or ca_cert_path required for secure communication"
122 | end
123 | if @cert_path
124 | raise Fluent::ConfigError, "private_key_path required" unless @private_key_path
125 | raise Fluent::ConfigError, "private_key_passphrase required" unless @private_key_passphrase
126 | certs = Fluent::SecureForward::CertUtil.certificates_from_file(@cert_path)
127 | if certs.size < 1
128 | raise Fluent::ConfigError, "no valid certificates in cert_path: #{@cert_path}"
129 | end
130 | else # @ca_cert_path
131 | raise Fluent::ConfigError, "ca_private_key_path required" unless @ca_private_key_path
132 | raise Fluent::ConfigError, "ca_private_key_passphrase required" unless @ca_private_key_passphrase
133 | end
134 | else
135 | log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks for clients (output plugins)."
136 | end
137 |
138 | @read_interval = @read_interval_msec / 1000.0
139 | @socket_interval = @socket_interval_msec / 1000.0
140 |
141 | @nodes = []
142 |
143 | @clients.each do |client|
144 | if client.host && client.network
145 | raise Fluent::ConfigError, "both of 'host' and 'network' are specified for client"
146 | end
147 | if !client.host && !client.network
148 | raise Fluent::ConfigError, "Either of 'host' and 'network' must be specified for client"
149 | end
150 | source = nil
151 | if client.host
152 | begin
153 | source = IPSocket.getaddress(client.host)
154 | rescue SocketError => e
155 | raise Fluent::ConfigError, "host '#{client.host}' cannot be resolved"
156 | end
157 | end
158 | source_addr = begin
159 | IPAddr.new(source || client.network)
160 | rescue ArgumentError => e
161 | raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
162 | end
163 | @nodes.push({
164 | address: source_addr,
165 | shared_key: (client.shared_key || @shared_key),
166 | users: (client.users ? client.users.split(',') : nil)
167 | })
168 | end
169 |
170 | @generate_cert_common_name ||= @self_hostname
171 |
172 | # To check whether certificates are successfully generated/loaded at startup time
173 | self.certificate
174 |
175 | true
176 | end
177 |
178 | def start
179 | super
180 | OpenSSL::Random.seed(SecureRandom.random_bytes(16))
181 | @sessions = []
182 | @sock = nil
183 | @listener = Thread.new(&method(:run))
184 | @listener.abort_on_exception
185 | end
186 |
187 | def shutdown
188 | @listener.kill
189 | @listener.join
190 | @sessions.each{ |s| s.shutdown }
191 | @sock.close
192 | end
193 |
194 | def select_authenticate_users(node, username)
195 | if node.nil? || node[:users].nil?
196 | @users.select{|u| u.username == username}
197 | else
198 | @users.select{|u| node[:users].include?(u.username) && u.username == username}
199 | end
200 | end
201 |
202 | def certificate
203 | return @cert, @key if @cert && @key
204 |
205 | @client_ca = nil
206 | if @cert_path
207 | @key = OpenSSL::PKey::RSA.new(File.read(@private_key_path), @private_key_passphrase)
208 | certs = Fluent::SecureForward::CertUtil.certificates_from_file(@cert_path)
209 | @cert = certs.shift
210 | @client_ca = certs
211 | elsif @ca_cert_path
212 | opts = {
213 | ca_cert_path: @ca_cert_path,
214 | ca_key_path: @ca_private_key_path,
215 | ca_key_passphrase: @ca_private_key_passphrase,
216 | private_key_length: @generate_private_key_length,
217 | country: @generate_cert_country,
218 | state: @generate_cert_state,
219 | locality: @generate_cert_locality,
220 | common_name: @generate_cert_common_name,
221 | }
222 | @cert, @key = Fluent::SecureForward::CertUtil.generate_server_pair(opts)
223 | else
224 | opts = {
225 | private_key_length: @generate_private_key_length,
226 | country: @generate_cert_country,
227 | state: @generate_cert_state,
228 | locality: @generate_cert_locality,
229 | common_name: @generate_cert_common_name,
230 | }
231 | @cert, @key = Fluent::SecureForward::CertUtil.generate_self_signed_server_pair(opts)
232 | end
233 | return @cert, @key
234 | end
235 |
236 | def run # sslsocket server thread
237 | log.trace "setup for ssl sessions"
238 | cert, key = self.certificate
239 |
240 | ctx = OpenSSL::SSL::SSLContext.new(@ssl_version)
241 | if @secure
242 | # inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
243 | # https://bugs.ruby-lang.org/issues/9424
244 | ctx.set_params({})
245 |
246 | if @ssl_ciphers
247 | ctx.ciphers = @ssl_ciphers
248 | else
249 | ### follow httpclient configuration by nahi
250 | # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
251 | ctx.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
252 | end
253 | end
254 |
255 | ctx.cert = cert
256 | ctx.key = key
257 | if @client_ca
258 | ctx.extra_chain_cert = @client_ca
259 | end
260 |
261 | log.trace "start to listen", bind: @bind, port: @port
262 | server = TCPServer.new(@bind, @port)
263 | log.trace "starting SSL server", bind: @bind, port: @port
264 | @sock = OpenSSL::SSL::SSLServer.new(server, ctx)
265 | @sock.start_immediately = false
266 | begin
267 | log.trace "accepting sessions"
268 | loop do
269 | while socket = @sock.accept
270 | log.trace "accept tcp connection (ssl session not established yet)"
271 | @sessions.push Session.new(self, socket)
272 |
273 | # cleanup closed session instance
274 | @sessions.delete_if(&:closed?)
275 | log.trace "session instances:", all: @sessions.size, closed: @sessions.select(&:closed?).size
276 | end
277 | end
278 | rescue OpenSSL::SSL::SSLError => e
279 | raise unless e.message.start_with?('SSL_accept SYSCALL') # signal trap on accept
280 | end
281 | end
282 |
283 | def on_message(msg)
284 | # NOTE: copy&paste from Fluent::ForwardInput#on_message(msg)
285 |
286 | # TODO: format error
287 | tag = msg[0].to_s
288 | entries = msg[1]
289 |
290 | if entries.class == String
291 | # PackedForward
292 | es = MessagePackEventStream.new(entries, @cached_unpacker)
293 | router.emit_stream(tag, es)
294 |
295 | elsif entries.class == Array
296 | # Forward
297 | es = Fluent::MultiEventStream.new
298 | entries.each {|e|
299 | time = e[0].to_i
300 | time = (now ||= Fluent::Engine.now) if time == 0
301 | record = e[1]
302 | es.add(time, record)
303 | }
304 | router.emit_stream(tag, es)
305 |
306 | else
307 | # Message
308 | time = msg[1]
309 | time = Fluent::Engine.now if time == 0
310 | record = msg[2]
311 | router.emit(tag, time, record)
312 | end
313 | end
314 | end
315 | end
316 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/input_session.rb:
--------------------------------------------------------------------------------
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 from #{host}:#{port}", error_class: e.class, error: e
200 | ensure
201 | log.debug "Shutting down #{host}:#{port}"
202 | self.shutdown
203 | end
204 |
205 | def shutdown
206 | @state = :closed
207 | log.debug "Shutdown called"
208 | @socket.close
209 | if @thread == Thread.current
210 | @thread.kill
211 | else
212 | if @thread
213 | @thread.kill
214 | @thread.join
215 | end
216 | end
217 | rescue => e
218 | log.debug "#{e.class}:#{e.message}"
219 | end
220 | end
221 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/openssl_util.rb:
--------------------------------------------------------------------------------
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
39 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/out_secure_forward.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | require 'fluent/output'
4 |
5 | require 'socket'
6 | require 'openssl'
7 | require 'digest'
8 | require 'resolve/hostname'
9 | require 'securerandom'
10 |
11 | module Fluent
12 | class SecureForwardOutput < ObjectBufferedOutput
13 | end
14 | end
15 |
16 | require_relative 'output_node'
17 |
18 | module Fluent
19 | class SecureForwardOutput < ObjectBufferedOutput
20 | DEFAULT_SECURE_CONNECT_PORT = 24284
21 |
22 | Fluent::Plugin.register_output('secure_forward', self)
23 |
24 | config_param :secure, :bool
25 |
26 | config_param :hostname, :string, default: nil # This is evaluated after rewriting conf in fact.
27 | config_param :self_hostname, :string
28 |
29 | config_param :shared_key, :string, secret: true
30 |
31 | config_param :keepalive, :time, default: nil # nil/0 means disable keepalive expiration
32 | config_param :connection_hard_timeout, :time, default: nil # specifying 0 explicitly means not to disconnect stuck connection forever
33 |
34 | config_param :send_timeout, :time, default: 60
35 | # config_param :hard_timeout, :time, :default => 60
36 |
37 | config_param :expire_dns_cache, :time, default: 60 # 0 means disable DNS cache
38 |
39 | config_param :ca_cert_path, :string, default: nil
40 |
41 | config_param :enable_strict_verification, :bool, default: nil # FQDN check with hostlabel
42 | config_param :ssl_version, :string, default: 'TLSv1_2'
43 | config_param :ssl_ciphers, :string, default: nil
44 |
45 | config_param :read_length, :size, default: 512 # 512bytes
46 | config_param :read_interval_msec, :integer, default: 50 # 50ms
47 | config_param :socket_interval_msec, :integer, default: 200 # 200ms
48 |
49 | config_param :reconnect_interval, :time, default: 5
50 | config_param :established_timeout, :time, default: 10
51 |
52 | config_param :proxy_uri, :string, default: nil
53 |
54 | attr_reader :read_interval, :socket_interval
55 |
56 | config_section :server, param_name: :servers do
57 | config_param :host, :string
58 | config_param :hostlabel, :string, default: nil
59 | config_param :port, :integer, default: DEFAULT_SECURE_CONNECT_PORT
60 | config_param :shared_key, :string, default: nil, secret: true
61 | config_param :username, :string, default: ''
62 | config_param :password, :string, default: '', secret: true
63 | config_param :standby, :bool, default: false
64 | config_param :proxy_uri, :string, default: nil
65 | end
66 | attr_reader :nodes
67 |
68 | attr_reader :hostname_resolver
69 |
70 | # Define `log` method for v0.10.42 or earlier
71 | unless method_defined?(:log)
72 | define_method("log") { $log }
73 | end
74 |
75 | HOSTNAME_PLACEHOLDERS = [ '__HOSTNAME__', '${hostname}' ]
76 |
77 | def replace_hostname_placeholder(conf, hostname)
78 | replace_element = ->(c) {
79 | c.keys.each do |key|
80 | v = c[key]
81 | if v && v.respond_to?(:include?) && v.respond_to?(:gsub)
82 | if HOSTNAME_PLACEHOLDERS.any?{|ph| v.include?(ph) }
83 | c[key] = HOSTNAME_PLACEHOLDERS.inject(v){|r, ph| r.gsub(ph, hostname) }
84 | end
85 | end
86 | end
87 | c.elements.each{|e| replace_element.call(e) }
88 | }
89 | replace_element.call(conf)
90 | end
91 |
92 | def configure(conf)
93 | hostname = conf.has_key?('hostname') ? conf['hostname'].to_s : Socket.gethostname
94 | replace_hostname_placeholder(conf, hostname)
95 |
96 | super
97 |
98 | if @secure
99 | if @ca_cert_path
100 | raise Fluent::ConfigError, "CA cert file not found nor readable at '#{@ca_cert_path}'" unless File.readable?(@ca_cert_path)
101 | begin
102 | OpenSSL::X509::Certificate.new File.read(@ca_cert_path)
103 | rescue OpenSSL::X509::CertificateError
104 | raise Fluent::ConfigError, "failed to load CA cert file"
105 | end
106 | else
107 | raise Fluent::ConfigError, "FQDN verification required for certificates issued from public CA" unless @enable_strict_verification
108 | log.info "secure connection with valid certificates issued from public CA"
109 | end
110 | else
111 | log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks."
112 | end
113 |
114 | if @keepalive && !@connection_hard_timeout
115 | @connection_hard_timeout = @keepalive * 1.2
116 | end
117 |
118 | @read_interval = @read_interval_msec / 1000.0
119 | @socket_interval = @socket_interval_msec / 1000.0
120 |
121 | @nodes = []
122 | @servers.each do |server|
123 | node = Node.new(self, server)
124 | node.first_session = true
125 | @nodes.push node
126 | end
127 |
128 | if @num_threads > @nodes.select{|n| not n.standby}.size
129 | log.warn "Too many num_threads for secure-forward: threads should be smaller or equal to non standby servers"
130 | end
131 |
132 | @next_node = 0
133 | @mutex = Mutex.new
134 |
135 | @hostname_resolver = Resolve::Hostname.new(system_resolver: true, ttl: @expire_dns_cache)
136 |
137 | true
138 | end
139 |
140 | def select_node(permit_standby=false)
141 | tries = 0
142 | nodes = @nodes.size
143 | @mutex.synchronize {
144 | n = nil
145 | while tries <= nodes
146 | n = @nodes[@next_node]
147 | @next_node += 1
148 | @next_node = 0 if @next_node >= nodes
149 |
150 | if n && n.established? && (! n.tained?) && (! n.detached?) && (!n.standby || permit_standby)
151 | n.tain!
152 | return n
153 | end
154 |
155 | tries += 1
156 | end
157 | nil
158 | }
159 | end
160 |
161 | def start
162 | super
163 |
164 | log.debug "starting secure-forward"
165 | OpenSSL::Random.seed(SecureRandom.random_bytes(16))
166 | log.debug "start to connect target nodes"
167 | @nodes.each do |node|
168 | log.debug "connecting node", host: node.host, port: node.port
169 | node.start
170 | end
171 | @nodewatcher = Thread.new(&method(:node_watcher))
172 | @nodewatcher.abort_on_exception = true
173 | end
174 |
175 | def node_watcher
176 | reconnectings = Array.new(@nodes.size)
177 | nodes_size = @nodes.size
178 |
179 | loop do
180 | sleep @reconnect_interval
181 |
182 | log.trace "in node health watcher"
183 |
184 | (0...nodes_size).each do |i|
185 | log.trace "node health watcher for #{@nodes[i].host}"
186 |
187 | next if @nodes[i].established? && ! @nodes[i].expired? && ! @nodes[i].detached?
188 |
189 | next if reconnectings[i]
190 |
191 | reason = :expired
192 |
193 | unless @nodes[i].established?
194 | log.warn "dead connection found: #{@nodes[i].host}, reconnecting..."
195 | reason = :dead
196 | end
197 |
198 | node = @nodes[i]
199 | log.debug "reconnecting to node", host: node.host, port: node.port, state: node.state, expire: node.expire, expired: node.expired?, detached: node.detached?
200 |
201 | renewed = node.dup
202 | renewed.start
203 |
204 | Thread.pass # to connection thread
205 | reconnectings[i] = { conn: renewed, at: Time.now, reason: reason }
206 | end
207 |
208 | (0...nodes_size).each do |i|
209 | next unless reconnectings[i]
210 |
211 | log.trace "checking reconnecting node #{reconnectings[i][:conn].host}"
212 |
213 | if reconnectings[i][:conn].established?
214 | log.debug "connection established for reconnecting node"
215 |
216 | oldconn = @nodes[i]
217 | @nodes[i] = reconnectings[i][:conn]
218 |
219 | if reconnectings[i][:reason] == :dead
220 | log.warn "recovered connection to dead node: #{nodes[i].host}"
221 | end
222 |
223 | log.trace "old connection shutting down"
224 | oldconn.detach! if oldconn # connection object doesn't raise any exceptions
225 | log.trace "old connection shutted down"
226 |
227 | reconnectings[i] = nil
228 | next
229 | end
230 |
231 | # not connected yet
232 |
233 | next if reconnectings[i][:at] + @established_timeout > Time.now
234 |
235 | # not connected yet, and timeout
236 | timeout_conn = reconnectings[i][:conn]
237 | log.debug "SSL connection is not established until timemout", host: timeout_conn.host, port: timeout_conn.port, timeout: @established_timeout
238 | reconnectings[i] = nil
239 | timeout_conn.detach! if timeout_conn # connection object doesn't raise any exceptions
240 | end
241 | end
242 | end
243 |
244 | def shutdown
245 | super
246 |
247 | @nodewatcher.kill
248 | @nodewatcher.join
249 |
250 | @nodes.each do |node|
251 | node.detach!
252 | node.join
253 | end
254 | end
255 |
256 | def write_objects(tag, es)
257 | node = select_node || select_node(true)
258 | unless node
259 | raise "no one nodes with valid ssl session"
260 | end
261 | log.trace "selected node", host: node.host, port: node.port, standby: node.standby
262 |
263 | begin
264 | send_data(node, tag, es)
265 | node.release!
266 | rescue Errno::EPIPE, IOError, OpenSSL::SSL::SSLError => e
267 | log.warn "Failed to send messages to #{node.host}, parging.", error_class: e.class, error: e
268 | node.release!
269 | node.detach!
270 |
271 | raise # to retry #write_objects
272 | end
273 | end
274 |
275 | # MessagePack FixArray length = 2
276 | FORWARD_HEADER = [0x92].pack('C')
277 |
278 | # to forward messages
279 | def send_data(node, tag, es)
280 | ssl = node.sslsession
281 | # beginArray(2)
282 | ssl.write FORWARD_HEADER
283 |
284 | # writeRaw(tag)
285 | ssl.write tag.to_msgpack
286 |
287 | # beginRaw(size)
288 | sz = es.size
289 | # # FixRaw
290 | # ssl.write [0xa0 | sz].pack('C')
291 | #elsif sz < 65536
292 | # # raw 16
293 | # ssl.write [0xda, sz].pack('Cn')
294 | #else
295 | # raw 32
296 | ssl.write [0xdb, sz].pack('CN')
297 | #end
298 |
299 | # writeRawBody(packed_es)
300 | es.write_to(ssl)
301 | end
302 | end
303 | end
304 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/output_node.rb:
--------------------------------------------------------------------------------
1 | # require 'msgpack'
2 | # require 'socket'
3 | # require 'openssl'
4 | # require 'digest'
5 | # require 'resolve/hostname'
6 |
7 | require 'proxifier'
8 |
9 | require_relative 'openssl_util'
10 |
11 | class Fluent::SecureForwardOutput::Node
12 | attr_accessor :host, :port, :hostlabel, :shared_key, :username, :password, :standby
13 |
14 | attr_accessor :authentication, :keepalive
15 | attr_accessor :socket, :sslsession, :unpacker, :shared_key_salt, :state
16 |
17 | attr_accessor :first_session, :detach
18 |
19 | attr_reader :expire
20 |
21 | def initialize(sender, conf)
22 | @sender = sender
23 | @shared_key = conf.shared_key || sender.shared_key
24 |
25 | @host = conf.host
26 | @port = conf.port
27 | @hostlabel = conf.hostlabel || conf.host
28 | @username = conf.username
29 | @password = conf.password
30 | @standby = conf.standby
31 |
32 | @proxy_uri = conf.proxy_uri
33 |
34 | @keepalive = sender.keepalive
35 | @connection_hard_timeout = sender.connection_hard_timeout
36 |
37 | @authentication = nil
38 |
39 | @writing = false
40 |
41 | @expire = nil
42 | @first_session = false
43 | @detach = false
44 |
45 | @socket = nil
46 | @sslsession = nil
47 | @unpacker = MessagePack::Unpacker.new
48 |
49 | @shared_key_salt = generate_salt
50 | @state = :helo
51 | @mtime = Time.now
52 | @thread = nil
53 | end
54 |
55 | def log
56 | @sender.log
57 | end
58 |
59 | def dup
60 | renewed = self.class.new(
61 | @sender,
62 | Fluent::Config::Section.new({host: @host, port: @port, hostlabel: @hostlabel, username: @username, password: @password, shared_key: @shared_key, standby: @standby, proxy_uri: @proxy_uri})
63 | )
64 | renewed
65 | end
66 |
67 | def start
68 | @thread = Thread.new(&method(:connect))
69 | end
70 |
71 | def detach!
72 | @detach = true
73 | end
74 |
75 | def detached?
76 | @detach
77 | end
78 |
79 | def tain!
80 | raise RuntimeError, "BUG: taining detached node" if @detach
81 | @writing = true
82 | end
83 |
84 | def tained?
85 | @writing
86 | end
87 |
88 | def release!
89 | @writing = false
90 | end
91 |
92 | def shutdown
93 | log.debug "shutting down node #{@host}"
94 | @state = :closed
95 |
96 | if @thread == Thread.current
97 | @sslsession.close if @sslsession
98 | @socket.close if @socket
99 | @thread.kill
100 | else
101 | if @thread
102 | @thread.kill
103 | @thread.join
104 | end
105 | @sslsession.close if @sslsession
106 | @socket.close if @socket
107 | end
108 | rescue => e
109 | log.debug "error on node shutdown #{e.class}:#{e.message}"
110 | end
111 |
112 | def join
113 | @thread && @thread.join
114 | end
115 |
116 | def established?
117 | @state == :established
118 | end
119 |
120 | def expired?
121 | if @keepalive.nil? || @keepalive == 0
122 | false
123 | else
124 | @expire && @expire < Time.now
125 | end
126 | end
127 |
128 | def generate_salt
129 | OpenSSL::Random.random_bytes(16)
130 | end
131 |
132 | def check_helo(message)
133 | log.debug "checking helo"
134 | # ['HELO', options(hash)]
135 | unless message.size == 2 && message[0] == 'HELO'
136 | return false
137 | end
138 | opts = message[1]
139 | @shared_key_nonce = opts['nonce'] || '' # make shared_key_check failed (instead of error) if protocol version mismatch exist
140 | @authentication = opts['auth']
141 | @allow_keepalive = opts['keepalive']
142 | @mtime = Time.now
143 | true
144 | end
145 |
146 | def generate_ping
147 | log.debug "generating ping"
148 | # ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + nonce + shared_key),
149 | # username || '', sha512\_hex(auth\_salt + username + password) || '']
150 | shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt).update(@sender.self_hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
151 | ping = ['PING', @sender.self_hostname, @shared_key_salt, shared_key_hexdigest]
152 | if @authentication != ''
153 | password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
154 | ping.push(@username, password_hexdigest)
155 | else
156 | ping.push('','')
157 | end
158 | @mtime = Time.now
159 | ping
160 | end
161 |
162 | def check_pong(message)
163 | log.debug "checking pong"
164 | # ['PONG', bool(authentication result), 'reason if authentication failed',
165 | # self_hostname, sha512\_hex(salt + self_hostname + nonce + sharedkey)]
166 | unless message.size == 5 && message[0] == 'PONG'
167 | return false, 'invalid format for PONG message'
168 | end
169 | _pong, auth_result, reason, hostname, shared_key_hexdigest = message
170 |
171 | unless auth_result
172 | return false, 'authentication failed: ' + reason
173 | end
174 |
175 | if hostname == @sender.self_hostname
176 | return false, 'same hostname between input and output: invalid configuration'
177 | end
178 |
179 | clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
180 | unless shared_key_hexdigest == clientside
181 | return false, 'shared key mismatch'
182 | end
183 |
184 | @mtime = Time.now
185 | return true, nil
186 | end
187 |
188 | def send_data(data)
189 | @mtime = Time.now
190 | @sslsession.write data.to_msgpack
191 | end
192 |
193 | def on_read(data)
194 | log.debug "on_read"
195 | if self.established?
196 | #TODO: ACK
197 | log.warn "unknown packets arrived..."
198 | return
199 | end
200 |
201 | case @state
202 | when :helo
203 | unless check_helo(data)
204 | log.warn "received invalid helo message from #{@host}"
205 | self.shutdown
206 | return
207 | end
208 | send_data generate_ping()
209 | @mtime = Time.now
210 | @state = :pingpong
211 | when :pingpong
212 | success, reason = check_pong(data)
213 | unless success
214 | log.warn "connection refused to #{@host}:" + reason
215 | self.shutdown
216 | return
217 | end
218 | log.info "connection established to #{@host}" if @first_session
219 | @state = :established
220 | @expire = Time.now + @keepalive if @keepalive && @keepalive > 0
221 | @mtime = Time.now
222 | log.debug "connection established", host: @host, port: @port, expire: @expire
223 | end
224 | end
225 |
226 | def connect
227 | Thread.current.abort_on_exception = true
228 | log.debug "starting client"
229 |
230 | begin
231 | addr = @sender.hostname_resolver.getaddress(@host)
232 | log.debug "create tcp socket to node", host: @host, address: addr, port: @port
233 | rescue => e
234 | log.warn "failed to resolve the hostname", error_class: e.class, error: e, host: @host
235 | @state = :failed
236 | return
237 | end
238 |
239 | begin
240 | if @proxy_uri.nil? then
241 | sock = TCPSocket.new(addr, @port)
242 | else
243 | proxy = Proxifier::Proxy(@proxy_uri)
244 | sock = proxy.open(addr, @port)
245 | end
246 | rescue => e
247 | log.warn "failed to connect for secure-forward", error_class: e.class, error: e, host: @host, address: addr, port: @port
248 | @state = :failed
249 | return
250 | end
251 |
252 | log.trace "changing socket options"
253 | opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
254 | sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
255 |
256 | opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
257 | sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
258 |
259 | log.trace "initializing SSL contexts"
260 |
261 | context = OpenSSL::SSL::SSLContext.new(@sender.ssl_version)
262 |
263 | log.trace "setting SSL verification options"
264 |
265 | if @sender.secure
266 | # inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
267 | # https://bugs.ruby-lang.org/issues/9424
268 | context.set_params({})
269 |
270 | if @sender.ssl_ciphers
271 | context.ciphers = @sender.ssl_ciphers
272 | else
273 | ### follow httpclient configuration by nahi
274 | # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
275 | context.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
276 | end
277 |
278 | log.trace "set verify_mode VERIFY_PEER"
279 | context.verify_mode = OpenSSL::SSL::VERIFY_PEER
280 | if @sender.enable_strict_verification
281 | context.cert_store = OpenSSL::X509::Store.new
282 | begin
283 | context.cert_store.set_default_paths
284 | rescue OpenSSL::X509::StoreError => e
285 | log.warn "faild to load system default certificates", error: e
286 | end
287 | end
288 | if @sender.ca_cert_path
289 | log.trace "set to use private CA", path: @sender.ca_cert_path
290 | context.ca_file = @sender.ca_cert_path
291 | end
292 | end
293 |
294 | log.debug "trying to connect ssl session", host: @host, address: addr, port: @port
295 | begin
296 | sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
297 | log.trace "connecting...", host: @host, address: addr, port: @port
298 | sslsession.connect
299 | @mtime = Time.now
300 | rescue => e
301 | log.warn "failed to establish SSL connection", error_class: e.class, error: e, host: @host, address: addr, port: @port
302 | @state = :failed
303 | return
304 | end
305 |
306 | log.debug "ssl session connected", host: @host, port: @port
307 |
308 | begin
309 | if @sender.enable_strict_verification
310 | log.debug "checking peer's certificate", subject: sslsession.peer_cert.subject
311 | sslsession.post_connection_check(@hostlabel)
312 | verify = sslsession.verify_result
313 | if verify != OpenSSL::X509::V_OK
314 | err_name = Fluent::SecureForwardOutput::OpenSSLUtil.verify_result_name(verify)
315 | log.warn "BUG: failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
316 | log.warn "BUG: verify_result: #{err_name}"
317 | raise RuntimeError, "BUG: failed to verify certification and to handle it correctly while connecting host #{@host} as #{@hostlabel}"
318 | end
319 | end
320 | rescue OpenSSL::SSL::SSLError => e
321 | log.warn "failed to verify certification while connecting ssl session", host: @host, hostlabel: @hostlabel
322 | self.shutdown
323 | raise
324 | end
325 |
326 | log.debug "ssl session connected", host: @host, port: @port
327 | @socket = sock
328 | @sslsession = sslsession
329 |
330 | buf = ''
331 | read_length = @sender.read_length
332 | read_interval = @sender.read_interval
333 | socket_interval = @sender.socket_interval
334 |
335 | @mtime = Time.now
336 |
337 | loop do
338 | break if @detach
339 | break if @connection_hard_timeout && Time.now > @mtime + @connection_hard_timeout
340 |
341 | begin
342 | while @sslsession.read_nonblock(read_length, buf)
343 | if buf == ''
344 | sleep read_interval
345 | next
346 | end
347 | @unpacker.feed_each(buf, &method(:on_read))
348 | @mtime = Time.now
349 | buf = ''
350 | end
351 | rescue OpenSSL::SSL::SSLError => e
352 | # to wait i/o restart
353 | log.trace "SSLError", error_class: e.class, error: e, mtime: @mtime, host: @host, port: @port
354 | if @connection_hard_timeout && Time.now > @mtime + @connection_hard_timeout
355 | log.warn "connection hard timeout", mtime: @mtime, timeout: @connection_hard_timeout, host: @host, port: @port
356 | log.warn "aborting connection", host: @host, port: @port
357 | self.release!
358 | self.detach!
359 | break
360 | else
361 | sleep socket_interval
362 | end
363 | rescue SystemCallError => e
364 | log.warn "disconnected by Error", error_class: e.class, error: e, host: @host, port: @port
365 | self.release!
366 | self.detach!
367 | break
368 | rescue EOFError
369 | log.warn "disconnected", host: @host, port: @port
370 | self.release!
371 | self.detach!
372 | break
373 | end
374 | end
375 | while @writing
376 | break if @detach
377 |
378 | sleep read_interval
379 | end
380 | self.shutdown
381 | end
382 | end
383 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/secure_forward/cert_util.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 |
3 | module Fluent
4 | module SecureForward
5 | module CertUtil
6 | def self.certificates_from_file(path)
7 | data = File.read(path)
8 | pattern = Regexp.compile('-+BEGIN CERTIFICATE-+\n(?:[^-]*\n)+-+END CERTIFICATE-+\n', Regexp::MULTILINE)
9 | list = []
10 | data.scan(pattern){|match| list << OpenSSL::X509::Certificate.new(match)}
11 | list
12 | end
13 |
14 | def self.generate_ca_pair(opts={})
15 | key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
16 |
17 | issuer = subject = OpenSSL::X509::Name.new
18 | subject.add_entry('C', opts[:cert_country])
19 | subject.add_entry('ST', opts[:cert_state])
20 | subject.add_entry('L', opts[:cert_locality])
21 | subject.add_entry('CN', opts[:cert_common_name])
22 |
23 | digest = OpenSSL::Digest::SHA256.new
24 |
25 | cert = OpenSSL::X509::Certificate.new
26 | cert.not_before = Time.at(0)
27 | cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
28 | cert.public_key = key
29 | cert.serial = 1
30 | cert.issuer = issuer
31 | cert.subject = subject
32 | cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)]))
33 | cert.sign(key, digest)
34 |
35 | return cert, key
36 | end
37 |
38 | def self.generate_server_pair(opts={})
39 | key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
40 |
41 | ca_key = OpenSSL::PKey::RSA.new(File.read(opts[:ca_key_path]), opts[:ca_key_passphrase])
42 | ca_cert = OpenSSL::X509::Certificate.new(File.read(opts[:ca_cert_path]))
43 | issuer = ca_cert.issuer
44 |
45 | subject = OpenSSL::X509::Name.new
46 | subject.add_entry('C', opts[:country])
47 | subject.add_entry('ST', opts[:state])
48 | subject.add_entry('L', opts[:locality])
49 | subject.add_entry('CN', opts[:common_name])
50 |
51 | digest = OpenSSL::Digest::SHA256.new
52 |
53 | cert = OpenSSL::X509::Certificate.new
54 | cert.not_before = Time.at(0)
55 | cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
56 | cert.public_key = key
57 | cert.serial = 2
58 | cert.issuer = issuer
59 | cert.subject = subject
60 |
61 | cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)]))
62 | cert.add_extension OpenSSL::X509::Extension.new('nsCertType', 'server')
63 |
64 | cert.sign ca_key, digest
65 |
66 | return cert, key
67 | end
68 |
69 | def self.generate_self_signed_server_pair(opts={})
70 | key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
71 |
72 | issuer = subject = OpenSSL::X509::Name.new
73 | subject.add_entry('C', opts[:country])
74 | subject.add_entry('ST', opts[:state])
75 | subject.add_entry('L', opts[:locality])
76 | subject.add_entry('CN', opts[:common_name])
77 |
78 | digest = OpenSSL::Digest::SHA256.new
79 |
80 | cert = OpenSSL::X509::Certificate.new
81 | cert.not_before = Time.at(0)
82 | cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
83 | cert.public_key = key
84 | cert.serial = 1
85 | cert.issuer = issuer
86 | cert.subject = subject
87 | cert.sign(key, digest)
88 |
89 | return cert, key
90 | end
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
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 | require 'fluent/input'
17 | require 'fluent/plugin/in_secure_forward'
18 | require 'fluent/plugin/out_secure_forward'
19 |
20 | class DummySocket
21 | attr_accessor :sync
22 | end
23 |
24 | class DummyInputPlugin
25 | attr_reader :log, :users, :nodes, :authentication, :allow_anonymous_source, :allow_keepalive
26 | attr_reader :shared_key, :self_hostname
27 | attr_reader :read_length, :read_interval, :socket_interval
28 |
29 | attr_reader :data
30 |
31 | def initialize(opts={})
32 | @log = $log
33 | @users = opts.fetch(:users, [])
34 | @nodes = opts.fetch(:nodes, [])
35 | @authentication = opts.fetch(:authentication, false)
36 | @allow_anonymous_source = opts.fetch(:allow_anonymous_source, true)
37 | @allow_keepalive = opts.fetch(:allow_keepalive, true)
38 | @shared_key = opts.fetch(:shared_key, 'shared key')
39 | @self_hostname = opts.fetch(:self_hostname, 'hostname.local')
40 | @read_length = opts.fetch(:read_length, 8*1024*1024)
41 | @read_interval = opts.fetch(:read_interval, 0.05)
42 | @socket_interval = opts.fetch(:socket_interval, 0.2)
43 |
44 | @data = []
45 | end
46 |
47 | def select_authenticate_users(node, username)
48 | if node.nil? || node[:users].nil?
49 | self.users.select{|u| u[:username] == username}
50 | else
51 | self.users.select{|u| node[:users].include?(u[:username]) && u[:username] == username}
52 | end
53 | end
54 |
55 | def on_message(data)
56 | raise NotImplementedError
57 | end
58 | end
59 |
60 | class DummyOutputPlugin
61 | end
62 |
63 |
64 | class Test::Unit::TestCase
65 | end
66 |
--------------------------------------------------------------------------------
/test/plugin/test_in_secure_forward.rb:
--------------------------------------------------------------------------------
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(<
34 | username tagomoris
35 | password foobar012
36 |
37 |
38 | password yakiniku
39 |
40 | CONFIG
41 | assert_raise(Fluent::ConfigError){ create_driver(<
48 | username tagomoris
49 | password foobar012
50 |
51 |
52 | username frsyuki
53 |
54 | CONFIG
55 |
56 | p2 = nil
57 | assert_nothing_raised { p2 = create_driver(<
64 | username tagomoris
65 | password foobar012
66 |
67 |
68 | username frsyuki
69 | password yakiniku
70 |
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(<
82 |
83 | host 192.168.10.30
84 | # network address (ex: 192.168.10.0/24) NOT Supported now
85 |
86 |
87 | host localhost
88 | network 192.168.1.1/32
89 |
90 |
91 | network 192.168.16.0/24
92 |
93 | CONFIG
94 | assert_raise(Fluent::ConfigError){ create_driver(<
100 |
101 | host 192.168.10.30
102 | # network address (ex: 192.168.10.0/24) NOT Supported now
103 |
104 |
105 |
106 |
107 | network 192.168.16.0/24
108 |
109 | CONFIG
110 |
111 | p3 = nil
112 | assert_nothing_raised { p3 = create_driver(<
118 |
119 | host 192.168.10.30
120 | # network address (ex: 192.168.10.0/24) NOT Supported now
121 |
122 |
123 | host localhost
124 | # wildcard (ex: *.host.fqdn.local) NOT Supported now
125 |
126 |
127 | network 192.168.16.0/24
128 |
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(<
144 | authentication yes # Deny clients without valid username/password
145 |
146 | username tagomoris
147 | password foobar012
148 |
149 |
150 | username frsyuki
151 | password sukiyaki
152 |
153 |
154 | username repeatedly
155 | password sushi
156 |
157 |
158 | host 192.168.10.30 # allow all users to connect from 192.168.10.30
159 |
160 |
161 | host 192.168.10.31
162 | users tagomoris,frsyuki # deny repeatedly from 192.168.10.31
163 |
164 |
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 |
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(<
23 | host server.fqdn.local # or IP
24 | # port 24284
25 |
26 |
27 | type forward
28 |
29 | host localhost
30 |
31 |
32 | CONFIG
33 | end
34 |
35 | def test_configure_standby_server
36 | p1 = nil
37 | assert_nothing_raised { p1 = create_driver(<
44 | host server1.fqdn.local
45 |
46 |
47 | host server2.fqdn.local
48 | hostlabel server2
49 |
50 |
51 | host server1.fqdn.local
52 | hostlabel server1
53 | port 24285
54 | shared_key secret_string_more
55 | standby
56 |
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(<
92 | host server1.fqdn.local
93 |
94 |
95 | host server2.fqdn.local
96 |
97 |
98 | host server3.fqdn.local
99 | standby
100 |
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(<
136 | host server1.fqdn.local
137 |
138 |
139 | host server2.fqdn.local
140 |
141 |
142 | host server3.fqdn.local
143 | standby
144 |
145 | CONFIG
146 | end
147 |
148 | def test_configure_using_hostname
149 | my_system_hostname = Socket.gethostname
150 |
151 | d = create_driver(%[
152 | secure no
153 | shared_key secret_string
154 | self_hostname ${hostname}
155 |
156 | host server.fqdn.local # or IP
157 | # port 24284
158 |
159 | ])
160 | assert_equal my_system_hostname, d.instance.self_hostname
161 |
162 | d = create_driver(%[
163 | secure no
164 | shared_key secret_string
165 | self_hostname __HOSTNAME__
166 |
167 | host server.fqdn.local # or IP
168 | # port 24284
169 |
170 | ])
171 | assert_equal my_system_hostname, d.instance.self_hostname
172 |
173 | d = create_driver(%[
174 | secure no
175 | shared_key secret_string
176 | self_hostname test.${hostname}
177 |
178 | host server.fqdn.local # or IP
179 | # port 24284
180 |
181 | ])
182 | assert_equal "test.#{my_system_hostname}", d.instance.self_hostname
183 |
184 | d = create_driver(%[
185 | secure no
186 | shared_key secret_string
187 | hostname dummy.local
188 | self_hostname test.${hostname}
189 |
190 | host server.fqdn.local # or IP
191 | # port 24284
192 |
193 | ])
194 | assert_equal "test.dummy.local", d.instance.self_hostname
195 | end
196 | end
197 |
--------------------------------------------------------------------------------