├── .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 | --------------------------------------------------------------------------------