-
Notifications
You must be signed in to change notification settings - Fork 48
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
feat: Handle x509 certs in HTTP Client #142
feat: Handle x509 certs in HTTP Client #142
Conversation
Thanks for this! I think env vars are a nice touch, but I wonder if having CLI options for these would be a preferred starting point for consistency with other flags? In any case, it's a niche case, so if this supports your need then that shouldn't stop this from going through, assuming we're happy with the env var naming and approach. In terms of consistency with env var naming, the convention is So |
@@ -127,6 +133,18 @@ def verbose? | |||
verbose || ENV["VERBOSE"] == "true" | |||
end | |||
|
|||
def custom_x509_certificate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this useful? Can we just use the presence of the env vars to work out whether to set the cert/key or not, the same way we do for the SSL cert env vars?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, good idea!
I have renamed the method and handled the presence validation for the env vars in the method.
I think it's better to have a single method with this logic rather than have it in two lines repeated.
Although, if you prefer to remove this method I can move the logic to each line 😄
end | ||
|
||
def x509_cert_file | ||
File.read(ENV['X509_CERT_FILE_PATH']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Matt said, we do usually prefix all env vars with PACT_
. We use SSL_CERT_FILE
and SSL_CERT_DIR
because they are pre-existing Unix standards, however.
There's no such standard for x509 certificates that I can find though, so we need to decide whether to make it match the SSL cert env vars, or the rest of the PACT env vars. The SSL env vars and the x509 env vars are likely to be used together, aren't they, given that this is about doing mutual certificate authentication. Given this is the client cert (not the server cert) it might make sense to include that in the name.
I think the most consistent approach would be to call them X509_CLIENT_CERT_FILE
and X509_CLIENT_KEY_FILE
.
We don't have command line arguments for the SSL certificates, so I don't think we need to add them for the x509 certs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are client certificates, that's correct. I agree with including that in the name for clarity.
I don't see anywhere in this code that requires the client to confirm the certificate (authenticity) of the server - so perhaps that's forthcoming, or not required for the use case.
i.e. as it stands, a malicious server could present a valid certificate and if it's in the CLI's CA then there are no client-side checks to ensure the requests are going to the right place.
Testing is going to be a bit fiddly, but we can copy from some prior art Here's a test that executes a request to a webrick server set up with SSL This is the file it uses to run the server: https://github.com/pact-foundation/pact_broker/blob/master/spec/support/ssl_webhook_server.rb Looks like there is a verify client option I'm guessing we would need to add |
Just kicked off CI, thanks for the updates post review Gerard |
Thanks @YOU54F. |
ty, fyi, you can ignore the failing pact and can-i-deploy test, they are failing as due to it being a PR from a outside collab, you can't retrieve secrets, in order to login to the pact broker for the tests, The other tests should be a healthy indicator though 👍🏾 |
before(:all) do | ||
@pipe = IO.popen("bundle exec ruby ./spec/support/ssl_server.rb") | ||
ENV['SSL_CERT_FILE'] = "./spec/fixtures/certificates/ca_cert.pem" | ||
ENV['SSL_CERT_DIR'] = "./spec/fixtures/certificates/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SSL_CERT_FILE is set, is SSL_CERT_DIR necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
I thought both were required, but seems like with the SSL_CERT_FILE is enough.
it "fails raising SSL error" do | ||
expect { do_get } | ||
.to raise_error { |error| | ||
expect([OpenSSL::SSL::SSLError, Errno::ECONNRESET]).to include(error.class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's curious. It could raise either error? Can you tell me more about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like so.
I ran the server in debug mode to check why this happening, and I got the following output by running it in:
ubuntu-latest ruby v3.1
[2023-09-20 08:52:01] INFO WEBrick::HTTPServer#start: pid=508 port=4444
| with valid x509 client certificates
| succeeds
| when invalid x509 certificates are set
| [2023-09-20 08:52:01] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:49104 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:52:01] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:49106 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| ERROR: Error making request - Errno::ECONNRESET Connection reset by peer /Users/jobandtalent/jobandtalent/pact_broker-client/lib/pact_broker/client/hal/http_client.rb:87:in `block (2 levels) in perform_request', attempt 1 of 5
| [2023-09-20 08:52:06] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:49112 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:52:06] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:49122 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| ERROR: Error making request - Errno::ECONNRESET Connection reset by peer /Users/jobandtalent/jobandtalent/pact_broker-client/lib/pact_broker/client/hal/http_client.rb:87:in `block (2 levels) in perform_request', attempt 2 of 5
| [2023-09-20 08:52:12] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:43724 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:52:12] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:43736 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| ERROR: Error making request - Errno::ECONNRESET Connection reset by peer /Users/jobandtalent/jobandtalent/pact_broker-client/lib/pact_broker/client/hal/http_client.rb:87:in `block (2 levels) in perform_request', attempt 3 of 5
| [2023-09-20 08:52:17] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:43752 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:52:17] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:43764 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| ERROR: Error making request - Errno::ECONNRESET Connection reset by peer /Users/jobandtalent/jobandtalent/pact_broker-client/lib/pact_broker/client/hal/http_client.rb:87:in `block (2 levels) in perform_request', attempt 4 of 5
| [2023-09-20 08:52:22] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:44812 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:52:22] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 peeraddr=127.0.0.1:44818 state=error: certificate verify failed (self-signed certificate)
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/3.1.4/x64/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| ERROR: Error making request - Errno::ECONNRESET Connection reset by peer /Users/jobandtalent/jobandtalent/pact_broker-client/lib/pact_broker/client/hal/http_client.rb:87:in `block (2 levels) in perform_request', attempt 5 of 5
| fails raising SSL error
ubuntu-latest ruby v2.7
with valid x509 client certificates
| succeeds
| when invalid x509 certificates are set
| [2023-09-20 08:59:59] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 state=error: certificate verify failed (self signed certificate)
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| [2023-09-20 08:59:59] ERROR OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 state=error: certificate verify failed (self signed certificate)
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `accept'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:302:in `block (2 levels) in start_thread'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/utils.rb:258:in `timeout'
| /opt/hostedtoolcache/Ruby/2.7.8/x64/lib/ruby/gems/2.7.0/gems/webrick-1.8.1/lib/webrick/server.rb:300:in `block in start_thread'
| fails raising SSL error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like depending on the OS and ruby version it handles the verification error differently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, thanks for digging into that.
Can you check for me what the verify_mode is when the disable ssl verification is not set? I can't tell what the default is from the docs at https://docs.ruby-lang.org/en//3.2/Net/HTTP.html |
In case the Additionally, library code that applies this default can be found here |
Can you add docs under the SSL cert section here https://github.com/pact-foundation/pact-ruby-standalone/blob/master/README.md for the pact-ruby-standalone and under the "Using a custom certificate" section here https://github.com/pact-foundation/pact-ruby-cli/blob/master/README.md#using-a-custom-certificate for the pact-cli |
@gmolki the specs for this have suddenly started failing. Can you have a look please? https://github.com/pact-foundation/pact_broker-client/actions/runs/6606534979/job/17942830227#step:5:858 |
I've worked out what it is - it's the new version of rack that was introduced by the pact mock service. Fixed it. |
Goal of this PR is to enable the usage of x509 certificates in http requests.
Related PR in pact-support: pact-foundation/pact-support#99