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
Automatic SSL certificate provisioning for localhost #2610
Merged
Merged
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
90353b0
Added puma to automatically use localhost gem to self signed https if…
5c1b82f
Update files according to rubocop rules
ye-lin-aung 0c5ff76
Moved localhost authority from tcp_listener to ssl_listener
ye-lin-aung 4b448ce
Reformatted codes according to rubocop rules
ye-lin-aung e3cace7
Fixed test case crashing in production env
ye-lin-aung 944cc0d
Removed transform_keys to support ruby version < 2.5.0
ye-lin-aung 0f33961
Changed wrong keystore_pass key given to context to keystore-pass
ye-lin-aung cc359e6
Remove accept_nonblock.rb since we are using MiniSSL Server
ye-lin-aung 014c703
Removed localhost_authority test case running in JRUBY since localhos…
ye-lin-aung c72f8de
Reload Localhost authority if not loaded runned from puma cli
ye-lin-aung c44c1df
Memorise localhost authority object on init
ye-lin-aung c459993
Added readme for self-signed certificates
ye-lin-aung f3126e3
Removed jruby version
ye-lin-aung d9b9e20
Update readme.md
ye-lin-aung 29c5763
Merged with master
ye-lin-aung 05c4926
Added validations to check certificate
ye-lin-aung f3ced89
Remove ssl test running in no ssl implementations
ye-lin-aung 54f07d1
Changed host in localhost authority
ye-lin-aung 345e6cc
Update ssl events wrong arguments error
ye-lin-aung 525b288
Update README.md
nateberkopec 522c286
Update binder.rb
nateberkopec de84ffd
Update binder.rb
nateberkopec d8b0451
Update binder.rb
nateberkopec 0699664
Update binder.rb
nateberkopec b07434c
Update request.rb
nateberkopec 540a971
Update binder
ye-lin-aung c71b062
Removed running test in JRUBY
ye-lin-aung 1ec13b6
Removed test for jruby
ye-lin-aung c09b3dc
Merge branch 'master' of github.com:puma/puma
ye-lin-aung fe16b29
Removed testing localhost authority file while in JRUBY
ye-lin-aung ca27c11
Removed unused variables
ye-lin-aung ca3fa6e
Updated readme
ye-lin-aung File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION | |
end | ||
|
||
gem 'm' | ||
gem "localhost", require: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
require 'openssl' | ||
|
||
module OpenSSL | ||
module SSL | ||
class SSLServer | ||
unless public_method_defined? :accept_nonblock | ||
def accept_nonblock | ||
sock = @svr.accept_nonblock | ||
|
||
begin | ||
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) | ||
ssl.sync_close = true | ||
ssl.accept if @start_immediately | ||
ssl | ||
rescue SSLError => ex | ||
if ssl | ||
ssl.close | ||
else | ||
sock.close | ||
end | ||
raise ex | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# Nothing in this file runs if Puma isn't compiled with ssl support | ||
# | ||
# helper is required first since it loads Puma, which needs to be | ||
# loaded so HAS_SSL is defined | ||
require_relative "helper" | ||
|
||
if ::Puma::HAS_SSL | ||
require "puma/events" | ||
require "net/http" | ||
require "localhost/authority" | ||
|
||
class SSLEventsHelper < ::Puma::Events | ||
attr_accessor :addr, :cert, :error | ||
|
||
def ssl_error(error, ssl_socket) | ||
self.error = error | ||
self.addr = ssl_socket.peeraddr.last rescue "<unknown>" | ||
self.cert = ssl_socket.peercert | ||
end | ||
end | ||
|
||
# net/http (loaded in helper) does not necessarily load OpenSSL | ||
require "openssl" unless Object.const_defined? :OpenSSL | ||
|
||
puts "", RUBY_DESCRIPTION, "RUBYOPT: #{ENV['RUBYOPT']}", | ||
ye-lin-aung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
" OpenSSL", | ||
"OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}", | ||
" OPENSSL_VERSION: #{OpenSSL::OPENSSL_VERSION}", "" | ||
end | ||
|
||
class TestPumaLocalhostAuthority < Minitest::Test | ||
nateberkopec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
parallelize_me! | ||
def setup | ||
@http = nil | ||
@server = nil | ||
end | ||
|
||
def teardown | ||
@http.finish if @http && @http.started? | ||
@server.stop(true) if @server | ||
end | ||
|
||
# yields ctx to block, use for ctx setup & configuration | ||
def start_server | ||
@host = "127.0.0.1" | ||
app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } | ||
@events = SSLEventsHelper.new STDOUT, STDERR | ||
|
||
@server = Puma::Server.new @app, @events | ||
@server.app = app | ||
@port = (@server.add_tcp_listener @host, 0).addr[1] | ||
|
||
@http = Net::HTTP.new @host, @port | ||
@http.use_ssl = true | ||
# Disabling verification since its self signed | ||
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||
# @http.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||
|
||
@server.run | ||
end | ||
|
||
def test_url_scheme_for_https | ||
start_server | ||
body = nil | ||
@http.start do | ||
req = Net::HTTP::Get.new "/", {} | ||
@http.request(req) do |rep| | ||
body = rep.body | ||
end | ||
end | ||
|
||
assert_equal "https", body | ||
end | ||
|
||
def test_request_wont_block_thread | ||
start_server | ||
# Open a connection and give enough data to trigger a read, then wait | ||
ctx = OpenSSL::SSL::SSLContext.new | ||
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||
port = @server.connected_ports[0] | ||
socket = OpenSSL::SSL::SSLSocket.new TCPSocket.new(@host, port), ctx | ||
socket.connect | ||
socket.write "HEAD" | ||
sleep 0.1 | ||
|
||
# Capture the amount of threads being used after connecting and being idle | ||
thread_pool = @server.instance_variable_get(:@thread_pool) | ||
busy_threads = thread_pool.spawned - thread_pool.waiting | ||
|
||
socket.close | ||
|
||
# The thread pool should be empty since the request would block on read | ||
# and our request should have been moved to the reactor. | ||
assert busy_threads.zero?, "Our connection is monopolizing a thread" | ||
end | ||
|
||
def test_very_large_return | ||
start_server | ||
giant = "x" * 2056610 | ||
@server.app = proc do | ||
[200, {}, [giant]] | ||
end | ||
|
||
body = nil | ||
@http.start do | ||
req = Net::HTTP::Get.new "/" | ||
@http.request(req) do |rep| | ||
body = rep.body | ||
end | ||
end | ||
|
||
assert_equal giant.bytesize, body.bytesize | ||
end | ||
|
||
def test_form_submit | ||
start_server | ||
body = nil | ||
@http.start do | ||
req = Net::HTTP::Post.new '/' | ||
req.set_form_data('a' => '1', 'b' => '2') | ||
|
||
@http.request(req) do |rep| | ||
body = rep.body | ||
end | ||
|
||
end | ||
|
||
assert_equal "https", body | ||
end | ||
|
||
def test_http_rejection | ||
body_http = nil | ||
body_https = nil | ||
start_server | ||
http = Net::HTTP.new @host, @server.connected_ports[0] | ||
http.use_ssl = false | ||
http.read_timeout = 6 | ||
|
||
tcp = Thread.new do | ||
req_http = Net::HTTP::Get.new "/", {} | ||
# Net::ReadTimeout - TruffleRuby | ||
assert_raises(Errno::ECONNREFUSED, EOFError, Net::ReadTimeout, Errno::ECONNRESET) do | ||
http.start.request(req_http) { |rep| body_http = rep.body } | ||
end | ||
end | ||
|
||
ssl = Thread.new do | ||
@http.start do | ||
req_https = Net::HTTP::Get.new "/", {} | ||
@http.request(req_https) { |rep_https| body_https = rep_https.body } | ||
end | ||
end | ||
|
||
tcp.join | ||
ssl.join | ||
http.finish | ||
sleep 1.0 | ||
|
||
assert_nil body_http | ||
assert_equal "https", body_https | ||
|
||
thread_pool = @server.instance_variable_get(:@thread_pool) | ||
busy_threads = thread_pool.spawned - thread_pool.waiting | ||
|
||
assert busy_threads.zero?, "Our connection is wasn't dropped" | ||
end | ||
end if ::Puma::HAS_SSL |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Try to do this in the top level process (I assume this is) because there are some race conditions which I need to fix - if multiple processes try to get the same certificate at the same time, they may clobber each other - in theory not a big problem (especially for development) but you can simply avoid it by ensuring it's only called once per session and at some point I'll make sure it's more robust. See socketry/localhost#1 for more details.
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.
By the way in several years of using It, I've not even had a single bug report about it. So, it's kind of a non issue in practice.
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.
Okay, I fixed it :) Still do it in the top level process to avoid generating a different certificate per worker if possible :)