Skip to content

Commit

Permalink
Merge pull request #1121 from kylerippey/adapter-cli-installation-checks
Browse files Browse the repository at this point in the history
Raise meaningful error messages when secret adapter CLIs are not installed
  • Loading branch information
djmb authored Oct 23, 2024
2 parents 66f6e8b + 8cec17d commit 53dad5f
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/kamal/secrets/adapters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Kamal::Secrets::Adapters::Base
delegate :optionize, to: Kamal::Utils

def fetch(secrets, account:, from: nil)
check_dependencies!
session = login(account)
full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
fetch_secrets(full_secrets, account: account, session: session)
Expand All @@ -15,4 +16,8 @@ def login(...)
def fetch_secrets(...)
raise NotImplementedError
end

def check_dependencies!
raise NotImplementedError
end
end
9 changes: 9 additions & 0 deletions lib/kamal/secrets/adapters/bitwarden.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,13 @@ def run_command(command, session: nil, raw: false)
result = `#{full_command}`.strip
raw ? result : JSON.parse(result)
end

def check_dependencies!
raise RuntimeError, "Bitwarden CLI is not installed" unless cli_installed?
end

def cli_installed?
`bw --version 2> /dev/null`
$?.success?
end
end
9 changes: 9 additions & 0 deletions lib/kamal/secrets/adapters/last_pass.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ def fetch_secrets(secrets, account:, session:)
end
end
end

def check_dependencies!
raise RuntimeError, "LastPass CLI is not installed" unless cli_installed?
end

def cli_installed?
`lpass --version 2> /dev/null`
$?.success?
end
end
9 changes: 9 additions & 0 deletions lib/kamal/secrets/adapters/one_password.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,13 @@ def op_item_get(vault, item, fields, account:, session:)
raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
end
end

def check_dependencies!
raise RuntimeError, "1Password CLI is not installed" unless cli_installed?
end

def cli_installed?
`op --version 2> /dev/null`
$?.success?
end
end
4 changes: 4 additions & 0 deletions lib/kamal/secrets/adapters/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ def login(account)
def fetch_secrets(secrets, account:, session:)
secrets.to_h { |secret| [ secret, secret.reverse ] }
end

def check_dependencies!
# no op
end
end
23 changes: 23 additions & 0 deletions test/secrets/bitwarden_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

class BitwardenAdapterTest < SecretAdapterTestCase
test "fetch" do
stub_ticks.with("bw --version 2> /dev/null")

stub_unlocked
stub_ticks.with("bw sync").returns("")
stub_mypassword
Expand All @@ -14,6 +16,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch with no login" do
stub_ticks.with("bw --version 2> /dev/null")

stub_unlocked
stub_ticks.with("bw sync").returns("")
stub_noteitem
Expand All @@ -25,6 +29,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch with from" do
stub_ticks.with("bw --version 2> /dev/null")

stub_unlocked
stub_ticks.with("bw sync").returns("")
stub_myitem
Expand All @@ -39,6 +45,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch with multiple items" do
stub_ticks.with("bw --version 2> /dev/null")

stub_unlocked

stub_ticks.with("bw sync").returns("")
Expand Down Expand Up @@ -80,6 +88,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch unauthenticated" do
stub_ticks.with("bw --version 2> /dev/null")

stub_ticks
.with("bw status")
.returns(
Expand All @@ -101,6 +111,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch locked" do
stub_ticks.with("bw --version 2> /dev/null")

stub_ticks
.with("bw status")
.returns(
Expand All @@ -126,6 +138,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end

test "fetch locked with session" do
stub_ticks.with("bw --version 2> /dev/null")

stub_ticks
.with("bw status")
.returns(
Expand All @@ -150,6 +164,15 @@ class BitwardenAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json
end

test "fetch without CLI installed" do
stub_ticks_with("bw --version 2> /dev/null", succeed: false)

error = assert_raises RuntimeError do
JSON.parse(shellunescape(run_command("fetch", "mynote")))
end
assert_equal "Bitwarden CLI is not installed", error.message
end

private
def run_command(*command)
stdouted do
Expand Down
13 changes: 13 additions & 0 deletions test/secrets/last_pass_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class LastPassAdapterTest < SecretAdapterTestCase
end

test "fetch" do
stub_ticks.with("lpass --version 2> /dev/null")
stub_ticks.with("lpass status --color never").returns("Logged in as email@example.com.")

stub_ticks
Expand Down Expand Up @@ -63,6 +64,7 @@ class LastPassAdapterTest < SecretAdapterTestCase
end

test "fetch with from" do
stub_ticks.with("lpass --version 2> /dev/null")
stub_ticks.with("lpass status --color never").returns("Logged in as email@example.com.")

stub_ticks
Expand Down Expand Up @@ -107,6 +109,8 @@ class LastPassAdapterTest < SecretAdapterTestCase
end

test "fetch with signin" do
stub_ticks.with("lpass --version 2> /dev/null")

stub_ticks_with("lpass status --color never", succeed: false).returns("Not logged in.")
stub_ticks_with("lpass login email@example.com", succeed: true).returns("")
stub_ticks.with("lpass show SECRET1 --json").returns(single_item_json)
Expand All @@ -120,6 +124,15 @@ class LastPassAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json
end

test "fetch without CLI installed" do
stub_ticks_with("lpass --version 2> /dev/null", succeed: false)

error = assert_raises RuntimeError do
JSON.parse(shellunescape(run_command("fetch", "SECRET1", "FOLDER1/FSECRET1", "FOLDER1/FSECRET2")))
end
assert_equal "LastPass CLI is not installed", error.message
end

private
def run_command(*command)
stdouted do
Expand Down
15 changes: 15 additions & 0 deletions test/secrets/one_password_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
test "fetch" do
stub_ticks.with("op --version 2> /dev/null")
stub_ticks.with("op account get --account myaccount 2> /dev/null")

stub_ticks
Expand Down Expand Up @@ -56,6 +57,7 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end

test "fetch with multiple items" do
stub_ticks.with("op --version 2> /dev/null")
stub_ticks.with("op account get --account myaccount 2> /dev/null")

stub_ticks
Expand Down Expand Up @@ -115,6 +117,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end

test "fetch with signin, no session" do
stub_ticks.with("op --version 2> /dev/null")

stub_ticks_with("op account get --account myaccount 2> /dev/null", succeed: false)
stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("")

Expand All @@ -132,6 +136,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end

test "fetch with signin and session" do
stub_ticks.with("op --version 2> /dev/null")

stub_ticks_with("op account get --account myaccount 2> /dev/null", succeed: false)
stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("1234567890")

Expand All @@ -148,6 +154,15 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json
end

test "fetch without CLI installed" do
stub_ticks_with("op --version 2> /dev/null", succeed: false)

error = assert_raises RuntimeError do
JSON.parse(shellunescape(run_command("fetch", "--from", "op://myvault/myitem", "section/SECRET1", "section/SECRET2", "section2/SECRET3")))
end
assert_equal "1Password CLI is not installed", error.message
end

private
def run_command(*command)
stdouted do
Expand Down

0 comments on commit 53dad5f

Please sign in to comment.