Add lastpass, bitwarden adapters
This commit is contained in:
24
lib/kamal/secrets/adapters/base.rb
Normal file
24
lib/kamal/secrets/adapters/base.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
class Kamal::Secrets::Adapters::Base
|
||||||
|
delegate :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
|
def fetch(secrets, account:, location: nil)
|
||||||
|
session = login(account)
|
||||||
|
full_secrets = secrets.map { |secret| [ location, secret ].compact.join("/") }
|
||||||
|
fetch_from_vault(full_secrets, account: account, session: session)
|
||||||
|
rescue => e
|
||||||
|
$stderr.puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
|
||||||
|
$stderr.puts e.backtrace if ENV["VERBOSE"]
|
||||||
|
|
||||||
|
Process.kill("INT", Process.ppid) if ENV["KAMAL_SECRETS_KILL_PARENT"]
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def login(...)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_from_vault(...)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
52
lib/kamal/secrets/adapters/bitwarden.rb
Normal file
52
lib/kamal/secrets/adapters/bitwarden.rb
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
||||||
|
private
|
||||||
|
def login(account)
|
||||||
|
status = run_command("status")
|
||||||
|
|
||||||
|
if status["status"] == "unauthenticated"
|
||||||
|
run_command("login #{account}")
|
||||||
|
status = run_command("status")
|
||||||
|
end
|
||||||
|
|
||||||
|
if status["status"] == "locked"
|
||||||
|
session = run_command("unlock --raw", raw: true)
|
||||||
|
status = run_command("status", session: session)
|
||||||
|
end
|
||||||
|
|
||||||
|
raise RuntimeError, "Failed to login to and unlock Bitwarden" unless status["status"] == "unlocked"
|
||||||
|
|
||||||
|
run_command("sync", raw: true)
|
||||||
|
raise RuntimeError, "Failed to sync Bitwarden" unless $?.success?
|
||||||
|
|
||||||
|
session
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_from_vault(secrets, account:, session:)
|
||||||
|
{}.tap do |results|
|
||||||
|
secrets.each do |secret|
|
||||||
|
item, field = secret.split("/")
|
||||||
|
item = run_command("get item #{item}", session: session)
|
||||||
|
raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success?
|
||||||
|
if field
|
||||||
|
item_field = item["fields"].find { |f| f["name"] == field }
|
||||||
|
raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
|
||||||
|
value = item_field["value"]
|
||||||
|
results[secret] = value
|
||||||
|
results[field] = value
|
||||||
|
else
|
||||||
|
results[secret] = item["login"]["password"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def signedin?(account)
|
||||||
|
JSON.parse(`bw status`.strip)["status"] != "unauthenticated"
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_command(command, session: nil, raw: false)
|
||||||
|
full_command = [ *("BW_SESSION=#{session}" if session), "bw", command ].join(" ")
|
||||||
|
result = `#{full_command}`.strip
|
||||||
|
raw ? result : JSON.parse(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
29
lib/kamal/secrets/adapters/last_pass.rb
Normal file
29
lib/kamal/secrets/adapters/last_pass.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
|
||||||
|
private
|
||||||
|
def login(account)
|
||||||
|
unless loggedin?(account)
|
||||||
|
`lpass login #{account}`
|
||||||
|
raise RuntimeError, "Failed to login to 1Password" unless $?.success?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def loggedin?(account)
|
||||||
|
`lpass status --color never`.strip == "Logged in as #{account}."
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_from_vault(secrets, account:, session:)
|
||||||
|
items = JSON.parse(`lpass show #{secrets.join(" ")} --json`
|
||||||
|
raise RuntimeError, "Could not read #{fields} from 1Password" unless $?.success?
|
||||||
|
|
||||||
|
{}.tap do |results|
|
||||||
|
items.each do |item|
|
||||||
|
results[item["name"]] = item["password"]
|
||||||
|
results[item["fullname"]] = item["password"]
|
||||||
|
end
|
||||||
|
|
||||||
|
if (missing_items = secrets - results.keys).any?
|
||||||
|
raise RuntimeError, "Could not find #{missing_items.join(", ")} in LassPass"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user