hacks/cli/bww.py

168 lines
5.4 KiB
Python
Raw Normal View History

2026-04-23 01:46:14 +00:00
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
import getpass
BW_EMAIL = "jeanmi.tremblay@gmail.com"
BW_PASS_PATH = "vaultwarden/jeanmi.tremblay@gmail.com/login"
BW_TOTP_PATH = "vaultwarden/jeanmi.tremblay@gmail.com/authenticator"
def run(cmd, input=None, check=True):
result = subprocess.run(cmd, input=input, capture_output=True, text=True)
if check and result.returncode != 0:
print(result.stderr.strip(), file=sys.stderr)
sys.exit(1)
return result.stdout.strip()
import tempfile
SESSION_FILE = os.path.join(tempfile.gettempdir(), f"bw_session_{os.getuid()}")
def save_session(session):
with open(SESSION_FILE, "w") as f:
f.write(session)
os.chmod(SESSION_FILE, 0o600)
def load_session():
try:
with open(SESSION_FILE) as f:
return f.read().strip()
except FileNotFoundError:
return ""
def ensure_session():
session = load_session() or os.environ.get("BW_SESSION", "")
if session:
result = subprocess.run(
["bw", "unlock", "--check"],
capture_output=True,
env={**os.environ, "BW_SESSION": session}
)
if result.returncode == 0:
os.environ["BW_SESSION"] = session
return session
print("Touch your YubiKey to decrypt password...")
password = run(["pass", "show", BW_PASS_PATH])
print("Touch your YubiKey to decrypt TOTP secret...")
totp_secret = run(["pass", "show", BW_TOTP_PATH])
totp = run(["oathtool", "--totp", "-b", totp_secret])
print("Authenticating...")
env = {**os.environ, "BW_PASSWORD": password}
session = subprocess.run(
["bw", "login", BW_EMAIL, "--passwordenv", "BW_PASSWORD", "--code", totp, "--raw"],
capture_output=True, text=True, env=env
).stdout.strip()
if not session:
session = subprocess.run(
["bw", "unlock", "--passwordenv", "BW_PASSWORD", "--raw"],
capture_output=True, text=True, env=env
).stdout.strip()
if not session:
print("Failed to get session.", file=sys.stderr)
sys.exit(1)
save_session(session)
os.environ["BW_SESSION"] = session
return session
def get_id(key, session):
items = json.loads(run(["bw", "list", "items", "--search", key, "--session", session]))
matches = [i for i in items if i["name"] == key]
return matches[0]["id"] if matches else None
def cmd_insert(key, session):
val = getpass.getpass("Secret: ")
val2 = getpass.getpass("Secret (again): ")
if val != val2:
print("Secrets do not match.", file=sys.stderr)
sys.exit(1)
item_id = get_id(key, session)
if item_id:
item = json.loads(run(["bw", "get", "item", item_id, "--session", session]))
item["login"]["password"] = val
encoded = run(["bw", "encode"], input=json.dumps(item))
run(["bw", "edit", "item", item_id, "--session", session], input=encoded)
else:
item = {"type": 1, "name": key, "login": {"username": "", "password": val}}
encoded = run(["bw", "encode"], input=json.dumps(item))
run(["bw", "create", "item", "--session", session], input=encoded)
print("Saved.")
def cmd_generate(key, session):
val = run(["bw", "generate", "-ulns", "--length", "32"])
item = {"type": 1, "name": key, "login": {"username": "", "password": val}}
encoded = run(["bw", "encode"], input=json.dumps(item))
run(["bw", "create", "item", "--session", session], input=encoded)
print(val)
def cmd_show(key, session):
result = subprocess.run(
["bw", "get", "password", key, "--session", session],
capture_output=True, text=True
)
if result.returncode != 0:
print(f"Not found: {key}", file=sys.stderr)
sys.exit(1)
print(result.stdout.strip())
def print_help():
print("Usage: bww.py insert|generate|show <key>\n")
print("Examples:")
print(" bww.py insert foo")
print(" Prompt for a secret (twice). Creates or updates the Vaultwarden item named 'foo' with the provided password.")
print("")
print(" bww.py show bar")
print(" Retrieves and prints the password for item 'bar' to stdout. Exits with error if not found.")
print("")
print(" bww.py generate foobar")
print(" Generates a 32-character password, stores it as the password for item 'foobar', and prints the generated value.")
print("")
print("Notes:")
print(" - The script reuses an unlocked Bitwarden session stored in a temp file or the BW_SESSION environment variable.")
print(" - If no valid session exists the script will attempt to unlock/login using values from your local 'pass' store and an oathtool TOTP code (it may prompt you to touch your YubiKey).")
print(" - Secrets and generated passwords are written to Vaultwarden via the 'bw' CLI.")
def main():
# Support a simple help flag
if len(sys.argv) >= 2 and sys.argv[1] in ("-h", "--help"):
print_help()
sys.exit(0)
if len(sys.argv) < 3:
print("Usage: bww insert|generate|show <key>", file=sys.stderr)
sys.exit(1)
cmd = sys.argv[1]
key = sys.argv[2]
session = ensure_session()
if cmd == "insert":
cmd_insert(key, session)
elif cmd == "generate":
cmd_generate(key, session)
elif cmd == "show":
cmd_show(key, session)
else:
print(f"Unknown command: {cmd}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()