Merge branch 'add_ldap_tls_options' into 'master'

Allow raw `tls_options` to be passed in LDAP configuration

Closes #46391

See merge request gitlab-org/gitlab-ce!20678
This commit is contained in:
Douwe Maan 2019-03-05 13:17:23 +00:00
commit ed41f4e6ea
8 changed files with 306 additions and 52 deletions

View File

@ -68,7 +68,7 @@ gem 'gpgme', '~> 2.0.18'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap'
gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap'
# API

View File

@ -291,7 +291,7 @@ GEM
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
gitlab_omniauth-ldap (2.0.4)
gitlab_omniauth-ldap (2.1.1)
net-ldap (~> 0.16)
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
@ -1024,7 +1024,7 @@ DEPENDENCIES
gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
google-protobuf (~> 3.6)

View File

@ -0,0 +1,5 @@
---
title: Allow raw `tls_options` to be passed in LDAP configuration
merge_request: 20678
author:
type: changed

View File

@ -379,19 +379,54 @@ production: &base
# "start_tls" or "simple_tls". Defaults to true.
verify_certificates: true
# Specifies the path to a file containing a PEM-format CA certificate,
# e.g. if you need to use an internal CA.
#
# Example: '/etc/ca.pem'
#
ca_file: ''
# OpenSSL::SSL::SSLContext options.
tls_options:
# Specifies the path to a file containing a PEM-format CA certificate,
# e.g. if you need to use an internal CA.
#
# Example: '/etc/ca.pem'
#
ca_file: ''
# Specifies the SSL version for OpenSSL to use, if the OpenSSL default
# is not appropriate.
#
# Example: 'TLSv1_1'
#
ssl_version: ''
# Specifies the SSL version for OpenSSL to use, if the OpenSSL default
# is not appropriate.
#
# Example: 'TLSv1_1'
#
ssl_version: ''
# Specific SSL ciphers to use in communication with LDAP servers.
#
# Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2'
ciphers: ''
# Client certificate
#
# Example:
# cert: |
# -----BEGIN CERTIFICATE-----
# MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
# bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
# CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4
# rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
# ...
# 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80
# Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg==
# -----END CERTIFICATE -----
cert: ''
# Client private key
# key: |
# -----BEGIN PRIVATE KEY-----
# MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6
# bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN
# 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C
# rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
# ...
# +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9
# l6RG+a/mW+0rCWn8JAd464Ps9hE=
# -----END PRIVATE KEY-----
key: ''
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
@ -653,8 +688,8 @@ production: &base
# # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# # encryption: 'AES256'
# # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
# # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
# # 'encryption' must also be set in order for this to have any effect.
# # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
# # 'encryption' must also be set in order for this to have any effect.
# # encryption_key: '<base64 key>'
# # Specifies Amazon S3 storage class to use for backups, this is optional
# # storage_class: 'STANDARD'

View File

@ -40,6 +40,24 @@ if Settings.ldap['enabled'] || Rails.env.test?
# Since GitLab 10.0, verify_certificates defaults to true for security.
server['verify_certificates'] = true if server['verify_certificates'].nil?
# Expose ability to set `tls_options` directly. Deprecate `ca_file` and
# `ssl_version` in favor of `tls_options` hash option.
server['tls_options'] ||= {}
if server['ssl_version'] || server['ca_file']
Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`'
end
if server['ssl_version']
server['tls_options']['ssl_version'] ||= server['ssl_version']
server.delete('ssl_version')
end
if server['ca_file']
server['tls_options']['ca_file'] ||= server['ca_file']
server.delete('ca_file')
end
Settings.ldap['servers'][key] = server
end
end

View File

@ -139,14 +139,54 @@ main:
##
verify_certificates: true
##
## Specifies the SSL version for OpenSSL to use, if the OpenSSL default
## is not appropriate.
##
## Example: 'TLSv1_1'
##
##
ssl_version: ''
# OpenSSL::SSL::SSLContext options.
tls_options:
# Specifies the path to a file containing a PEM-format CA certificate,
# e.g. if you need to use an internal CA.
#
# Example: '/etc/ca.pem'
#
ca_file: ''
# Specifies the SSL version for OpenSSL to use, if the OpenSSL default
# is not appropriate.
#
# Example: 'TLSv1_1'
#
ssl_version: ''
# Specific SSL ciphers to use in communication with LDAP servers.
#
# Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2'
ciphers: ''
# Client certificate
#
# Example:
# cert: |
# -----BEGIN CERTIFICATE-----
# MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
# bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
# CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4
# rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
# ...
# 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80
# Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg==
# -----END CERTIFICATE -----
cert: ''
# Client private key
# key: |
# -----BEGIN PRIVATE KEY-----
# MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6
# bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN
# 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C
# rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
# ...
# +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9
# l6RG+a/mW+0rCWn8JAd464Ps9hE=
# -----END PRIVATE KEY-----
key: ''
##
## Set a timeout, in seconds, for LDAP queries. This helps avoid blocking

View File

@ -75,7 +75,8 @@ module Gitlab
encryption: options['encryption'],
filter: omniauth_user_filter,
name_proc: name_proc,
disable_verify_certificates: !options['verify_certificates']
disable_verify_certificates: !options['verify_certificates'],
tls_options: tls_options
)
if has_auth?
@ -85,9 +86,6 @@ module Gitlab
)
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts
end
@ -196,24 +194,28 @@ module Gitlab
end
def encryption_options
method = translate_method(options['encryption'])
method = translate_method
return nil unless method
{
method: method,
tls_options: tls_options(method)
tls_options: tls_options
}
end
def translate_method(method_from_config)
NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
def translate_method
NET_LDAP_ENCRYPTION_METHOD[options['encryption']&.to_sym]
end
def tls_options(method)
return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
def tls_options
return @tls_options if defined?(@tls_options)
opts = if options['verify_certificates']
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
method = translate_method
return nil unless method
opts = if options['verify_certificates'] && method != 'plain'
# Dup so we don't accidentally overwrite the constant
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.dup
else
# It is important to explicitly set verify_mode for two reasons:
# 1. The behavior of OpenSSL is undefined when verify_mode is not set.
@ -222,10 +224,35 @@ module Gitlab
{ verify_mode: OpenSSL::SSL::VERIFY_NONE }
end
opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
opts.merge!(custom_tls_options)
opts
@tls_options = opts
end
def custom_tls_options
return {} unless options['tls_options']
# Dup so we don't overwrite the original value
custom_options = options['tls_options'].dup.delete_if { |_, value| value.nil? || value.blank? }
custom_options.symbolize_keys!
if custom_options[:cert]
begin
custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert])
rescue OpenSSL::X509::CertificateError => e
Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}"
end
end
if custom_options[:key]
begin
custom_options[:key] = OpenSSL::PKey.read(custom_options[:key])
rescue OpenSSL::PKey::PKeyError => e
Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}"
end
end
custom_options
end
def auth_options

View File

@ -5,6 +5,65 @@ describe Gitlab::Auth::LDAP::Config do
let(:config) { described_class.new('ldapmain') }
def raw_cert
<<-EOS
-----BEGIN CERTIFICATE-----
MIIDZjCCAk4CCQDX+u/9fICksDANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJV
UzEMMAoGA1UECAwDRm9vMQwwCgYDVQQHDANCYXIxDDAKBgNVBAoMA0JhejEMMAoG
A1UECwwDUXV4MQ0wCwYDVQQDDARsZGFwMR8wHQYJKoZIhvcNAQkBFhBsZGFwQGV4
YW1wbGUuY29tMB4XDTE5MDIyNzE1NTUxNFoXDTE5MDMyOTE1NTUxNFowdTELMAkG
A1UEBhMCVVMxDDAKBgNVBAgMA0ZvbzEMMAoGA1UEBwwDQmFyMQwwCgYDVQQKDANC
YXoxDDAKBgNVBAsMA1F1eDENMAsGA1UEAwwEbGRhcDEfMB0GCSqGSIb3DQEJARYQ
bGRhcEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
APuDB/4/AUmTEmhYzN13no4Kt8hkRbLQuENRHlOeQw05/MVdoB1AWLOPzIXn4kex
GD9tHkoJl8S0QPmAAcPHn5O97e+gd0ze5dRQZl/cSd2/j5zeaMvZ1mCrPN/dOluM
94Oj+wQU4bEcOlrqIMSh0ezJw10R3IHXCQFeGtIZU57WmKcrryQX4kP7KTOgRw/t
CYp+NivQHtLbBEj1MU0l10qMS2+w8Qpqov4MdW4gx4wTgId2j1ZZ56+n6Jsc9qoI
wBWBNL4XU5a3kwhYZDOJoOvI9po33KLdT1dXS81uOFXClp3LGmKDgLTwQ1w+RmQG
+JG4EvTfDIShdcTDXEaOfCECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJM9Btu5g
k8qDiz5TilvpyoGuI4viCwusARFAFmOB/my/cHlVvkuq4bbfV1KJoWWGJg8GcklL
cnIdxc35uYM5icr6xXQyrW0GqAO+LEXyUxVQqYETxrQ/LJ03xhBnuF7hvZJIBiky
GwUy0clJxGfaCeEM8zXwePawLgGjuUawDDQOwigysoWqoMu3VFW8zl8UPa84bow9
Kn2QmPAkLw4EcqYSCNSSvnyzu5SM64jwLWRXFsmlqD7773oT29vTkqM1EQANFEfT
7gQomLyPqoPBoFph5oSNn6Rf31QX1Sie92EAKVnZ1XmD68hKzjv6ChCtzTv4jABg
XrDwnLkORIAF/Q==
-----END CERTIFICATE-----
EOS
end
def raw_key
<<-EOS
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD7gwf+PwFJkxJo
WMzdd56OCrfIZEWy0LhDUR5TnkMNOfzFXaAdQFizj8yF5+JHsRg/bR5KCZfEtED5
gAHDx5+Tve3voHdM3uXUUGZf3Endv4+c3mjL2dZgqzzf3TpbjPeDo/sEFOGxHDpa
6iDEodHsycNdEdyB1wkBXhrSGVOe1pinK68kF+JD+ykzoEcP7QmKfjYr0B7S2wRI
9TFNJddKjEtvsPEKaqL+DHVuIMeME4CHdo9WWeevp+ibHPaqCMAVgTS+F1OWt5MI
WGQziaDryPaaN9yi3U9XV0vNbjhVwpadyxpig4C08ENcPkZkBviRuBL03wyEoXXE
w1xGjnwhAgMBAAECggEAbw82GVui6uUpjLAhjm3CssAi1TcJ2+L0aq1IMe5Bd3ay
mkg0apY+VNPboQl6zuNxbJh3doPz42UhB8sxfE0Ktwd4KIb4Bxap7+2stwmkCGoN
NVy0c8d2NWuHzuZ2XXTK2vMu5Wd/HWD0l66o14sJEoEpZlB7yU216UevmjSayxjh
aBTSaYyyrf24haTaCuqwph/V73ZlMpFdSALGny0uiP/5inxciMCkMpHfX6BflSb4
EGKsIYt9BJ0kY4GNG5bCP7971UCxp2eEJhU2fV8HuFGCOD12IqSpUqPxHxjsWpfx
T7FZ3V2kM/58Ca+5LB2y3atcPIdY0/g7/43V4VD+7QKBgQD/PO4/0cmZuuLU1LPT
C/C596kPK0JLlvvRqhbz4byRAkW/n7uQFG7TMtFNle3UmT7rk7pjtbHnByqzEd+9
jMhBysjHOMg0+DWm7fEtSg/tJ3qLVO3nbdA4qmXYobLcLoG+PCYRLskEHHqTG/Bv
QZLbavOU6rrTqckNr1TMpNBmXwKBgQD8Q0C2YTOpwgjRUe8i6Chnc3o4x8a1i98y
9la6c7y7acWHSbEczMkNfEBrbM73rTb+bBA0Zqw+Z1gkv8bGpvGxX8kbSfJJ2YKW
9koxpLNTVNVapqBa9ImiaozV285dz9Ukx8bnMOJlTELpOl7RRV7iF0smYjfHIl3D
Yxyda/MtfwKBgHb9l/Dmw77IkqE4PFFimqqIHCe3OiP1UpavXh36midcUNoCBLYp
4HTTlyI9iG/5tYysBVQgy7xx6eUrqww6Ss3pVOsTvLp9EL4u5aYAhiZApm+4e2TO
HCmevvZcg/8EK3Zdoj2Wex5QjJBykQe9IVLrrH07ZTfySon3uGfjWkivAoGAGvqS
VC8HGHOw/7n0ilYr5Ax8mM/813OzFj80PVKdb6m7P2HJOFxKcE/Gj/aeF+0FgaZL
AV+tsirZSWzdNGesV5z35Bw/dlh11/FVNAP6TcI34y8I3VFj2uPsVf7hDjVpBTr8
ccNPoyfJzCm69ESoBiQZnGxKrNhnELtr1wYxhr8CgYApWwf4hVrTWV1zs+pEJenh
AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
0Ff8afd2Q/OfBeUdq9KA4JO9fNqzEwOWvv8Ryn4ZSYcAuLP7IVJKjjI6R7rYaO/G
3OWJdizbykGOi0BFDu+3dw==
-----END PRIVATE KEY-----
EOS
end
describe '.servers' do
it 'returns empty array if no server information is available' do
allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false)
@ -89,6 +148,42 @@ describe Gitlab::Auth::LDAP::Config do
expect(config.adapter_options[:encryption]).to include({ method: :start_tls })
end
it 'transforms SSL cert and key to OpenSSL objects' do
stub_ldap_config(
options: {
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'start_tls',
'tls_options' => {
'cert' => raw_cert,
'key' => raw_key
}
}
)
expect(config.adapter_options[:encryption][:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate)
expect(config.adapter_options[:encryption][:tls_options][:key]).to be_a(OpenSSL::PKey::RSA)
end
it 'logs an error when an invalid key or cert are configured' do
allow(Rails.logger).to receive(:error)
stub_ldap_config(
options: {
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'start_tls',
'tls_options' => {
'cert' => 'invalid cert',
'key' => 'invalid_key'
}
}
)
config.adapter_options
expect(Rails.logger).to have_received(:error).with(/LDAP TLS Options/).twice
end
context 'when verify_certificates is enabled' do
it 'sets tls_options to OpenSSL defaults' do
stub_ldap_config(
@ -130,7 +225,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
'ca_file' => '/etc/ca.pem'
'tls_options' => {
'ca_file' => '/etc/ca.pem'
}
}
)
@ -145,7 +242,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
'ca_file' => ' '
'tls_options' => {
'ca_file' => ' '
}
}
)
@ -160,7 +259,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
'ssl_version' => 'TLSv1_2'
'tls_options' => {
'ssl_version' => 'TLSv1_2'
}
}
)
@ -175,7 +276,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
'ssl_version' => ' '
'tls_options' => {
'ssl_version' => ' '
}
}
)
@ -223,6 +326,23 @@ describe Gitlab::Auth::LDAP::Config do
)
end
it 'transforms SSL cert and key to OpenSSL objects' do
stub_ldap_config(
options: {
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'start_tls',
'tls_options' => {
'cert' => raw_cert,
'key' => raw_key
}
}
)
expect(config.omniauth_options[:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate)
expect(config.omniauth_options[:tls_options][:key]).to be_a(OpenSSL::PKey::RSA)
end
context 'when verify_certificates is enabled' do
it 'specifies disable_verify_certificates as false' do
stub_ldap_config(
@ -261,11 +381,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
'ca_file' => '/etc/ca.pem'
'tls_options' => {
'ca_file' => '/etc/ca.pem'
}
}
)
expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' })
expect(config.omniauth_options[:tls_options]).to include({ ca_file: '/etc/ca.pem' })
end
end
@ -277,11 +399,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
'ca_file' => ' '
'tls_options' => {
'ca_file' => ' '
}
}
)
expect(config.omniauth_options).not_to have_key(:ca_file)
expect(config.omniauth_options[:tls_options]).not_to have_key(:ca_file)
end
end
@ -293,11 +417,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
'ssl_version' => 'TLSv1_2'
'tls_options' => {
'ssl_version' => 'TLSv1_2'
}
}
)
expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' })
expect(config.omniauth_options[:tls_options]).to include({ ssl_version: 'TLSv1_2' })
end
end
@ -309,11 +435,14 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
'ssl_version' => ' '
'tls_options' => {
'ssl_version' => ' '
}
}
)
expect(config.omniauth_options).not_to have_key(:ssl_version)
# OpenSSL default params includes `ssl_version` so we just check that it's not blank
expect(config.omniauth_options[:tls_options]).not_to include({ ssl_version: ' ' })
end
end
end