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

Fix rejecting HTTP requests when TLS1.3 is used by server #2116

Merged
merged 3 commits into from May 15, 2020

Conversation

MSP-Greg
Copy link
Member

Description

Closes #2115

  • I have reviewed the guidelines for contributing to this repository.
  • I have added an entry to History.md if this PR fixes a bug or adds a feature. If it doesn't need an entry to HISTORY.md, I have added [changelog skip] the pull request title.
  • I have added appropriate tests if this PR fixes a bug or adds a feature.
  • My pull request is 100 lines added/removed or less so that it can be easily reviewed.
  • If this PR doesn't need tests (docs change), I added [ci skip] to the title of the PR.
  • If this closes any issues, I have added "Closes #issue" to the PR description or my commit messages.
  • I have updated the documentation accordingly.
  • All new and existing tests passed, including Rubocop.

# see OpenSSL ssl/ssl_stat.c SSL_state_string for info
#
def ssl_version_state
::Puma::IS_JRUBY ? ['', ''] : @engine.ssl_vers_st.map(&:rstrip)
Copy link
Member

Choose a reason for hiding this comment

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

This is because you didn't implement a JRuby version, right? Not because the JRuby SSL version should actually return different values?

Copy link
Member

Choose a reason for hiding this comment

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

Or our JRuby extension doesn't support TLS 1.3?

Copy link
Member Author

@MSP-Greg MSP-Greg Mar 5, 2020

Choose a reason for hiding this comment

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

I wrote the code quite a while ago. At the time, JRuby didn't support TLSv1.3 yet, and I opened an issue somewhere asking about it, didn't really get a response.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, got it.

Copy link
Member

Choose a reason for hiding this comment

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

@MSP-Greg is this the issue? jruby/jruby-openssl#184

Copy link
Member Author

Choose a reason for hiding this comment

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

@dentarg Yes

@nateberkopec
Copy link
Member

nateberkopec commented Mar 5, 2020

Wondering what your response is to #2115 (comment)

@nateberkopec nateberkopec added c-ext ssl waiting-for-changes Waiting on changes from the requestor and removed waiting-for-review Waiting on review from anyone labels Mar 5, 2020
@MSP-Greg
Copy link
Member Author

MSP-Greg commented Mar 5, 2020

Wondering what your response is to

Part of the issue is slow connections, so the code has to wait for some interval to determine if a handshake has completed. The test seems to show that it's rejected, whereas currently it is not. Also, I don't recall what 'state' is as a protocol is negotiated down from TLSv1.3 to TLSv1.2.

What should the code do with the connection as far a response or logging? It's kind like dictionary attacks on mail servers. What's the best way to respond? Let it disappear, respond with unknown whatever, etc...

@MSP-Greg
Copy link
Member Author

MSP-Greg commented Mar 5, 2020

As to why I made this a draft PR...

This is one potential fix for what could be an attack vector, in that http requests to a Puma https server do need to be 'cleaned out' in a timely fashion. But:

  1. Does it affect normal throughput?

  2. How should these https requests be handled? Should the handling change depending on whether Puma is the 'front end' server or behind something like NGINX? If so, how to allow configuration of the handling?

output = @engine.read
return output if bad_tlsv1_3?
rescue Puma::MiniSSL::SSLError => e
if e.message.end_with? 'System error: Undefined error: 0 - 0'
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look robust to me, matching against the message... do we know where does this error come from? What does Undefined error: 0 - 0 mean?

At #1708 (comment) I saw the similar but different error

Error in reactor loop escaped: System error: Success - 0 (Puma::MiniSSL::SSLError)

Copy link
Member Author

Choose a reason for hiding this comment

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

@dentarg

matching against the message

I wasn't happy with using a string match either. Let me get back into this and see what can be done.

What does Undefined error: 0 - 0 mean?

Similar to what happened with 1.1.1e. The error previously had a numeric code of 0, then with 1.1.1e they changed the code, which was a breaking change. I'll look at the code for Puma::MiniSSL::SSLError and see if better info can be passed from the c code.

This issue is messy, as flooding an https server with http connections could be used as an DOS attack.

We want Puma to reject http connections, and we want that done as quickly as possible so a minimal amount of resources are used by them. We also don't want to slow down real https connections.

The issue is made worse by the fact that TLSv1.3 and TLSv1.2 handle http connections differently, and right now, I can't recall that exact difference. As I recall, TLSv1.2 handled the problem itself, TLSv1.3 forces Puma to deal with it...

@nateberkopec
Copy link
Member

How should these https requests be handled? Should the handling change depending on whether Puma is the 'front end' server or behind something like NGINX? If so, how to allow configuration of the handling?

@MSP-Greg Any further thoughts on this front?

@MSP-Greg
Copy link
Member Author

MSP-Greg commented May 8, 2020

@nateberkopec

Any further thoughts on this front?

Maybe. Question - OpenSSL 1.0.2 is EOL. Are you ok with adding methods that will only be available with OpenSSL 1.1.0 or later? I don't think many people are using 1.1.0, so it's really about 1.1.1. For instance, the context min/max protocol methods in newer Ruby versions maps directly to new OpenSSL methods, and translating them back into 1.0.2 is a PITA.

I think I mentioned it somewhere else, let me look at the SSL error issue and see what can be done. I've got time this weekend...

@nateberkopec
Copy link
Member

This is related to the Ruby version issue, no? What's the compat between Ruby versions and OpenSSL versions look like?

@MSP-Greg
Copy link
Member Author

MSP-Greg commented May 8, 2020

What's the compat between Ruby versions and OpenSSL versions look like?

It's a little grey.

Generally, Ruby 2.2 and 2.3 are considered incompatible with OpenSSL 1.1.0. The 2.2 and 2.3 builds available on Actions for all OS's use 1.0.2.

Re Ruby 2.4, the Actions Ubuntu & macOS builds are done using 1.1.1, but interestingly, you cannot force a TLSv1.3 connection without an error. Whether they'll connect when left alone, I haven't determined.

But, the current Ruby OpenSSL gem (2.1.2) is compatible with Ruby 2.3. So, a crazy person could probably install it using OpenSSL 1.1.1 on Ruby 2.3. I haven't tried that.

Since I wanted to double check some things, I finally created some notes on release dates, etc.

       OpenSSL
Ruby   stdlib               OpenSSL
                            1.1.0 pre 2015-Dec-10
2.3     1.1.1  2015-Dec
                            1.1.0     2016-Aug-25
2.4     2.0.9  2016-Dec
2.5     2.1.2  2017-Dec
                            1.1.1 pre 2018-Feb-13  * includes TLSv1.3
                            1.1.1     2018-Sep-11
2.6     2.1.2  2018-Dec
2.7     2.1.2  2019-Dec
2.8     2.2.0

@nateberkopec
Copy link
Member

Are you ok with adding methods that will only be available with OpenSSL 1.1.0 or later?

So then IMO the answer is "only if you can still run Puma with SSL on on ruby 2.3+". Adding things in a backwards-compatible way or gating new features behind OpenSSL 1.1.0 is IMO fine.

@MSP-Greg
Copy link
Member Author

MSP-Greg commented May 8, 2020

Adding things in a backwards-compatible way or gating new features behind OpenSSL 1.1.0 is IMO fine.

Just to be clear, if one writes code making use of the new features (using a Ruby compiled with OpenSSL 1.1.1), then one tries to run the same code with an older version of OpenSSL, it's ok that it won't run, since the methods won't be defined?

BTW, I don't recall how much this affects the changes I'd like to make to the SSL error handling.

@nateberkopec
Copy link
Member

it's ok that it won't run

Maybe even stronger, perhaps, we should blow up and exception in that situation. If you say something in your config explicitly that uses Open SSL 1.1+, and then in production you only have 1.0 available at runtime, we should probably kick and scream about that.

@MSP-Greg
Copy link
Member Author

I got a little side tracked with MSYS2 jumping from gcc 9.2.0 to 10.1.0 this weekend...

On the one hand, I agree. On the other hand, if someone is touching a production server and they don't understand the importance of having similar OpenSSL versions for development and production, maybe they need to learn a lesson?

Anyway, we could set it up so the methods that require 1.1.1 are undefined if compiled with 1.0?

@nateberkopec
Copy link
Member

Anyway, we could set it up so the methods that require 1.1.1 are undefined if compiled with 1.0?

Sure, let's give it a shot.

1. Add method Puma::MiniSSL::Engine#ssl_vers_st.  This returns connection protocol version and SSL_state_string info.

2. Add 12 bit mask for ssl erors of type SSL_ERROR_SSL that do not involve certificate verification.  This translates numbers suffixing error message to match numbers in OpenSSL's 'SSL reason codes' defined  include/openssl/sslerr.h
Changes to Puma::MiniSSL

1. Add HAS_TLS1_3 constant.
2. Add #bad_tlsv1_3? method, used to determine if an http  connection to an https server has been made.  TLSv1.3 behaves differently than previous TLS versions.
3. Change #engine_read_all to close http connections.
4. Add #ssl_version_state method, unused at present.
@MSP-Greg MSP-Greg marked this pull request as ready for review May 13, 2020 17:44
@MSP-Greg
Copy link
Member Author

  1. Yanked out some code that wasn't needed.
  2. macOS JRuby failure is a stable test issue?
  3. Haven't rerun PR Run test_http_rejection without code updates [changelog skip] #2117, but it failed locally without the lib/ext code changes.

@nateberkopec nateberkopec merged commit 91e57f4 into puma:master May 15, 2020
@nateberkopec
Copy link
Member

JRuby definitely a stability issue, made a new issue about it

@MSP-Greg MSP-Greg deleted the ssl-vers branch September 20, 2020 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug c-ext ssl waiting-for-changes Waiting on changes from the requestor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Puma errors and performs badly when receiving a non-HTTPS request while TLSv1.3-configured
3 participants