diff --git a/lib/kamal/cli/secrets.rb b/lib/kamal/cli/secrets.rb index 0cfcf628e..ac4e47647 100644 --- a/lib/kamal/cli/secrets.rb +++ b/lib/kamal/cli/secrets.rb @@ -4,6 +4,7 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base option :account, type: :string, required: false, desc: "The account identifier or username" option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from" option :inline, type: :boolean, required: false, hidden: true + option :server_url, type: :string, aliases: "-u", required: false, desc: "Override the default server-url" def fetch(*secrets) adapter = initialize_adapter(options[:adapter]) @@ -11,7 +12,7 @@ def fetch(*secrets) return puts "No value provided for required options '--account'" end - results = adapter.fetch(secrets, **options.slice(:account, :from).symbolize_keys) + results = adapter.fetch(secrets, **options.slice(:account, :from, :server_url).symbolize_keys) return_or_puts JSON.dump(results).shellescape, inline: options[:inline] end diff --git a/lib/kamal/secrets/adapters/aws_secrets_manager.rb b/lib/kamal/secrets/adapters/aws_secrets_manager.rb index 27d413edc..d69c6bcf3 100644 --- a/lib/kamal/secrets/adapters/aws_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/aws_secrets_manager.rb @@ -4,11 +4,11 @@ def requires_account? end private - def login(_account) + def login(_account, **) nil end - def fetch_secrets(secrets, from:, account: nil, session:) + def fetch_secrets(secrets, from:, account: nil, **) {}.tap do |results| get_from_secrets_manager(prefixed_secrets(secrets, from: from), account: account).each do |secret| secret_name = secret["Name"] diff --git a/lib/kamal/secrets/adapters/base.rb b/lib/kamal/secrets/adapters/base.rb index c74f7c414..ad1d61e24 100644 --- a/lib/kamal/secrets/adapters/base.rb +++ b/lib/kamal/secrets/adapters/base.rb @@ -1,13 +1,13 @@ class Kamal::Secrets::Adapters::Base delegate :optionize, to: Kamal::Utils - def fetch(secrets, account: nil, from: nil) + def fetch(secrets, account: nil, from: nil, server_url: nil) raise RuntimeError, "Missing required option '--account'" if requires_account? && account.blank? check_dependencies! - session = login(account) - fetch_secrets(secrets, from: from, account: account, session: session) + session = login(account, server_url: server_url) + fetch_secrets(secrets, from: from, account: account, session: session, server_url: server_url) end def requires_account? diff --git a/lib/kamal/secrets/adapters/bitwarden.rb b/lib/kamal/secrets/adapters/bitwarden.rb index 6bd4fb259..b37e5bbfb 100644 --- a/lib/kamal/secrets/adapters/bitwarden.rb +++ b/lib/kamal/secrets/adapters/bitwarden.rb @@ -1,6 +1,6 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base private - def login(account) + def login(account, **) status = run_command("status") if status["status"] == "unauthenticated" @@ -21,7 +21,7 @@ def login(account) session end - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, session:, **) {}.tap do |results| items_fields(prefixed_secrets(secrets, from: from)).each do |item, fields| item_json = run_command("get item #{item.shellescape}", session: session, raw: true) diff --git a/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb b/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb index 84aed7dce..3824d59f5 100644 --- a/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb @@ -9,7 +9,7 @@ def requires_account? LIST_COMMAND = "secret list" GET_COMMAND = "secret get" - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, server_url:, **) raise RuntimeError, "You must specify what to retrieve from Bitwarden Secrets Manager" if secrets.length == 0 secrets = prefixed_secrets(secrets, from: from) @@ -18,13 +18,13 @@ def fetch_secrets(secrets, from:, account:, session:) {}.tap do |results| if command.nil? secrets.each do |secret_uuid| - item_json = run_command("#{GET_COMMAND} #{secret_uuid.shellescape}") + item_json = run_command("#{GET_COMMAND} #{secret_uuid.shellescape}", server_url: server_url) raise RuntimeError, "Could not read #{secret_uuid} from Bitwarden Secrets Manager" unless $?.success? item_json = JSON.parse(item_json) results[item_json["key"]] = item_json["value"] end else - items_json = run_command(command) + items_json = run_command(command, server_url: server_url) raise RuntimeError, "Could not read secrets from Bitwarden Secrets Manager" unless $?.success? JSON.parse(items_json).each do |item_json| @@ -45,13 +45,14 @@ def extract_command_and_project(secrets) end end - def run_command(command, session: nil) - full_command = [ "bws", command ].join(" ") - `#{full_command}` + def run_command(command, server_url: nil) + full_command = [ "bws", command ] + full_command << "--server-url=#{server_url}" if server_url + `#{full_command.join(" ")}` end - def login(account) - run_command("project list") + def login(_account, server_url: nil) + run_command("project list", server_url: server_url) raise RuntimeError, "Could not authenticate to Bitwarden Secrets Manager. Did you set a valid access token?" unless $?.success? end diff --git a/lib/kamal/secrets/adapters/enpass.rb b/lib/kamal/secrets/adapters/enpass.rb index 96dea11a5..466eaae33 100644 --- a/lib/kamal/secrets/adapters/enpass.rb +++ b/lib/kamal/secrets/adapters/enpass.rb @@ -14,7 +14,7 @@ def requires_account? end private - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, **) secrets_titles = fetch_secret_titles(secrets) result = `enpass-cli -json -vault #{from.shellescape} show #{secrets_titles.map(&:shellescape).join(" ")}`.strip @@ -31,7 +31,7 @@ def cli_installed? $?.success? end - def login(account) + def login(_account, **) nil end diff --git a/lib/kamal/secrets/adapters/gcp_secret_manager.rb b/lib/kamal/secrets/adapters/gcp_secret_manager.rb index 8ce381ff2..f2baf90a8 100644 --- a/lib/kamal/secrets/adapters/gcp_secret_manager.rb +++ b/lib/kamal/secrets/adapters/gcp_secret_manager.rb @@ -1,6 +1,6 @@ class Kamal::Secrets::Adapters::GcpSecretManager < Kamal::Secrets::Adapters::Base private - def login(account) + def login(_account, **) # Since only the account option is passed from the cli, we'll use it for both account and service account # impersonation. # @@ -26,7 +26,7 @@ def login(account) nil end - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, account:, **) user, service_account = parse_account(account) {}.tap do |results| diff --git a/lib/kamal/secrets/adapters/last_pass.rb b/lib/kamal/secrets/adapters/last_pass.rb index dff872a84..070d6e51d 100644 --- a/lib/kamal/secrets/adapters/last_pass.rb +++ b/lib/kamal/secrets/adapters/last_pass.rb @@ -1,6 +1,6 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base private - def login(account) + def login(account, **) unless loggedin?(account) `lpass login #{account.shellescape}` raise RuntimeError, "Failed to login to LastPass" unless $?.success? @@ -11,7 +11,7 @@ def loggedin?(account) `lpass status --color never`.strip == "Logged in as #{account}." end - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, **) secrets = prefixed_secrets(secrets, from: from) items = `lpass show #{secrets.map(&:shellescape).join(" ")} --json` raise RuntimeError, "Could not read #{secrets} from LastPass" unless $?.success? diff --git a/lib/kamal/secrets/adapters/one_password.rb b/lib/kamal/secrets/adapters/one_password.rb index 53eb347f8..0322fd041 100644 --- a/lib/kamal/secrets/adapters/one_password.rb +++ b/lib/kamal/secrets/adapters/one_password.rb @@ -2,7 +2,7 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base delegate :optionize, to: Kamal::Utils private - def login(account) + def login(account, **) unless loggedin?(account) `op signin #{to_options(account: account, force: true, raw: true)}`.tap do raise RuntimeError, "Failed to login to 1Password" unless $?.success? @@ -15,7 +15,7 @@ def loggedin?(account) $?.success? end - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, account:, session:, **) if secrets.blank? fetch_all_secrets(from: from, account: account, session: session) else diff --git a/lib/kamal/secrets/adapters/test.rb b/lib/kamal/secrets/adapters/test.rb index ac48960b8..4034c44c0 100644 --- a/lib/kamal/secrets/adapters/test.rb +++ b/lib/kamal/secrets/adapters/test.rb @@ -1,10 +1,10 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base private - def login(account) + def login(_account, **) true end - def fetch_secrets(secrets, from:, account:, session:) + def fetch_secrets(secrets, from:, **) prefixed_secrets(secrets, from: from).to_h { |secret| [ secret, secret.reverse ] } end diff --git a/test/secrets/bitwarden_secrets_manager_adapter_test.rb b/test/secrets/bitwarden_secrets_manager_adapter_test.rb index 50dccadc6..979bb8ddf 100644 --- a/test/secrets/bitwarden_secrets_manager_adapter_test.rb +++ b/test/secrets/bitwarden_secrets_manager_adapter_test.rb @@ -178,6 +178,27 @@ class BitwardenSecretsManagerAdapterTest < SecretAdapterTestCase assert_equal "Bitwarden Secrets Manager CLI is not installed", error.message end + test "fetch with different server-url" do + stub_ticks.with("bws --version 2> /dev/null") + stub_ticks.with("bws project list --server-url=https://example.com").returns("OK") + stub_ticks + .with("bws secret get 82aeb5bd-6958-4a89-8197-eacab758acce --server-url=https://example.com") + .returns(<<~JSON) + { + "key": "KAMAL_REGISTRY_PASSWORD", + "value": "some_password" + } + JSON + + json = JSON.parse(shellunescape(run_command("fetch", "82aeb5bd-6958-4a89-8197-eacab758acce", + "--server-url", "https://example.com"))) + expected_json = { + "KAMAL_REGISTRY_PASSWORD"=>"some_password" + } + + assert_equal expected_json, json + end + private def stub_login stub_ticks.with("bws project list").returns("OK")