From 11e4f37409beeced2db093bd71fbba0a8a9ceb8a Mon Sep 17 00:00:00 2001 From: Matthew Croall Date: Sat, 30 Nov 2024 11:10:49 +1030 Subject: [PATCH 1/5] Add proxy boot_config --publish-ip argument --- lib/kamal/cli/proxy.rb | 3 ++- lib/kamal/configuration.rb | 5 +++-- test/cli/proxy_test.rb | 9 +++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index d0e9ba2b..6b42adad 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -23,6 +23,7 @@ def boot desc "boot_config ", "Manage kamal-proxy boot configuration" option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host" + option :publish_ip, type: :string, desc: "IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host" option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host" option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs" @@ -31,7 +32,7 @@ def boot_config(subcommand) case subcommand when "set" boot_options = [ - *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port]) if options[:publish]), + *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_ip]) if options[:publish]), *(KAMAL.config.proxy_logging_args(options[:log_max_size])), *options[:docker_options].map { |option| "--#{option}" } ] diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 021e5e49..cafc25d4 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -249,8 +249,9 @@ def env_tag(name) env_tags.detect { |t| t.name == name.to_s } end - def proxy_publish_args(http_port, https_port) - argumentize "--publish", [ "#{http_port}:#{PROXY_HTTP_PORT}", "#{https_port}:#{PROXY_HTTPS_PORT}" ] + def proxy_publish_args(http_port, https_port, bind_ip = nil) + bind_ip = bind_ip ? "#{bind_ip}:" : "" + argumentize "--publish", [ "#{bind_ip}#{http_port}:#{PROXY_HTTP_PORT}", "#{bind_ip}#{https_port}:#{PROXY_HTTPS_PORT}" ] end def proxy_logging_args(max_size) diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index 0a890451..f499de3b 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -281,6 +281,15 @@ class CliProxyTest < CliTestCase end end + test "boot_config set custom bind ip" do + run_command("boot_config", "set", "--publish-ip", "127.0.0.1").tap do |output| + %w[ 1.1.1.1 1.1.1.2 ].each do |host| + assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output + assert_match "Uploading \"--publish 127.0.0.1:80:80 --publish 127.0.0.1:443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output + end + end + end + test "boot_config set docker options" do run_command("boot_config", "set", "--docker_options", "label=foo=bar", "add_host=thishost:thathost").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| From ffe1ac34838f503b131a313083832884dec38a83 Mon Sep 17 00:00:00 2001 From: Matthew Croall Date: Tue, 3 Dec 2024 08:11:19 +1030 Subject: [PATCH 2/5] Refactor proxy_publish_args argument concatenation --- lib/kamal/configuration.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index cafc25d4..5099e9ab 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -250,8 +250,9 @@ def env_tag(name) end def proxy_publish_args(http_port, https_port, bind_ip = nil) - bind_ip = bind_ip ? "#{bind_ip}:" : "" - argumentize "--publish", [ "#{bind_ip}#{http_port}:#{PROXY_HTTP_PORT}", "#{bind_ip}#{https_port}:#{PROXY_HTTPS_PORT}" ] + publish_http = [ bind_ip, http_port, PROXY_HTTP_PORT ].compact.join(":") + publish_https = [ bind_ip, https_port, PROXY_HTTPS_PORT ].compact.join(":") + argumentize "--publish", [ publish_http, publish_https ] end def proxy_logging_args(max_size) From 0bafa02e7d9266cc274cd3ed03e6b18fcca84a3a Mon Sep 17 00:00:00 2001 From: Matthew Croall Date: Tue, 3 Dec 2024 08:13:20 +1030 Subject: [PATCH 3/5] Rename proxy bind cli argument to publish_host_ip --- lib/kamal/cli/proxy.rb | 4 ++-- test/cli/proxy_test.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 6b42adad..7ff73b4e 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -23,7 +23,7 @@ def boot desc "boot_config ", "Manage kamal-proxy boot configuration" option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host" - option :publish_ip, type: :string, desc: "IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" + option :publish_host_ip, type: :string, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host" option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host" option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs" @@ -32,7 +32,7 @@ def boot_config(subcommand) case subcommand when "set" boot_options = [ - *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_ip]) if options[:publish]), + *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]), *(KAMAL.config.proxy_logging_args(options[:log_max_size])), *options[:docker_options].map { |option| "--#{option}" } ] diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index f499de3b..961256af 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -282,7 +282,7 @@ class CliProxyTest < CliTestCase end test "boot_config set custom bind ip" do - run_command("boot_config", "set", "--publish-ip", "127.0.0.1").tap do |output| + run_command("boot_config", "set", "--publish-host-ip", "127.0.0.1").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output assert_match "Uploading \"--publish 127.0.0.1:80:80 --publish 127.0.0.1:443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output From e597ae6155eb81a21de2a814f7e6d5874a236852 Mon Sep 17 00:00:00 2001 From: Matthew Croall Date: Wed, 4 Dec 2024 10:42:50 +1030 Subject: [PATCH 4/5] Add support for multiple publish ip addresses --- lib/kamal/cli/proxy.rb | 2 +- lib/kamal/configuration.rb | 32 ++++++++++++++++++++++++++++---- test/cli/proxy_test.rb | 19 ++++++++++++++++++- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 7ff73b4e..43444539 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -23,7 +23,7 @@ def boot desc "boot_config ", "Manage kamal-proxy boot configuration" option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host" - option :publish_host_ip, type: :string, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" + option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host" option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host" option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs" diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 5099e9ab..4955928d 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -249,10 +249,16 @@ def env_tag(name) env_tags.detect { |t| t.name == name.to_s } end - def proxy_publish_args(http_port, https_port, bind_ip = nil) - publish_http = [ bind_ip, http_port, PROXY_HTTP_PORT ].compact.join(":") - publish_https = [ bind_ip, https_port, PROXY_HTTPS_PORT ].compact.join(":") - argumentize "--publish", [ publish_http, publish_https ] + def proxy_publish_args(http_port, https_port, bind_ips = nil) + ensure_valid_bind_ips(bind_ips) + + (bind_ips || [ nil ]).map do |bind_ip| + bind_ip = format_bind_ip(bind_ip) + publish_http = [ bind_ip, http_port, PROXY_HTTP_PORT ].compact.join(":") + publish_https = [ bind_ip, https_port, PROXY_HTTPS_PORT ].compact.join(":") + + argumentize "--publish", [ publish_http, publish_https ] + end.join(" ") end def proxy_logging_args(max_size) @@ -346,6 +352,15 @@ def ensure_valid_kamal_version true end + def ensure_valid_bind_ips(bind_ips) + bind_ips.present? && bind_ips.each do |ip| + next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex + raise Kamal::ConfigurationError, "Invalid publish IP address: #{ip}" + end + + true + end + def ensure_retain_containers_valid raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1 @@ -377,6 +392,15 @@ def ensure_unique_hosts_for_ssl_roles true end + def format_bind_ip(ip) + # Ensure IPv6 address inside square brackets - e.g. [::1] + if ip =~ Resolv::IPv6::Regex && ip !~ /\[.*\]/ + "[#{ip}]" + else + ip + end + end + def role_names raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort end diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index 961256af..381fee55 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -281,7 +281,7 @@ class CliProxyTest < CliTestCase end end - test "boot_config set custom bind ip" do + test "boot_config set bind IP" do run_command("boot_config", "set", "--publish-host-ip", "127.0.0.1").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output @@ -290,6 +290,23 @@ class CliProxyTest < CliTestCase end end + test "boot_config set multiple bind IPs" do + run_command("boot_config", "set", "--publish-host-ip", "127.0.0.1", "--publish-host-ip", "::1").tap do |output| + %w[ 1.1.1.1 1.1.1.2 ].each do |host| + assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output + assert_match "Uploading \"--publish 127.0.0.1:80:80 --publish 127.0.0.1:443:443 --publish [::1]:80:80 --publish [::1]:443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output + end + end + end + + test "boot_config set invalid bind IPs" do + exception = assert_raises do + run_command("boot_config", "set", "--publish-host-ip", "1.2.3.invalidIP", "--publish-host-ip", "::1") + end + + assert_includes exception.message, "Invalid publish IP address: 1.2.3.invalidIP" + end + test "boot_config set docker options" do run_command("boot_config", "set", "--docker_options", "label=foo=bar", "add_host=thishost:thathost").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| From 1c8a56b8cf5afe7691fe100160c556dfe0263811 Mon Sep 17 00:00:00 2001 From: Matthew Croall Date: Wed, 4 Dec 2024 10:44:16 +1030 Subject: [PATCH 5/5] Change invalid publish ip exception class --- lib/kamal/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 4955928d..bbb16324 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -355,7 +355,7 @@ def ensure_valid_kamal_version def ensure_valid_bind_ips(bind_ips) bind_ips.present? && bind_ips.each do |ip| next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex - raise Kamal::ConfigurationError, "Invalid publish IP address: #{ip}" + raise ArgumentError, "Invalid publish IP address: #{ip}" end true