#!/usr/bin/env python3 import argparse import getpass import shutil import subprocess as sp from pathlib import Path import jinja2 bitwarden_session = None def _add_args(parser: argparse.ArgumentParser): parser.add_argument( "search_paths", help="start directory to walk files to find secret references", default=".", nargs="*", ) parser.add_argument( "--bw-path", "-b", help="custom path for bitwarden cli executable", default=shutil.which("bw"), ) def init_bw_session(bw_path: Path): global bitwarden_session if (pw_file := Path("./.bw2secrets")).exists(): bitwarden_password = pw_file.read_text().strip() else: print("Please, provide your bitwarden master password") bitwarden_password = getpass.getpass("Master password: ") res = sp.run( [bw_path, "unlock", bitwarden_password, "--raw"], capture_output=True, text=True, ) res.check_returncode() bitwarden_session = res.stdout def find_templates(base_dirs: set[Path]) -> set[Path]: env_templates: set[Path] = set() for path in base_dirs: for env_template in path.glob(f"{path}/**/*.template"): env_templates.add(env_template) return env_templates def fetch_secret(bw_path: Path, secret_id: str) -> str: global bitwarden_session res = sp.run( [bw_path, "get", "password", secret_id, "--session", bitwarden_session], capture_output=True, text=True, ) res.check_returncode() return res.stdout def secret_filter(bw_path: Path, secret_id: str) -> str: return fetch_secret(bw_path, secret_id) def compile_file(file_path: Path, bw_path: Path): jinja_env = jinja2.Environment( loader=jinja2.FileSystemLoader(file_path.parent), ) jinja_env.filters['secret'] = lambda secret_id: secret_filter( bw_path, secret_id, ) template = jinja_env.get_template(file_path.name) rendered_template = template.render() file_path.with_name( file_path.name.replace( ".template", "", ), ).write_text(rendered_template) def main() -> int: parser = argparse.ArgumentParser("bw2secrets") _add_args(parser) args = parser.parse_args() if not (bw_path := args.bw_path): print("Bitwarden CLI `bw` executable not found in PATH") return 1 search_paths: set[Path | None] = set() for path in args.search_paths: search_path = Path(path) search_paths.add(search_path) template_files = find_templates(search_paths) init_bw_session(bw_path) for file in template_files: compile_file(file, bw_path) return 0 if __name__ == "__main__": raise SystemExit(main())