Skip to content

Commit

Permalink
Merge pull request #2802 from fluent/add-tls-module
Browse files Browse the repository at this point in the history
Add TLS module
  • Loading branch information
repeatedly authored Jan 31, 2020
2 parents 237b276 + 0d5aecd commit 71d77bd
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 31 deletions.
5 changes: 3 additions & 2 deletions lib/fluent/plugin/out_forward.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require 'fluent/output'
require 'fluent/config/error'
require 'fluent/clock'
require 'fluent/tls'
require 'base64'
require 'forwardable'

Expand Down Expand Up @@ -89,9 +90,9 @@ class ForwardOutput < Output
config_param :compress, :enum, list: [:text, :gzip], default: :text

desc 'The default version of TLS transport.'
config_param :tls_version, :enum, list: Fluent::PluginHelper::Socket::TLS_SUPPORTED_VERSIONS, default: Fluent::PluginHelper::Socket::TLS_DEFAULT_VERSION
config_param :tls_version, :enum, list: Fluent::TLS::SUPPORTED_VERSIONS, default: Fluent::TLS::DEFAULT_VERSION
desc 'The cipher configuration of TLS transport.'
config_param :tls_ciphers, :string, default: Fluent::PluginHelper::Socket::CIPHERS_DEFAULT
config_param :tls_ciphers, :string, default: Fluent::TLS::CIPHERS_DEFAULT
desc 'Skip all verification of certificates or not.'
config_param :tls_insecure_mode, :bool, default: false
desc 'Allow self signed certificates or not.'
Expand Down
5 changes: 3 additions & 2 deletions lib/fluent/plugin/out_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require 'net/http'
require 'uri'
require 'openssl'
require 'fluent/tls'
require 'fluent/plugin/output'
require 'fluent/plugin_helper/socket'

Expand Down Expand Up @@ -57,9 +58,9 @@ class RetryableResponse < StandardError; end
desc 'The verify mode of TLS'
config_param :tls_verify_mode, :enum, list: [:none, :peer], default: :peer
desc 'The default version of TLS'
config_param :tls_version, :enum, list: Fluent::PluginHelper::Socket::TLS_SUPPORTED_VERSIONS, default: Fluent::PluginHelper::Socket::TLS_DEFAULT_VERSION
config_param :tls_version, :enum, list: Fluent::TLS::SUPPORTED_VERSIONS, default: Fluent::TLS::DEFAULT_VERSION
desc 'The cipher configuration of TLS'
config_param :tls_ciphers, :string, default: Fluent::PluginHelper::Socket::CIPHERS_DEFAULT
config_param :tls_ciphers, :string, default: Fluent::TLS::CIPHERS_DEFAULT

desc 'Raise UnrecoverableError when the response is non success, 4xx/5xx'
config_param :error_response_as_unrecoverable, :bool, default: true
Expand Down
5 changes: 4 additions & 1 deletion lib/fluent/plugin_helper/cert_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
require 'openssl'
require 'socket'

require 'fluent/tls'

# this module is only for Socket/Server/HttpServer plugin helpers
module Fluent
module PluginHelper
module CertOption
def cert_option_create_context(version, insecure, ciphers, conf)
cert, key, extra = cert_option_server_validate!(conf)

ctx = OpenSSL::SSL::SSLContext.new(version)
ctx = OpenSSL::SSL::SSLContext.new
unless insecure
# inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
# https://bugs.ruby-lang.org/issues/9424
Expand All @@ -43,6 +45,7 @@ def cert_option_create_context(version, insecure, ciphers, conf)
if extra && !extra.empty?
ctx.extra_chain_cert = extra
end
Fluent::TLS.set_version_to_context(ctx, version, conf.min_version, conf.max_version)

ctx
end
Expand Down
15 changes: 5 additions & 10 deletions lib/fluent/plugin_helper/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def server_create_for_tls_connection(shared, bind, port, conf, backlog, socket_o
end

SERVER_TRANSPORT_PARAMS = [
:protocol, :version, :ciphers, :insecure,
:protocol, :version, :min_version, :max_version, :ciphers, :insecure,
:ca_path, :cert_path, :private_key_path, :private_key_passphrase, :client_cert_auth,
:ca_cert_path, :ca_private_key_path, :ca_private_key_passphrase,
:generate_private_key_length,
Expand All @@ -260,18 +260,13 @@ def server_create_transport_section_object(opts)
end

module ServerTransportParams
TLS_DEFAULT_VERSION = :'TLSv1_2'
TLS_SUPPORTED_VERSIONS = [:'TLSv1_1', :'TLSv1_2']
### follow httpclient configuration by nahi
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default

include Fluent::Configurable
config_section :transport, required: false, multi: false, init: true, param_name: :transport_config do
config_argument :protocol, :enum, list: [:tcp, :tls], default: :tcp
config_param :version, :enum, list: TLS_SUPPORTED_VERSIONS, default: TLS_DEFAULT_VERSION

config_param :ciphers, :string, default: CIPHERS_DEFAULT
config_param :version, :enum, list: Fluent::TLS::SUPPORTED_VERSIONS, default: Fluent::TLS::DEFAULT_VERSION
config_param :min_version, :enum, list: Fluent::TLS::SUPPORTED_VERSIONS, default: nil
config_param :max_version, :enum, list: Fluent::TLS::SUPPORTED_VERSIONS, default: nil
config_param :ciphers, :string, default: Fluent::TLS::CIPHERS_DEFAULT
config_param :insecure, :bool, default: false

# Cert signed by public CA
Expand Down
12 changes: 4 additions & 8 deletions lib/fluent/plugin_helper/socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require 'certstore'
end

require 'fluent/tls'
require_relative 'socket_option'

module Fluent
Expand All @@ -33,12 +34,6 @@ module Socket

include Fluent::PluginHelper::SocketOption

TLS_DEFAULT_VERSION = :'TLSv1_2'
TLS_SUPPORTED_VERSIONS = [:'TLSv1_1', :'TLSv1_2']
### follow httpclient configuration by nahi
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default

attr_reader :_sockets # for tests

# TODO: implement connection pool for specified host
Expand Down Expand Up @@ -97,7 +92,7 @@ def socket_create_udp(host, port, resolve_name: false, connect: false, **kwargs,

def socket_create_tls(
host, port,
version: TLS_DEFAULT_VERSION, ciphers: CIPHERS_DEFAULT, insecure: false, verify_fqdn: true, fqdn: nil,
version: Fluent::TLS::DEFAULT_VERSION, min_version: nil, max_version: nil, ciphers: Fluent::TLS::CIPHERS_DEFAULT, insecure: false, verify_fqdn: true, fqdn: nil,
enable_system_cert_store: true, allow_self_signed_cert: false, cert_paths: nil,
cert_path: nil, private_key_path: nil, private_key_passphrase: nil,
cert_thumbprint: nil, cert_logical_store_name: nil, cert_use_enterprise_store: true,
Expand All @@ -106,7 +101,7 @@ def socket_create_tls(
host_is_ipaddress = IPAddr.new(host) rescue false
fqdn ||= host unless host_is_ipaddress

context = OpenSSL::SSL::SSLContext.new(version)
context = OpenSSL::SSL::SSLContext.new

if insecure
log.trace "setting TLS verify_mode NONE"
Expand Down Expand Up @@ -154,6 +149,7 @@ def socket_create_tls(
context.cert = OpenSSL::X509::Certificate.new(File.read(cert_path)) if cert_path
context.key = OpenSSL::PKey::read(File.read(private_key_path), private_key_passphrase) if private_key_path
end
Fluent::TLS.set_version_to_context(context, version, min_version, max_version)

tcpsock = socket_create_tcp(host, port, **kwargs)
sock = WrappedSocket::TLS.new(tcpsock, context)
Expand Down
81 changes: 81 additions & 0 deletions lib/fluent/tls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#
# Fluentd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'openssl'
require 'fluent/config/error'

module Fluent
module TLS
DEFAULT_VERSION = :'TLSv1_2'
SUPPORTED_VERSIONS = if defined?(OpenSSL::SSL::TLS1_3_VERSION)
[:'TLSv1_1', :'TLSv1_2', :'TLSv1_3', :'TLS1_1', :'TLS1_2', :'TLS1_3'].freeze
else
[:'TLSv1_1', :'TLSv1_2', :'TLS1_1', :'TLS1_2'].freeze
end
### follow httpclient configuration by nahi
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2".freeze # OpenSSL >1.0.0 default

METHODS_MAP = begin
map = {
TLSv1: OpenSSL::SSL::TLS1_VERSION,
TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION
}
map[:'TLSv1_3'] = OpenSSL::SSL::TLS1_3_VERSION if defined?(OpenSSL::SSL::TLS1_3_VERSION)
MIN_MAX_AVAILABLE = true
map.freeze
rescue NameError
# ruby 2.4 doesn't have OpenSSL::SSL::TLSXXX constants and min_version=/max_version= methods
map = {
TLS1: :'TLSv1',
TLS1_1: :'TLSv1_1',
TLS1_2: :'TLSv1_2',
}.freeze
MIN_MAX_AVAILABLE = false
map
end
private_constant :METHODS_MAP

# Helper for old syntax/method support:
# ruby 2.4 uses ssl_version= but this method is now deprecated.
# min_version=/max_version= use 'TLS1_2' but ssl_version= uses 'TLSv1_2'
def set_version_to_context(ctx, version, min_version, max_version)
if MIN_MAX_AVAILABLE
case
when min_version.nil? && max_version.nil?
min_version = METHODS_MAP[version] || version
max_version = METHODS_MAP[version] || version
when min_version.nil? && max_version
raise Fluent::ConfigError, "When you set max_version, must set min_version together"
when min_version && max_version.nil?
raise Fluent::ConfigError, "When you set min_version, must set max_version together"
else
min_version = METHODS_MAP[min_version] || min_version
max_version = METHODS_MAP[max_version] || max_version
end
ctx.min_version = min_version
ctx.max_version = max_version
else
ctx.ssl_version = METHODS_MAP[version] || version
end

ctx
end
module_function :set_version_to_context
end
end

45 changes: 37 additions & 8 deletions test/plugin_helper/test_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ def create_server_pair_chained_with_root_ca(ca_cert_path, ca_key_path, ca_key_pa
File.chmod(0600, cert_path, private_key_path)
end

def open_tls_session(addr, port, verify: true, cert_path: nil, selfsigned: true, hostname: nil)
def open_tls_session(addr, port, version: Fluent::TLS::DEFAULT_VERSION, verify: true, cert_path: nil, selfsigned: true, hostname: nil)
context = OpenSSL::SSL::SSLContext.new
context.set_params({})
if verify
Expand All @@ -866,6 +866,7 @@ def open_tls_session(addr, port, verify: true, cert_path: nil, selfsigned: true,
else
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
Fluent::TLS.set_version_to_context(context, version, nil, nil)

sock = OpenSSL::SSL::SSLSocket.new(TCPSocket.new(addr, port), context)
sock.hostname = hostname if hostname && sock.respond_to?(:hostname)
Expand Down Expand Up @@ -908,7 +909,7 @@ def assert_certificate(cert, expected_extensions)
# insecure
tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: true,
generate_private_key_length: 2048,
Expand Down Expand Up @@ -952,7 +953,7 @@ def assert_certificate(cert, expected_extensions)

tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: false,
cert_path: cert_path,
Expand Down Expand Up @@ -986,7 +987,7 @@ def assert_certificate(cert, expected_extensions)

tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: false,
ca_cert_path: ca_cert_path,
Expand Down Expand Up @@ -1026,7 +1027,7 @@ def assert_certificate(cert, expected_extensions)

tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: false,
cert_path: cert_path,
Expand Down Expand Up @@ -1056,7 +1057,7 @@ def assert_certificate(cert, expected_extensions)

tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: false,
cert_path: cert_path,
Expand Down Expand Up @@ -1253,7 +1254,7 @@ def assert_certificate(cert, expected_extensions)

@tls_options = {
protocol: :tls,
version: 'TLSv1_2',
version: :'TLSv1_2',
ciphers: 'ALL:!aNULL:!eNULL:!SSLv2',
insecure: false,
cert_path: @cert_path,
Expand Down Expand Up @@ -1454,6 +1455,35 @@ def assert_certificate(cert, expected_extensions)
assert_equal ["yayfoo\n", "yayfoo\n", "yayfoo\n"], lines
assert_equal ["closed", "closed", "closed"], callback_results
end

sub_test_case 'TLS version connection check' do
test "can't connect with different TLS version" do
@d.server_create_tls(:s, PORT, tls_options: @tls_options) do |data, conn|
end
assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET) {
open_tls_session('127.0.0.1', PORT, cert_path: @cert_path, version: :'TLS1_1') do |sock|
end
}
end

test "can specify multiple TLS versions by min_version/max_version" do
omit "min_version=/max_version= is not supported" unless Fluent::TLS::MIN_MAX_AVAILABLE

opts = @tls_options.merge(min_version: :'TLS1_1', max_version: :'TLSv1_2')
@d.server_create_tls(:s, PORT, tls_options: opts) do |data, conn|
end
assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET) {
open_tls_session('127.0.0.1', PORT, cert_path: @cert_path, version: :'TLS1') do |sock|
end
}
[:'TLS1_1', :'TLS1_2'].each { |ver|
assert_nothing_raised {
open_tls_session('127.0.0.1', PORT, cert_path: @cert_path, version: ver) do |sock|
end
}
}
end
end
end

sub_test_case '#server_create_unix' do
Expand Down Expand Up @@ -1738,5 +1768,4 @@ def open_client(proto, addr, port)
# pend "not implemented yet"
end
end

end
Loading

0 comments on commit 71d77bd

Please sign in to comment.