[rubygems/rubygems] Diagnose the bare net/http connection
https://github.com/rubygems/rubygems/commit/38a0bdc123
This commit is contained in:
parent
7a10ce8c95
commit
19477ef287
@ -17,6 +17,8 @@ module Bundler
|
||||
output_ssl_environment
|
||||
bundler_success = bundler_connection_successful?
|
||||
rubygem_success = rubygem_connection_successful?
|
||||
|
||||
return unless net_http_connection_successful?
|
||||
end
|
||||
|
||||
private
|
||||
@ -92,6 +94,29 @@ module Bundler
|
||||
false
|
||||
end
|
||||
|
||||
def net_http_connection_successful?
|
||||
::Gem::Net::HTTP.new(uri.host, uri.port).tap do |http|
|
||||
http.use_ssl = true
|
||||
http.min_version = tls_version
|
||||
http.max_version = tls_version
|
||||
http.verify_mode = verify_mode
|
||||
end.start
|
||||
|
||||
Bundler.ui.info("Ruby net/http: success")
|
||||
|
||||
true
|
||||
rescue StandardError => error
|
||||
Bundler.ui.warn(<<~MSG)
|
||||
Ruby net/http: failed
|
||||
|
||||
Unfortunately, this Ruby can't connect to #{host}.
|
||||
|
||||
#{Explanation.explain_net_http_error(error, host, tls_version)}
|
||||
MSG
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
module Explanation
|
||||
extend self
|
||||
|
||||
@ -107,6 +132,57 @@ module Bundler
|
||||
error.message
|
||||
end
|
||||
end
|
||||
|
||||
def explain_net_http_error(error, host, tls_version)
|
||||
case error.message
|
||||
# Check for certificate errors
|
||||
when /certificate verify failed/
|
||||
<<~MSG
|
||||
#{show_ssl_certs}
|
||||
Your Ruby can't connect to #{host} because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine #{host} servers.
|
||||
MSG
|
||||
# Check for TLS version errors
|
||||
when /read server hello A/, /tlsv1 alert protocol version/
|
||||
if tls_version.to_s == "TLS1_3"
|
||||
"Your Ruby can't connect to #{host} because #{tls_version} isn't supported yet.\n"
|
||||
else
|
||||
<<~MSG
|
||||
Your Ruby can't connect to #{host} because your version of OpenSSL is too old.
|
||||
You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.
|
||||
MSG
|
||||
end
|
||||
# OpenSSL doesn't support TLS version specified by argument
|
||||
when /unknown SSL method/
|
||||
"Your Ruby can't connect because #{tls_version} isn't supported by your version of OpenSSL."
|
||||
else
|
||||
<<~MSG
|
||||
Even worse, we're not sure why.
|
||||
|
||||
Here's the full error information:
|
||||
#{error.class}: #{error.message}
|
||||
#{error.backtrace.join("\n ")}
|
||||
|
||||
You might have more luck using Mislav's SSL doctor.rb script. You can get it here:
|
||||
https://github.com/mislav/ssl-tools/blob/8b3dec4/doctor.rb
|
||||
|
||||
Read more about the script and how to use it in this blog post:
|
||||
https://mislav.net/2013/07/ruby-openssl/
|
||||
MSG
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_ssl_certs
|
||||
ssl_cert_file = ENV["SSL_CERT_FILE"] || OpenSSL::X509::DEFAULT_CERT_FILE
|
||||
ssl_cert_dir = ENV["SSL_CERT_DIR"] || OpenSSL::X509::DEFAULT_CERT_DIR
|
||||
|
||||
<<~MSG
|
||||
Below affect only Ruby net/http connections:
|
||||
SSL_CERT_FILE: #{File.exist?(ssl_cert_file) ? "exists #{ssl_cert_file}" : "is missing #{ssl_cert_file}"}
|
||||
SSL_CERT_DIR: #{Dir.exist?(ssl_cert_dir) ? "exists #{ssl_cert_dir}" : "is missing #{ssl_cert_dir}"}
|
||||
MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -30,73 +30,6 @@ puts "Ruby: %s" % ruby_version
|
||||
puts "RubyGems: %s" % Gem::VERSION if defined?(Gem::VERSION)
|
||||
puts "Bundler: %s" % Bundler::VERSION if defined?(Bundler::VERSION)
|
||||
|
||||
def show_ssl_certs
|
||||
puts "", "Below affect only Ruby net/http connections:"
|
||||
puts
|
||||
t = ENV['SSL_CERT_FILE'] || OpenSSL::X509::DEFAULT_CERT_FILE
|
||||
ssl_file = File.exist?(t) ? "✅ exists #{t}" : "❌ is missing #{t}"
|
||||
puts "SSL_CERT_FILE: %s" % ssl_file
|
||||
|
||||
t = ENV['SSL_CERT_DIR'] || OpenSSL::X509::DEFAULT_CERT_DIR
|
||||
ssl_dir = Dir.exist?(t) ? "✅ exists #{t}" : "❌ is missing #{t}"
|
||||
puts "SSL_CERT_DIR: %s" % ssl_dir
|
||||
puts
|
||||
end
|
||||
|
||||
begin
|
||||
# Try to connect using HTTPS
|
||||
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
||||
http.use_ssl = true
|
||||
if tls_version
|
||||
if http.respond_to? :min_version=
|
||||
vers = tls_version.sub("v", "").to_sym
|
||||
http.min_version = vers
|
||||
http.max_version = vers
|
||||
else
|
||||
http.ssl_version = tls_version.to_sym
|
||||
end
|
||||
end
|
||||
http.verify_mode = verify_mode
|
||||
end.start
|
||||
|
||||
puts "Ruby net/http: ✅ success"
|
||||
puts
|
||||
rescue => error
|
||||
puts "Ruby net/http: ❌ failed"
|
||||
puts
|
||||
puts "Unfortunately, this Ruby can't connect to #{host}. 😡"
|
||||
|
||||
case error.message
|
||||
# Check for certificate errors
|
||||
when /certificate verify failed/
|
||||
show_ssl_certs
|
||||
puts "\nYour Ruby can't connect to #{host} because you are missing the certificate",
|
||||
"files OpenSSL needs to verify you are connecting to the genuine #{host} servers.", ""
|
||||
# Check for TLS version errors
|
||||
when /read server hello A/, /tlsv1 alert protocol version/
|
||||
if tls_version == "TLSv1_3"
|
||||
puts "\nYour Ruby can't connect to #{host} because #{tls_version} isn't supported yet.\n\n"
|
||||
else
|
||||
puts "\nYour Ruby can't connect to #{host} because your version of OpenSSL is too old.",
|
||||
"You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.", ""
|
||||
end
|
||||
# OpenSSL doesn't support TLS version specified by argument
|
||||
when /unknown SSL method/
|
||||
puts "\nYour Ruby can't connect because #{tls_version} isn't supported by your version of OpenSSL.\n\n"
|
||||
else
|
||||
puts "\nEven worse, we're not sure why. 😕"
|
||||
puts
|
||||
puts "Here's the full error information:",
|
||||
"#{error.class}: #{error.message}",
|
||||
" #{error.backtrace.join("\n ")}"
|
||||
puts
|
||||
puts "You might have more luck using Mislav's SSL doctor.rb script. You can get it here:",
|
||||
"https://github.com/mislav/ssl-tools/blob/8b3dec4/doctor.rb",
|
||||
"Read more about the script and how to use it in this blog post:",
|
||||
"https://mislav.net/2013/07/ruby-openssl/", ""
|
||||
end
|
||||
exit 1
|
||||
end
|
||||
|
||||
guide_url = "http://ruby.to/ssl-check-failed"
|
||||
if bundler_status =~ /success/ && rubygems_status =~ /success/
|
||||
|
@ -67,6 +67,15 @@ RSpec.describe "bundle doctor ssl" do
|
||||
expected_err = <<~MSG
|
||||
Bundler: failed (certificate verification)
|
||||
RubyGems: failed (certificate verification)
|
||||
Ruby net/http: failed
|
||||
|
||||
Unfortunately, this Ruby can't connect to rubygems.org.
|
||||
|
||||
Below affect only Ruby net/http connections:
|
||||
SSL_CERT_FILE: exists #{OpenSSL::X509::DEFAULT_CERT_FILE}
|
||||
SSL_CERT_DIR: exists #{OpenSSL::X509::DEFAULT_CERT_DIR}
|
||||
|
||||
Your Ruby can't connect to rubygems.org because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine rubygems.org servers.
|
||||
|
||||
MSG
|
||||
|
||||
@ -100,11 +109,54 @@ RSpec.describe "bundle doctor ssl" do
|
||||
expected_err = <<~MSG
|
||||
Bundler: failed (SSL/TLS protocol version mismatch)
|
||||
RubyGems: failed (SSL/TLS protocol version mismatch)
|
||||
Ruby net/http: failed
|
||||
|
||||
Unfortunately, this Ruby can't connect to rubygems.org.
|
||||
|
||||
Your Ruby can't connect to rubygems.org because your version of OpenSSL is too old.
|
||||
You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.
|
||||
|
||||
MSG
|
||||
|
||||
expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
|
||||
end
|
||||
|
||||
it "fails due to unsupported tls 1.3 version" do
|
||||
net_http = Class.new(Artifice::Net::HTTP) do
|
||||
def connect
|
||||
raise OpenSSL::SSL::SSLError, "read server hello A"
|
||||
end
|
||||
end
|
||||
|
||||
Artifice.replace_net_http(net_http)
|
||||
Gem::Request::ConnectionPools.client = net_http
|
||||
Gem::RemoteFetcher.fetcher.close_all
|
||||
|
||||
expected_out = <<~MSG
|
||||
Here's your OpenSSL environment:
|
||||
|
||||
OpenSSL: #{OpenSSL::VERSION}
|
||||
Compiled with: #{OpenSSL::OPENSSL_VERSION}
|
||||
Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
|
||||
|
||||
Trying connections to https://rubygems.org:
|
||||
MSG
|
||||
|
||||
expected_err = <<~MSG
|
||||
Bundler: failed (SSL/TLS protocol version mismatch)
|
||||
RubyGems: failed (SSL/TLS protocol version mismatch)
|
||||
Ruby net/http: failed
|
||||
|
||||
Unfortunately, this Ruby can't connect to rubygems.org.
|
||||
|
||||
Your Ruby can't connect to rubygems.org because TLS1_3 isn't supported yet.
|
||||
|
||||
MSG
|
||||
|
||||
subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3")
|
||||
expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "when no diagnostic fails" do
|
||||
@ -119,11 +171,53 @@ RSpec.describe "bundle doctor ssl" do
|
||||
Trying connections to https://rubygems.org:
|
||||
Bundler: success
|
||||
RubyGems: success
|
||||
Ruby net/http: success
|
||||
|
||||
MSG
|
||||
|
||||
subject = Bundler::CLI::Doctor::SSL.new({})
|
||||
expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr
|
||||
end
|
||||
|
||||
it "uses the tls_version verify mode and host when given as option" do
|
||||
net_http = Class.new(Artifice::Net::HTTP) do
|
||||
class << self
|
||||
attr_accessor :verify_mode, :min_version, :max_version
|
||||
end
|
||||
|
||||
def connect
|
||||
self.class.verify_mode = verify_mode
|
||||
self.class.min_version = min_version
|
||||
self.class.max_version = max_version
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
net_http.endpoint = @dummy_endpoint
|
||||
Artifice.replace_net_http(net_http)
|
||||
Gem::Request::ConnectionPools.client = net_http
|
||||
Gem::RemoteFetcher.fetcher.close_all
|
||||
|
||||
expected_out = <<~MSG
|
||||
Here's your OpenSSL environment:
|
||||
|
||||
OpenSSL: #{OpenSSL::VERSION}
|
||||
Compiled with: #{OpenSSL::OPENSSL_VERSION}
|
||||
Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
|
||||
|
||||
Trying connections to https://example.org:
|
||||
Bundler: success
|
||||
RubyGems: success
|
||||
Ruby net/http: success
|
||||
|
||||
MSG
|
||||
|
||||
subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3", "verify-mode": :none, host: "example.org")
|
||||
expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr
|
||||
expect(net_http.verify_mode).to eq(0)
|
||||
expect(net_http.min_version.to_s).to eq("TLS1_3")
|
||||
expect(net_http.max_version.to_s).to eq("TLS1_3")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user