From 5a92683afd28b12f28c352ed889489f7c1fd0772 Mon Sep 17 00:00:00 2001 From: Michael Herold Date: Tue, 18 Dec 2018 18:13:59 -0600 Subject: [PATCH] Allow mutual TLS CA to be set using `ssl_bind` DSL When using mutual TLS, you must specify the CA certificate chain to use for verifying the peer. Using Puma's `ssl_bind` DSL did not give you the option of doing so, which lead to confusing errors when attempting to use it. Now, when specifying the `verify_mode` as either `peer` or `force_peer`, you can use the DSL to set the `ca` value as needed within the `Binder`. This allows you to use the DSL instead of falling back to the default `bind` syntax via the URI-style configuration pattern. --- lib/puma/dsl.rb | 5 +++-- test/config/ssl_config.rb | 11 ++++++++++- test/test_config.rb | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index cd84ba26..f1f7c03a 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -296,13 +296,14 @@ module Puma def ssl_bind(host, port, opts) verify = opts.fetch(:verify_mode, 'none') no_tlsv1 = opts.fetch(:no_tlsv1, 'false') + ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify) if defined?(JRUBY_VERSION) keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}" - bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}" + bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}" else ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter] - bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}" + bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}" end end diff --git a/test/config/ssl_config.rb b/test/config/ssl_config.rb index efcbb682..c6b866ff 100644 --- a/test/config/ssl_config.rb +++ b/test/config/ssl_config.rb @@ -1,4 +1,13 @@ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ +ca = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__ -ssl_bind "0.0.0.0", 9292, :cert => cert, :key => key +ssl_bind "0.0.0.0", 9292, :cert => cert, :key => key, :verify_mode => "peer", :ca => ca + +app do |env| + [200, {}, ["embedded app"]] +end + +lowlevel_error_handler do |err| + [200, {}, ["error page"]] +end diff --git a/test/test_config.rb b/test/test_config.rb index 41c0f828..9b8b005b 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -30,6 +30,22 @@ class TestConfigFile < Minitest::Test assert_equal [200, {}, ["embedded app"]], app.call({}) end + def test_ssl_configuration_from_DSL + conf = Puma::Configuration.new do |config| + config.load "test/config/ssl_config.rb" + end + + conf.load + + bind_configuration = conf.options.file_options[:binds].first + app = conf.app + + assert bind_configuration =~ %r{ca=.*ca.crt} + assert bind_configuration =~ /verify_mode=peer/ + + assert_equal [200, {}, ["embedded app"]], app.call({}) + end + def test_double_bind_port port = (rand(10_000) + 30_000).to_s with_env("PORT" => port) do