Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/kamal/configuration/docs/proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ proxy:
- X-Request-ID
- X-Request-Start

# Basic authentication
#
# Protect the app behind HTTP Basic Auth, enforced by kamal-proxy. Requests
# without valid credentials receive a 401 response.
#
# Both `username` and `password` are required. Because credentials are sent on
# every request, enable `ssl` when using basic auth.
#
# Avoid committing the password in plain text - inject it from a secret instead:
# password: <%= ENV["WEB_PASSWORD"] %>
basic_auth:
username: "admin"
password: "secret"

# Run configuration
#
# These options are used when booting the proxy container.
Expand Down
13 changes: 12 additions & 1 deletion lib/kamal/configuration/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ def path_prefixes
proxy_config["path_prefixes"] || proxy_config["path_prefix"]&.split(",") || []
end

def basic_auth?
proxy_config["basic_auth"].is_a?(Hash)
end

def basic_auth
return nil unless basic_auth?
auth = proxy_config["basic_auth"]
"#{auth["username"]}:#{auth["password"]}"
end
Comment thread
r4mbo7 marked this conversation as resolved.

def deploy_options
{
host: hosts,
Expand All @@ -90,7 +100,8 @@ def deploy_options
"tls-redirect": proxy_config.dig("ssl_redirect"),
"log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
"log-response-header": proxy_config.dig("logging", "response_headers"),
"error-pages": error_pages
"error-pages": error_pages,
"basic-auth": basic_auth
}.compact
end

Expand Down
6 changes: 6 additions & 0 deletions lib/kamal/configuration/validator/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def validate!
end
end

if config["basic_auth"].is_a?(Hash)
if config["basic_auth"]["username"].blank? || config["basic_auth"]["password"].blank?
error "basic_auth requires both username and password to be set"
end
end
Comment on lines +24 to +28

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good instinct, but this is already covered: the proxy config is schema-validated against docs/proxy.yml, where basic_auth is defined as a hash. A string/boolean/array value raises proxy/basic_auth: should be a hash before basic_auth? is ever reached, so it fails closed rather than silently ignoring the misconfiguration. I've added a test ("basic auth must be a hash") to make that explicit and prevent regressions.

Comment on lines +24 to +28

if run_config = config["run"]
if run_config["bind_ips"].present?
ensure_valid_bind_ips(config["bind_ips"])
Expand Down
34 changes: 34 additions & 0 deletions test/configuration/proxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,40 @@ class ConfigurationProxyTest < ActiveSupport::TestCase
end
end

test "basic auth in deploy options and command args" do
@deploy[:proxy] = { "basic_auth" => { "username" => "abc", "password" => "123456" } }

proxy = config.proxy
assert_equal "abc:123456", proxy.deploy_options[:"basic-auth"]
assert_includes proxy.deploy_command_args(target: "172.1.0.2"), "--basic-auth=\"abc:123456\""
Comment thread
r4mbo7 marked this conversation as resolved.
Outdated
end

test "no basic auth option when not configured" do
@deploy[:proxy] = { "host" => "example.com" }

proxy = config.proxy
assert_nil proxy.deploy_options[:"basic-auth"]
assert_not proxy.basic_auth?
assert_not_includes proxy.deploy_command_args(target: "172.1.0.2").join(" "), "--basic-auth"
end

test "basic auth with only username" do
@deploy[:proxy] = { "basic_auth" => { "username" => "abc" } }
assert_raises(Kamal::ConfigurationError) { config.proxy }
end

test "basic auth with only password" do
@deploy[:proxy] = { "basic_auth" => { "password" => "123456" } }
assert_raises(Kamal::ConfigurationError) { config.proxy }
end

test "basic auth specialized on a role overrides root proxy config" do
@deploy[:proxy] = { "basic_auth" => { "username" => "abc", "password" => "123456" } }
@deploy[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "proxy" => { "basic_auth" => { "username" => "xyz", "password" => "secret" } } } }

assert_equal "xyz:secret", config.role(:web).proxy.deploy_options[:"basic-auth"]
end

private
def config
Kamal::Configuration.new(@deploy)
Expand Down