Made secret adapters raise a meaningful error if the required CLI is not installed

This commit is contained in:
Kyle Rippey
2024-10-15 23:20:18 -07:00
parent 607368121e
commit 8cec17dd05
8 changed files with 87 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ class Kamal::Secrets::Adapters::Base
delegate :optionize, to: Kamal::Utils delegate :optionize, to: Kamal::Utils
def fetch(secrets, account:, from: nil) def fetch(secrets, account:, from: nil)
check_dependencies!
session = login(account) session = login(account)
full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") } full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
fetch_secrets(full_secrets, account: account, session: session) fetch_secrets(full_secrets, account: account, session: session)
@@ -15,4 +16,8 @@ class Kamal::Secrets::Adapters::Base
def fetch_secrets(...) def fetch_secrets(...)
raise NotImplementedError raise NotImplementedError
end end
def check_dependencies!
raise NotImplementedError
end
end end

View File

@@ -63,4 +63,13 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
result = `#{full_command}`.strip result = `#{full_command}`.strip
raw ? result : JSON.parse(result) raw ? result : JSON.parse(result)
end 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 end

View File

@@ -27,4 +27,13 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
end end
end 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 end

View File

@@ -58,4 +58,13 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base
raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success? raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
end end
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 end

View File

@@ -7,4 +7,8 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base
def fetch_secrets(secrets, account:, session:) def fetch_secrets(secrets, account:, session:)
secrets.to_h { |secret| [ secret, secret.reverse ] } secrets.to_h { |secret| [ secret, secret.reverse ] }
end end
def check_dependencies!
# no op
end
end end

View File

@@ -2,6 +2,8 @@ require "test_helper"
class BitwardenAdapterTest < SecretAdapterTestCase class BitwardenAdapterTest < SecretAdapterTestCase
test "fetch" do test "fetch" do
stub_ticks.with("bw --version 2> /dev/null")
stub_unlocked stub_unlocked
stub_ticks.with("bw sync").returns("") stub_ticks.with("bw sync").returns("")
stub_mypassword stub_mypassword
@@ -14,6 +16,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch with no login" do test "fetch with no login" do
stub_ticks.with("bw --version 2> /dev/null")
stub_unlocked stub_unlocked
stub_ticks.with("bw sync").returns("") stub_ticks.with("bw sync").returns("")
stub_noteitem stub_noteitem
@@ -25,6 +29,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch with from" do test "fetch with from" do
stub_ticks.with("bw --version 2> /dev/null")
stub_unlocked stub_unlocked
stub_ticks.with("bw sync").returns("") stub_ticks.with("bw sync").returns("")
stub_myitem stub_myitem
@@ -39,6 +45,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch with multiple items" do test "fetch with multiple items" do
stub_ticks.with("bw --version 2> /dev/null")
stub_unlocked stub_unlocked
stub_ticks.with("bw sync").returns("") stub_ticks.with("bw sync").returns("")
@@ -80,6 +88,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch unauthenticated" do test "fetch unauthenticated" do
stub_ticks.with("bw --version 2> /dev/null")
stub_ticks stub_ticks
.with("bw status") .with("bw status")
.returns( .returns(
@@ -101,6 +111,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch locked" do test "fetch locked" do
stub_ticks.with("bw --version 2> /dev/null")
stub_ticks stub_ticks
.with("bw status") .with("bw status")
.returns( .returns(
@@ -126,6 +138,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase
end end
test "fetch locked with session" do test "fetch locked with session" do
stub_ticks.with("bw --version 2> /dev/null")
stub_ticks stub_ticks
.with("bw status") .with("bw status")
.returns( .returns(
@@ -150,6 +164,15 @@ class BitwardenAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json assert_equal expected_json, json
end 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 private
def run_command(*command) def run_command(*command)
stdouted do stdouted do

View File

@@ -6,6 +6,7 @@ class LastPassAdapterTest < SecretAdapterTestCase
end end
test "fetch" do 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.with("lpass status --color never").returns("Logged in as email@example.com.")
stub_ticks stub_ticks
@@ -63,6 +64,7 @@ class LastPassAdapterTest < SecretAdapterTestCase
end end
test "fetch with from" do 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.with("lpass status --color never").returns("Logged in as email@example.com.")
stub_ticks stub_ticks
@@ -107,6 +109,8 @@ class LastPassAdapterTest < SecretAdapterTestCase
end end
test "fetch with signin" do 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 status --color never", succeed: false).returns("Not logged in.")
stub_ticks_with("lpass login email@example.com", succeed: true).returns("") stub_ticks_with("lpass login email@example.com", succeed: true).returns("")
stub_ticks.with("lpass show SECRET1 --json").returns(single_item_json) stub_ticks.with("lpass show SECRET1 --json").returns(single_item_json)
@@ -120,6 +124,15 @@ class LastPassAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json assert_equal expected_json, json
end 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 private
def run_command(*command) def run_command(*command)
stdouted do stdouted do

View File

@@ -2,6 +2,7 @@ require "test_helper"
class SecretsOnePasswordAdapterTest < SecretAdapterTestCase class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
test "fetch" do test "fetch" do
stub_ticks.with("op --version 2> /dev/null")
stub_ticks.with("op account get --account myaccount 2> /dev/null") stub_ticks.with("op account get --account myaccount 2> /dev/null")
stub_ticks stub_ticks
@@ -56,6 +57,7 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end end
test "fetch with multiple items" do 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.with("op account get --account myaccount 2> /dev/null")
stub_ticks stub_ticks
@@ -115,6 +117,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end end
test "fetch with signin, no session" do 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 account get --account myaccount 2> /dev/null", succeed: false)
stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("") stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("")
@@ -132,6 +136,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
end end
test "fetch with signin and session" do 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 account get --account myaccount 2> /dev/null", succeed: false)
stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("1234567890") stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("1234567890")
@@ -148,6 +154,15 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json assert_equal expected_json, json
end 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 private
def run_command(*command) def run_command(*command)
stdouted do stdouted do