From e3a05b05263eb3cf6c8ddde5f6c4b0a1490f4036 Mon Sep 17 00:00:00 2001 From: Marcin Olichwirowicz Date: Thu, 23 Sep 2021 22:33:04 +0200 Subject: [PATCH 1/4] Support localhost integratin in ssl_bind --- lib/puma/dsl.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index c3e9337513..b2d82578cf 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -46,6 +46,14 @@ def self.ssl_bind_str(host, port, opts) else '' end + if ['127.0.0.1', 'localhost'].include?(host) + key = "#{ENV["HOME"]}/.localhost/localhost.key" + cert = "#{ENV["HOME"]}/.localhost/localhost.crt" + end + + key = opts[:key] if opts[:key] + cert = opts[:cert] if opts[:cert] + ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify) if defined?(JRUBY_VERSION) @@ -63,7 +71,7 @@ def self.ssl_bind_str(host, port, opts) v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil - "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \ + "ssl://#{host}:#{port}?cert=#{cert}&key=#{key}" \ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}" end end @@ -439,6 +447,12 @@ def threads(min, max) # Instead of `bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'` you # can also use the this method. # + # When binding on localhost you don't need to specify cert and key - it will assume you are + # using localhost gem and will try to load appropriate files for you + # + # @example + # ssl_bind '127.0.0.1', '9292' + # # @example # ssl_bind '127.0.0.1', '9292', { # cert: path_to_cert, @@ -454,7 +468,8 @@ def threads(min, max) # ssl_cipher_list: cipher_list, # optional # verify_mode: verify_mode # default 'none' # } - def ssl_bind(host, port, opts) + # + def ssl_bind(host, port, opts = {}) bind self.class.ssl_bind_str(host, port, opts) end From 2f41a31846b95bcc01868eb7ff4ff2acf531458f Mon Sep 17 00:00:00 2001 From: Marcin Olichwirowicz Date: Fri, 1 Oct 2021 19:38:45 +0200 Subject: [PATCH 2/4] Feedback changes --- lib/puma/binder.rb | 30 +++++------------------------ lib/puma/dsl.rb | 10 +--------- lib/puma/minissl/context_builder.rb | 27 ++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/lib/puma/binder.rb b/lib/puma/binder.rb index 6151889e79..d9bee423b1 100644 --- a/lib/puma/binder.rb +++ b/lib/puma/binder.rb @@ -57,7 +57,6 @@ def initialize(events, conf = Configuration.new) @envs = {} @ios = [] - localhost_authority end attr_reader :ios @@ -229,11 +228,6 @@ def parse(binds, logger, log_msg = 'Listening') params = Util.parse_query uri.query - # If key and certs are not defined and localhost gem is required. - # localhost gem will be used for self signed - # Load localhost authority if not loaded. - ctx = localhost_authority && localhost_authority_context if params.empty? - ctx ||= MiniSSL::ContextBuilder.new(params, @events).context if fd = @inherited_fds.delete(str) @@ -246,9 +240,11 @@ def parse(binds, logger, log_msg = 'Listening') ios_len = @ios.length io = add_ssl_listener uri.host, uri.port, ctx + uri_query = params.map { |k, v| "#{k}=#{v}" }.join('&') + @ios[ios_len..-1].each do |i| addr = loc_addr_str i - logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}" + logger.log "* #{log_msg} on ssl://#{addr}?#{uri_query}" end end @@ -292,22 +288,6 @@ def parse(binds, logger, log_msg = 'Listening') end end - def localhost_authority - @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY - end - - def localhost_authority_context - return unless localhost_authority - - key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) } - [localhost_authority.key_path, localhost_authority.certificate_path] - else - local_certificates_path = File.expand_path("~/.localhost") - [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")] - end - MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context - end - # Tell the server to listen on host +host+, port +port+. # If +optimize_for_latency+ is true (the default) then clients connecting # will be optimized for latency over throughput. @@ -348,7 +328,7 @@ def add_ssl_listener(host, port, ctx, raise "Puma compiled without SSL support" unless HAS_SSL # Puma will try to use local authority context if context is supplied nil - ctx ||= localhost_authority_context + ctx ||= MiniSSL::ContextBuilder.new({}, @events).context if host == "localhost" loopback_addresses.each do |addr| @@ -377,7 +357,7 @@ def add_ssl_listener(host, port, ctx, def inherit_ssl_listener(fd, ctx) raise "Puma compiled without SSL support" unless HAS_SSL # Puma will try to use local authority context if context is supplied nil - ctx ||= localhost_authority_context + ctx ||= MiniSSL::ContextBuilder.new({}, @events).context s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd) diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index b2d82578cf..eaf9c1046f 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -46,14 +46,6 @@ def self.ssl_bind_str(host, port, opts) else '' end - if ['127.0.0.1', 'localhost'].include?(host) - key = "#{ENV["HOME"]}/.localhost/localhost.key" - cert = "#{ENV["HOME"]}/.localhost/localhost.crt" - end - - key = opts[:key] if opts[:key] - cert = opts[:cert] if opts[:cert] - ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify) if defined?(JRUBY_VERSION) @@ -71,7 +63,7 @@ def self.ssl_bind_str(host, port, opts) v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil - "ssl://#{host}:#{port}?cert=#{cert}&key=#{key}" \ + "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}" end end diff --git a/lib/puma/minissl/context_builder.rb b/lib/puma/minissl/context_builder.rb index a30a26dc3d..dade2e1842 100644 --- a/lib/puma/minissl/context_builder.rb +++ b/lib/puma/minissl/context_builder.rb @@ -6,6 +6,21 @@ def initialize(params, events) @events = events end + def localhost_authority + @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY + end + + def localhost_authority_context + return unless localhost_authority + + key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) } + [localhost_authority.key_path, localhost_authority.certificate_path] + else + local_certificates_path = File.expand_path("~/.localhost") + [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")] + end + end + def context ctx = MiniSSL::Context.new @@ -24,13 +39,21 @@ def context ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list'] else unless params['key'] - events.error "Please specify the SSL key via 'key='" + if localhost_authority + params['key'] = localhost_authority_context[0] + else + events.error "Please specify the SSL key via 'key='" + end end ctx.key = params['key'] unless params['cert'] - events.error "Please specify the SSL cert via 'cert='" + if localhost_authority + params['cert'] = localhost_authority_context[1] + else + events.error "Please specify the SSL cert via 'cert='" + end end ctx.cert = params['cert'] From 1d3f775918439c64690cdce258fbe5cf1002eea4 Mon Sep 17 00:00:00 2001 From: Marcin Olichwirowicz Date: Mon, 4 Oct 2021 23:16:23 +0200 Subject: [PATCH 3/4] Add test --- lib/puma/minissl/context_builder.rb | 4 +-- test/test_integration_ssl.rb | 52 +++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/lib/puma/minissl/context_builder.rb b/lib/puma/minissl/context_builder.rb index dade2e1842..866108ed9b 100644 --- a/lib/puma/minissl/context_builder.rb +++ b/lib/puma/minissl/context_builder.rb @@ -38,7 +38,7 @@ def context ctx.keystore_pass = params['keystore-pass'] ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list'] else - unless params['key'] + if params['key'].nil? || params['key'] == "" if localhost_authority params['key'] = localhost_authority_context[0] else @@ -48,7 +48,7 @@ def context ctx.key = params['key'] - unless params['cert'] + if params['cert'].nil? || params['cert'] == "" if localhost_authority params['cert'] = localhost_authority_context[1] else diff --git a/test/test_integration_ssl.rb b/test/test_integration_ssl.rb index 8f746e5ab7..0228222592 100644 --- a/test/test_integration_ssl.rb +++ b/test/test_integration_ssl.rb @@ -15,18 +15,10 @@ class TestIntegrationSSL < TestIntegration require "net/http" require "openssl" - def teardown - @server.close unless @server.closed? - @server = nil - super - end - - def generate_config(opts = nil) + def setup @bind_port = UniquePort.call @control_tcp_port = UniquePort.call - - config = < Date: Mon, 4 Oct 2021 23:57:27 +0200 Subject: [PATCH 4/4] Update tests --- test/test_integration_ssl.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_integration_ssl.rb b/test/test_integration_ssl.rb index 0228222592..f32d72ef36 100644 --- a/test/test_integration_ssl.rb +++ b/test/test_integration_ssl.rb @@ -47,7 +47,7 @@ def setup RUBY @localhost_config = <