Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Windows to Actions CI, Ruby 2.5, misc fixes #20

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ on: [push, pull_request]

jobs:
build:
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
name: ${{ matrix.os }} ${{ matrix.ruby }}
strategy:
fail-fast: false
matrix:
ruby: [ '3.0', 2.7, 2.6, head ]
os: [ ubuntu-latest, macos-latest ]
os: [ ubuntu-20.04, macos-10.15, windows-2019 ]
ruby: [ '3.0', 2.7, 2.6, 2.5, head ]
include:
- { os: windows-2019 , ruby: mingw }
- { os: windows-2019 , ruby: mswin }
exclude:
- { os: windows-2019 , ruby: head }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
Expand All @@ -19,4 +25,4 @@ jobs:
- name: Install dependencies
run: bundle install
- name: Run test
run: rake test
run: bundle exec rake test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/pkg/
/spec/reports/
/tmp/
Gemfile.lock
33 changes: 0 additions & 33 deletions Gemfile.lock

This file was deleted.

2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "bundler/gem_tasks"
require "bundler/gem_tasks" if defined?(Bundler)
require "rake/testtask"

Rake::TestTask.new(:test) do |t|
Expand Down
2 changes: 1 addition & 1 deletion net-http.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
spec.summary = %q{HTTP client api for Ruby.}
spec.description = %q{HTTP client api for Ruby.}
spec.homepage = "https://github.com/ruby/net-http"
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
spec.licenses = ["Ruby", "BSD-2-Clause"]

spec.metadata["homepage_uri"] = spec.homepage
Expand Down
62 changes: 24 additions & 38 deletions test/net/http/test_https.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ def self.read_fixture(key)
File.read(File.expand_path("../fixtures/#{key}", __dir__))
end

HOST = 'localhost'
HOST_IP = '127.0.0.1'
CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem"))
SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key"))
SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt"))
DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem"))
TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) }

CONFIG = {
'host' => '127.0.0.1',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have the error output without this change?

I'm wondering if those rescue SystemCallError lines scattered over the file are relevant to this. If this (bind to "localhost") works, can they be removed safely now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They may be. Current mingw jobs in ruby/ruby are failing because of this (see https://github.com/ruby/ruby/actions). I don't have any CI logs in my fork. I fixed locally, then pushed...

I didn't look at any blame/history, but if it's never been tested on Windows...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://github.com/ruby/ruby/runs/2456377434?check_suite_focus=true:

   1) Failure:
TestNetHTTPS#test_session_reuse [D:/a/ruby/ruby/src/test/net/http/utils.rb:48]:
<[]> expected but was
<["[2021-04-28 10:35:33] ERROR Errno::ECONNABORTED: An established connection was aborted by the software in your host machine.\n" +
 "\tD:/a/ruby/ruby/build/.ext/common/openssl/buffering.rb:80:in `sysread'\n" +
 "\tD:/a/ruby/ruby/build/.ext/common/openssl/buffering.rb:80:in `fill_rbuff'\n" +
 "\tD:/a/ruby/ruby/build/.ext/common/openssl/buffering.rb:323:in `eof?'\n" +
 "\tD:/a/ruby/ruby/src/tool/lib/webrick/httpserver.rb:82:in `run'\n" +
 "\tD:/a/ruby/ruby/src/tool/lib/webrick/server.rb:310:in `block in start_thread'\n"]>.

  2) Failure:
TestNetHTTPS#test_session_reuse [D:/a/ruby/ruby/src/test/net/http/test_https.rb:166]:
<true> expected but was
<false>.

Thanks for the pointer, but this puzzles me... I was expecting the failing test cases to be test_get_SNI and alike, which were only recently added and didn't have rescue SystemCallError yet. (FWIW, the explanation for rescue is in 2504847.)

Copy link
Contributor Author

@MSP-Greg MSP-Greg Apr 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate this. I pushed the test file change to ruby-loco and my ruby/ruby fork. test-all passed everywhere.

But, in test-spec, it's timing out in ruby-loco, and there are errors in:
spec/ruby/library/net/http/http/active_spec.rb
spec/ruby/library/net/http/http/get2_spec.rb

Maybe more...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re the failures, thanks for looking further. I can revert that commit and the other rescue SystemCallError code?

I'm looking at the spec failures.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding localhost vs 127.0.0.1 and rescue, I /think/ the changes in this Pull Request are correct, but I can't confirm - I don't know in which environment it is actually needed. Without the changes, TestNetHTTPS#test_min_version must fail in such setup, but we have no such a setup in our CI (rubyci.org / GitHub Actions).

`test_get_SNI` was a wrong example. It does `http.ipaddr = config('host')`, so it was fine as is.

ruby/ruby's Github Actions failure on mingw (TestNetHTTPS#test_session_reuse) looks like a separate issue (as I wrote in my last comment).

Copy link
Contributor Author

@MSP-Greg MSP-Greg Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhenium

Thanks for your additional investigation. I saw ruby-loco failed, then went to ruby/ruby, and saw Ubuntu & macOS passing (along with CI here), but failing mingw, I assumed it was probably a test issue. Didn't even look at lib code here. I did review the code in ruby/openssl test_ssl_session.rb.

Anyway, re the sleep 2.5 addition, I think http.finish is closing the socket, and the handshake occurs when http.start is called (it calls connect_nonblock)? So I'm not sure what the sleep is affecting, as I would think keep_alive_timeout would be different than a 'data' timeout, but I haven't looked...

Also, wondering whether to define HOST_IP = '127.0.0.1' and use it for all refs in the test?

EDIT:
Re test_session_reuse, the following works:

http.start
http.get("/")
http.finish

http.start
assert_equal true, http.instance_variable_get(:@socket).io.session_reused?
assert_equal $test_net_http_data, http.get("/").body
http.finish

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#21 should fix TestNetHTTPS#test_session_reuse, though running the test suite takes 10 minutes on Windows. This is due to the slow fallback from IPv6 (localhost) to IPv4 (127.0.0.1) and this (your) Pull Request should resolve it.

Session resumption was not done correctly because the test case was establishing 3 TLS connections in total instead of the intended 2 due to the bug & a change in OpenSSL 1.1.1 which prohibited using a session ticket more than once in TLS 1.3 clients (openssl/openssl#6601).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change in OpenSSL 1.1.1

Thanks. I vaguely recalled something about that, but my finding it might have taken a while...

Should I change test_session_reuse as above? session_reused? is important, but the real test is can the socket be used, and hence, the body assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhenium

I removed a Rakefile change that wasn't needed. I revised the test as above, and also added the constant. Changed it to an IPv6 address locally, and all passed.

And, since I haven't contributed here previously, it needs approval for CI. Passed in my fork. Thanks.

'host' => HOST,
'proxy_host' => nil,
'proxy_port' => nil,
'ssl_enable' => true,
Expand All @@ -31,7 +33,7 @@ def self.read_fixture(key)
}

def test_get
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
certs = []
Expand All @@ -43,15 +45,13 @@ def test_get
assert_equal($test_net_http_data, res.body)
}
# TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility
certs.zip([CA_CERT, SERVER_CERT][-certs.size..]) do |actual, expected|
certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected|
assert_equal(expected.to_der, actual.to_der)
end
rescue SystemCallError
skip $!
end

def test_get_SNI
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.ipaddr = config('host')
http.use_ssl = true
http.cert_store = TEST_STORE
Expand All @@ -64,16 +64,16 @@ def test_get_SNI
assert_equal($test_net_http_data, res.body)
}
# TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility
certs.zip([CA_CERT, SERVER_CERT][-certs.size..]) do |actual, expected|
certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected|
assert_equal(expected.to_der, actual.to_der)
end
end

def test_get_SNI_proxy
TCPServer.open("127.0.0.1", 0) {|serv|
TCPServer.open(HOST_IP, 0) {|serv|
_, port, _, _ = serv.addr
client_thread = Thread.new {
proxy = Net::HTTP.Proxy("127.0.0.1", port, 'user', 'password')
proxy = Net::HTTP.Proxy(HOST_IP, port, 'user', 'password')
http = proxy.new("foo.example.org", 8000)
http.ipaddr = "192.0.2.1"
http.use_ssl = true
Expand Down Expand Up @@ -125,23 +125,21 @@ def test_get_SNI_failure
end

def test_post
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
data = config('ssl_private_key').to_der
http.request_post("/", data, {'content-type' => 'application/x-www-form-urlencoded'}) {|res|
assert_equal(data, res.body)
}
rescue SystemCallError
skip $!
end

def test_session_reuse
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
# See https://github.com/openssl/openssl/pull/5967 for details.
skip if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /OpenSSL 1.1.0h/

http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE

Expand All @@ -154,25 +152,21 @@ def test_session_reuse
end

http.start
assert_equal false, http.instance_variable_get(:@socket).io.session_reused?
http.get("/")
http.finish

http.start
http.get("/")

socket = http.instance_variable_get(:@socket).io
assert_equal true, socket.session_reused?

assert_equal true, http.instance_variable_get(:@socket).io.session_reused?
assert_equal $test_net_http_data, http.get("/").body
http.finish
rescue SystemCallError
skip $!
end

def test_session_reuse_but_expire
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
skip if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /OpenSSL 1.1.0h/

http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE

Expand All @@ -188,8 +182,6 @@ def test_session_reuse_but_expire
assert_equal false, socket.session_reused?

http.finish
rescue SystemCallError
skip $!
end

if ENV["RUBY_OPENSSL_TEST_ALL"]
Expand All @@ -204,14 +196,12 @@ def test_verify
end

def test_verify_none
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.request_get("/") {|res|
assert_equal($test_net_http_data, res.body)
}
rescue SystemCallError
skip $!
end

def test_skip_hostname_verification
Expand Down Expand Up @@ -240,14 +230,10 @@ def test_fail_if_verify_hostname_is_true
end

def test_certificate_verify_failure
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
ex = assert_raise(OpenSSL::SSL::SSLError){
begin
http.request_get("/") {|res| }
rescue SystemCallError
skip $!
end
http.request_get("/") {|res| }
}
assert_match(/certificate verify failed/, ex.message)
unless /mswin|mingw/ =~ RUBY_PLATFORM
Expand All @@ -262,25 +248,25 @@ def test_certificate_verify_failure

def test_identity_verify_failure
# the certificate's subject has CN=localhost
http = Net::HTTP.new("127.0.0.1", config("port"))
http = Net::HTTP.new(HOST_IP, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
@log_tester = lambda {|_| }
ex = assert_raise(OpenSSL::SSL::SSLError){
http.request_get("/") {|res| }
}
re_msg = /certificate verify failed|hostname \"127.0.0.1\" does not match/
re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/
assert_match(re_msg, ex.message)
end

def test_timeout_during_SSL_handshake
bug4246 = "expected the SSL connection to have timed out but have not. [ruby-core:34203]"

# listen for connections... but deliberately do not complete SSL handshake
TCPServer.open('localhost', 0) {|server|
TCPServer.open(HOST, 0) {|server|
port = server.addr[1]

conn = Net::HTTP.new('localhost', port)
conn = Net::HTTP.new(HOST, port)
conn.use_ssl = true
conn.read_timeout = 0.01
conn.open_timeout = 0.01
Expand All @@ -295,7 +281,7 @@ def test_timeout_during_SSL_handshake
end

def test_min_version
http = Net::HTTP.new("localhost", config("port"))
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.min_version = :TLS1
http.cert_store = TEST_STORE
Expand All @@ -305,7 +291,7 @@ def test_min_version
end

def test_max_version
http = Net::HTTP.new("127.0.0.1", config("port"))
http = Net::HTTP.new(HOST_IP, config("port"))
http.use_ssl = true
http.max_version = :SSL2
http.verify_callback = Proc.new do |preverify_ok, store_ctx|
Expand Down