lib/fluent/plugin_helper/cert_option.rb
#
# 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 '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
# inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
# https://bugs.ruby-lang.org/issues/9424
ctx.set_params({}) unless insecure
if conf.client_cert_auth
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
end
ctx.ca_file = conf.ca_path
ctx.cert = cert
ctx.key = key
if extra && !extra.empty?
ctx.extra_chain_cert = extra
end
if conf.cert_verifier
sandbox = Class.new
ctx.verify_callback = if File.exist?(conf.cert_verifier)
verifier = File.read(conf.cert_verifier)
sandbox.instance_eval(verifier, File.basename(conf.cert_verifier))
else
sandbox.instance_eval(conf.cert_verifier)
end
end
Fluent::TLS.set_version_to_context(ctx, version, conf.min_version, conf.max_version)
ctx.ciphers = ciphers unless insecure
ctx
end
def cert_option_server_validate!(conf)
case
when conf.cert_path
raise Fluent::ConfigError, "private_key_path is required when cert_path is specified" unless conf.private_key_path
log.warn "For security reason, setting private_key_passphrase is recommended when cert_path is specified" unless conf.private_key_passphrase
cert_option_load(conf.cert_path, conf.private_key_path, conf.private_key_passphrase)
when conf.ca_cert_path
raise Fluent::ConfigError, "ca_private_key_path is required when ca_cert_path is specified" unless conf.ca_private_key_path
log.warn "For security reason, setting ca_private_key_passphrase is recommended when ca_cert_path is specified" unless conf.ca_private_key_passphrase
generate_opts = cert_option_cert_generation_opts_from_conf(conf)
cert_option_generate_server_pair_by_ca(
conf.ca_cert_path,
conf.ca_private_key_path,
conf.ca_private_key_passphrase,
generate_opts
)
when conf.insecure
log.warn "insecure TLS communication server is configured (using 'insecure' mode)"
generate_opts = cert_option_cert_generation_opts_from_conf(conf)
cert_option_generate_server_pair_self_signed(generate_opts)
else
raise Fluent::ConfigError, "no valid cert options configured. specify either 'cert_path', 'ca_cert_path' or 'insecure'"
end
end
def cert_option_load(cert_path, private_key_path, private_key_passphrase)
key = OpenSSL::PKey::read(File.read(private_key_path), private_key_passphrase)
certs = cert_option_certificates_from_file(cert_path)
cert = certs.shift
return cert, key, certs
end
def cert_option_cert_generation_opts_from_conf(conf)
{
private_key_length: conf.generate_private_key_length,
country: conf.generate_cert_country,
state: conf.generate_cert_state,
locality: conf.generate_cert_locality,
common_name: conf.generate_cert_common_name || ::Socket.gethostname,
expiration: conf.generate_cert_expiration,
digest: conf.generate_cert_digest,
}
end
def cert_option_generate_pair(opts, issuer = nil)
key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
subject = OpenSSL::X509::Name.new
subject.add_entry('C', opts[:country])
subject.add_entry('ST', opts[:state])
subject.add_entry('L', opts[:locality])
subject.add_entry('CN', opts[:common_name])
issuer ||= subject
cert = OpenSSL::X509::Certificate.new
cert.not_before = Time.at(0)
cert.not_after = Time.now + opts[:expiration]
cert.public_key = key
cert.version = 2
cert.serial = rand(2**(8*10))
cert.issuer = issuer
cert.subject = subject
return cert, key
end
def cert_option_add_extensions(cert, extensions)
ef = OpenSSL::X509::ExtensionFactory.new
extensions.each do |ext|
oid, value = ext
cert.add_extension ef.create_extension(oid, value)
end
end
def cert_option_generate_ca_pair_self_signed(generate_opts)
cert, key = cert_option_generate_pair(generate_opts)
cert_option_add_extensions(cert, [
['basicConstraints', 'CA:TRUE']
])
cert.sign(key, generate_opts[:digest].to_s)
return cert, key
end
def cert_option_generate_server_pair_by_ca(ca_cert_path, ca_key_path, ca_key_passphrase, generate_opts)
ca_key = OpenSSL::PKey::read(File.read(ca_key_path), ca_key_passphrase)
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
cert, key = cert_option_generate_pair(generate_opts, ca_cert.subject)
raise "BUG: certificate digest algorithm not set" unless generate_opts[:digest]
cert_option_add_extensions(cert, [
['basicConstraints', 'CA:FALSE'],
['nsCertType', 'server'],
['keyUsage', 'digitalSignature,keyEncipherment'],
['extendedKeyUsage', 'serverAuth']
])
cert.sign(ca_key, generate_opts[:digest].to_s)
return cert, key, nil
end
def cert_option_generate_server_pair_self_signed(generate_opts)
cert, key = cert_option_generate_pair(generate_opts)
raise "BUG: certificate digest algorithm not set" unless generate_opts[:digest]
cert_option_add_extensions(cert, [
['basicConstraints', 'CA:FALSE'],
['nsCertType', 'server']
])
cert.sign(key, generate_opts[:digest].to_s)
return cert, key, nil
end
def cert_option_certificates_from_file(path)
data = File.read(path)
pattern = Regexp.compile('-+BEGIN CERTIFICATE-+\r?\n(?:[^-]*\r?\n)+-+END CERTIFICATE-+\r?\n?', Regexp::MULTILINE)
list = []
data.scan(pattern){|match| list << OpenSSL::X509::Certificate.new(match) }
if list.length == 0
raise Fluent::ConfigError, "cert_path does not contain a valid certificate"
end
list
end
end
end
end