Skip to content

Commit

Permalink
feat: add Bitwarden Secrets Manager adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
oandalib committed Nov 2, 2024
1 parent 685312c commit 09cdccf
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/kamal/cli/secrets.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Kamal::Cli::Secrets < Kamal::Cli::Base
desc "fetch [SECRETS...]", "Fetch secrets from a vault"
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
option :account, type: :string, required: true, desc: "The account identifier or username"
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
def fetch(*secrets)
Expand Down
1 change: 1 addition & 0 deletions lib/kamal/secrets/adapters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Kamal::Secrets::Adapters
def self.lookup(name)
name = "one_password" if name.downcase == "1password"
name = "last_pass" if name.downcase == "lastpass"
name = "bitwarden_secrets_manager" if name.downcase == "bitwarden-sm"
adapter_class(name)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/kamal/secrets/adapters/base.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Kamal::Secrets::Adapters::Base
delegate :optionize, to: Kamal::Utils

def fetch(secrets, account:, from: nil)
def fetch(secrets, account: nil, from: nil)
check_dependencies!
session = login(account)
full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
Expand Down
32 changes: 32 additions & 0 deletions lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Kamal::Secrets::Adapters::BitwardenSecretsManager < Kamal::Secrets::Adapters::Base
private
def login(account)
nil
end

def fetch_secrets(secrets, account:, session:)
{}.tap do |results|
secrets = run_command("secret list -o env")
raise RuntimeError, "Could not read secrets from Bitwarden Secrets Manager" unless $?.success?
secrets.split("\n").each do |secret|
key, value = secret.split("=", 2)
value = value.gsub(/^"|"$/, "")
results[key] = value
end
end
end

def run_command(command, session: nil)
full_command = [ "bws", command ].join(" ")
`#{full_command}`.strip unless full_command.nil?
end

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

def cli_installed?
`bws --version 2> /dev/null`
$?.success?
end
end
58 changes: 58 additions & 0 deletions test/secrets/bitwarden_secrets_manager_adapter_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require "test_helper"

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

stub_ticks
.with("bws secret list -o env")
.returns("KAMAL_REGISTRY_PASSWORD=\"some_password\"\nMY_OTHER_SECRET=\"my=weird\"secret\"")

actual = shellunescape(run_command("fetch"))

expected =
'{"KAMAL_REGISTRY_PASSWORD":"some_password","MY_OTHER_SECRET":"my\=weird\"secret"}'

assert_equal expected, actual
end

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

stub_ticks_with("bws secret list -o env", succeed: false).returns("Error:\n0: Received error message from server")

error = assert_raises RuntimeError do
(shellunescape(run_command("fetch")))
end
assert_equal("Could not read secrets from Bitwarden Secrets Manager", error.message)
end

test "fetch with no session token" do
stub_ticks.with("bws --version 2> /dev/null")

stub_ticks_with("bws secret list -o env", succeed: false).returns("Error:\n0: Missing access token")

error = assert_raises RuntimeError do
(shellunescape(run_command("fetch")))
end
assert_equal("Could not read secrets from Bitwarden Secrets Manager", error.message)
end

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

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

private
def run_command(*command)
stdouted do
Kamal::Cli::Secrets.start \
[ *command,
"--adapter", "bitwarden-sm" ]
end
end
end

0 comments on commit 09cdccf

Please sign in to comment.