From 1d48a0fb0a8a1b9cb45b0d8b60cedd02635ac063 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Sat, 28 Sep 2024 20:04:34 -0400 Subject: [PATCH 1/5] Allow specifying multiple hosts for kamal proxy via an array --- lib/kamal/configuration.rb | 2 +- lib/kamal/configuration/docs/proxy.yml | 11 +++++++++++ lib/kamal/configuration/proxy.rb | 6 +++--- lib/kamal/configuration/validator/proxy.rb | 2 +- test/commands/app_test.rb | 16 ++++++++++++++++ test/configuration/proxy_test.rb | 10 ++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 32f11850..82f72bc2 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -360,7 +360,7 @@ class Kamal::Configuration end def ensure_unique_hosts_for_ssl_roles - hosts = roles.select(&:ssl?).map { |role| role.proxy.host } + hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts } duplicates = hosts.tally.filter_map { |host, count| host if count > 1 } raise Kamal::ConfigurationError, "Different roles can't share the same host for SSL: #{duplicates.join(", ")}" if duplicates.any? diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 905f81dc..092a7f32 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -28,6 +28,17 @@ proxy: # If multiple hosts are needed, these can be specified by comma-separating the hosts. host: foo.example.com,bar.example.com + # Hosts + # + # The hosts that will be used to serve the app. The proxy will only route requests + # to this host to your app. + # + # If no hosts are set, then all requests will be forwarded, except for matching + # requests for other apps deployed on that server that do have a host set. + hosts: + - foo.example.com + - bar.example.com + # App port # # The port the application container is exposed on. diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index c8fbbb6a..ed108a02 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -22,13 +22,13 @@ class Kamal::Configuration::Proxy proxy_config.fetch("ssl", false) end - def host - proxy_config["host"] + def hosts + proxy_config["hosts"] || proxy_config["host"]&.split(",") || [] end def deploy_options { - host: proxy_config["host"], + host: hosts.present? ? hosts.join(",") : nil, tls: proxy_config["ssl"] ? true : nil, "deploy-timeout": seconds_duration(config.deploy_timeout), "drain-timeout": seconds_duration(config.drain_timeout), diff --git a/lib/kamal/configuration/validator/proxy.rb b/lib/kamal/configuration/validator/proxy.rb index bf2e5e9e..c6dbcb2d 100644 --- a/lib/kamal/configuration/validator/proxy.rb +++ b/lib/kamal/configuration/validator/proxy.rb @@ -3,7 +3,7 @@ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator unless config.nil? super - if config["host"].blank? && config["ssl"] + if config["host"].blank? && config["hosts"].blank? && config["ssl"] error "Must set a host to enable automatic SSL" end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 6704adb6..6d444569 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -119,6 +119,22 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.deploy(target: "172.1.0.2").join(" ") end + test "deploy with SSL" do + @config[:proxy] = { "ssl" => true, "host" => "example.com" } + + assert_equal \ + "docker exec kamal-proxy kamal-proxy deploy app-web --target \"172.1.0.2:80\" --host \"example.com\" --tls --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + new_command.deploy(target: "172.1.0.2").join(" ") + end + + test "deploy with SSL targeting multiple hosts" do + @config[:proxy] = { "ssl" => true, "hosts" => [ "example.com", "anotherexample.com" ] } + + assert_equal \ + "docker exec kamal-proxy kamal-proxy deploy app-web --target \"172.1.0.2:80\" --host \"example.com,anotherexample.com\" --tls --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + new_command.deploy(target: "172.1.0.2").join(" ") + end + test "remove" do assert_equal \ "docker exec kamal-proxy kamal-proxy remove app-web --target \"172.1.0.2:80\"", diff --git a/test/configuration/proxy_test.rb b/test/configuration/proxy_test.rb index 891bf6b8..7772012d 100644 --- a/test/configuration/proxy_test.rb +++ b/test/configuration/proxy_test.rb @@ -13,6 +13,16 @@ class ConfigurationProxyTest < ActiveSupport::TestCase assert_equal true, config.proxy.ssl? end + test "ssl with multiple hosts passed via host" do + @deploy[:proxy] = { "ssl" => true, "host" => "example.com,anotherexample.com" } + assert_equal true, config.proxy.ssl? + end + + test "ssl with multiple hosts passed via hosts" do + @deploy[:proxy] = { "ssl" => true, "hosts" => [ "example.com", "anotherexample.com" ] } + assert_equal true, config.proxy.ssl? + end + test "ssl with no host" do @deploy[:proxy] = { "ssl" => true } assert_raises(Kamal::ConfigurationError) { config.proxy.ssl? } From 8df7d7d92dc04bd79319d725466ffdd4176c4d87 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Sat, 28 Sep 2024 20:13:14 -0400 Subject: [PATCH 2/5] Do not allow both host and hosts for proxy configuration --- lib/kamal/configuration/docs/proxy.yml | 2 +- lib/kamal/configuration/validator/proxy.rb | 4 ++++ test/configuration/proxy_test.rb | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 092a7f32..444e2da7 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -31,7 +31,7 @@ proxy: # Hosts # # The hosts that will be used to serve the app. The proxy will only route requests - # to this host to your app. + # to these hosts to your app. # # If no hosts are set, then all requests will be forwarded, except for matching # requests for other apps deployed on that server that do have a host set. diff --git a/lib/kamal/configuration/validator/proxy.rb b/lib/kamal/configuration/validator/proxy.rb index c6dbcb2d..3e2eda19 100644 --- a/lib/kamal/configuration/validator/proxy.rb +++ b/lib/kamal/configuration/validator/proxy.rb @@ -6,6 +6,10 @@ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator if config["host"].blank? && config["hosts"].blank? && config["ssl"] error "Must set a host to enable automatic SSL" end + + if config["host"].present? && config["hosts"].present? + error "Must use either 'host' or 'hosts', not both" + end end end end diff --git a/test/configuration/proxy_test.rb b/test/configuration/proxy_test.rb index 7772012d..1bdfaeab 100644 --- a/test/configuration/proxy_test.rb +++ b/test/configuration/proxy_test.rb @@ -28,6 +28,11 @@ class ConfigurationProxyTest < ActiveSupport::TestCase assert_raises(Kamal::ConfigurationError) { config.proxy.ssl? } end + test "ssl with both host and hosts" do + @deploy[:proxy] = { "ssl" => true, host: "example.com", hosts: [ "anotherexample.com" ] } + assert_raises(Kamal::ConfigurationError) { config.proxy.ssl? } + end + test "ssl false" do @deploy[:proxy] = { "ssl" => false } assert_not config.proxy.ssl? From c63ec39f0735e62536c3df40eb2b3e66f6bf8b22 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Sat, 28 Sep 2024 20:17:22 -0400 Subject: [PATCH 3/5] Added a test for colliding hosts passed via hosts array --- test/configuration_test.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 814aa26e..21709180 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -377,4 +377,15 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal "Different roles can't share the same host for SSL: foo.example.com", exception.message end + + test "two proxy ssl roles with same host in a hosts array" do + @deploy_with_roles[:servers]["web"] = { "hosts" => [ "1.1.1.1" ], "proxy" => { "ssl" => true, "hosts" => [ "foo.example.com", "bar.example.com" ] } } + @deploy_with_roles[:servers]["workers"] = { "hosts" => [ "1.1.1.1" ], "proxy" => { "ssl" => true, "hosts" => [ "www.example.com", "foo.example.com" ] } } + + exception = assert_raises(Kamal::ConfigurationError) do + Kamal::Configuration.new(@deploy_with_roles) + end + + assert_equal "Different roles can't share the same host for SSL: foo.example.com", exception.message + end end From 67ad7662ab9091523421eef0239a70b6bf76c112 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Sun, 29 Sep 2024 11:46:21 -0400 Subject: [PATCH 4/5] Simplified proxy hosts validation and documentation, similar to accessory config --- lib/kamal/configuration/docs/proxy.yml | 14 +++----------- lib/kamal/configuration/validator/proxy.rb | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 444e2da7..76ec3e41 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -17,24 +17,16 @@ # `proxy: true` or providing a proxy configuration. proxy: - # Host + # Hosts # # The hosts that will be used to serve the app. The proxy will only route requests # to this host to your app. # # If no hosts are set, then all requests will be forwarded, except for matching # requests for other apps deployed on that server that do have a host set. + # + # Specify one of `host` or `hosts`. host: foo.example.com - # If multiple hosts are needed, these can be specified by comma-separating the hosts. - host: foo.example.com,bar.example.com - - # Hosts - # - # The hosts that will be used to serve the app. The proxy will only route requests - # to these hosts to your app. - # - # If no hosts are set, then all requests will be forwarded, except for matching - # requests for other apps deployed on that server that do have a host set. hosts: - foo.example.com - bar.example.com diff --git a/lib/kamal/configuration/validator/proxy.rb b/lib/kamal/configuration/validator/proxy.rb index 3e2eda19..b9e11cd9 100644 --- a/lib/kamal/configuration/validator/proxy.rb +++ b/lib/kamal/configuration/validator/proxy.rb @@ -7,8 +7,8 @@ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator error "Must set a host to enable automatic SSL" end - if config["host"].present? && config["hosts"].present? - error "Must use either 'host' or 'hosts', not both" + if (config.keys & [ "host", "hosts" ]).size > 1 + error "Specify one of 'host' or 'hosts', not both" end end end From e75365c8c63cef1eb5da3b5bc8a6e9d8bd4af571 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Mon, 30 Sep 2024 08:09:05 -0400 Subject: [PATCH 5/5] Simplified deploy options for kamal-proxy as it supports multiple --host arguments --- lib/kamal/configuration/proxy.rb | 2 +- test/commands/app_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index ed108a02..653b93d4 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -28,7 +28,7 @@ class Kamal::Configuration::Proxy def deploy_options { - host: hosts.present? ? hosts.join(",") : nil, + host: hosts, tls: proxy_config["ssl"] ? true : nil, "deploy-timeout": seconds_duration(config.deploy_timeout), "drain-timeout": seconds_duration(config.drain_timeout), diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 6d444569..e2a6fe83 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -131,7 +131,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:proxy] = { "ssl" => true, "hosts" => [ "example.com", "anotherexample.com" ] } assert_equal \ - "docker exec kamal-proxy kamal-proxy deploy app-web --target \"172.1.0.2:80\" --host \"example.com,anotherexample.com\" --tls --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + "docker exec kamal-proxy kamal-proxy deploy app-web --target \"172.1.0.2:80\" --host \"example.com\" --host \"anotherexample.com\" --tls --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", new_command.deploy(target: "172.1.0.2").join(" ") end