pyinfra/scripts/bw2secrets
2024-08-21 23:51:49 +02:00

159 lines
4.1 KiB
Python
Executable file

#!/usr/bin/env python3
import argparse
import enum
import getpass
import shutil
import subprocess as sp
from pathlib import Path
import jinja2
PROJECT_ROOT_DIRECTORY = Path(__file__).parent.parent.resolve()
bitwarden_session = None
class TemplateEnvType(enum.StrEnum):
USERNAME = "username"
PASSWORD = "password"
class TemplateEnv:
bw_path: Path
env_type: TemplateEnvType
cached_items: dict[str, str]
def _fetch_secret(self, secret_id: str) -> str:
global bitwarden_session
if not bitwarden_session:
raise NotImplementedError("Failed to get bitwarden session")
res = sp.run(
[
self.bw_path, "get", self.env_type.value,
secret_id, "--session", bitwarden_session,
],
capture_output=True,
text=True,
)
res.check_returncode()
return res.stdout
def __init__(self, _type: TemplateEnvType, bw_path: Path):
self.env_type = _type
self.bw_path = bw_path
self.cached_items = dict()
def __getitem__(self, item):
if cached_item := self.cached_items.get(item):
return cached_item
self.cached_items[item] = self._fetch_secret(
item,
)
return self.cached_items[item]
def _add_args(parser: argparse.ArgumentParser):
parser.add_argument(
"search_paths",
help="start directory to walk files to find secret references",
default=[PROJECT_ROOT_DIRECTORY],
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 := (PROJECT_ROOT_DIRECTORY / ".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 sync_bw_session(bw_path: Path):
global bitwarden_session
if not bitwarden_session:
raise NotImplementedError("Failed to get bitwarden session")
res = sp.run(
[bw_path, "sync", "--session", bitwarden_session], capture_output=True,
text=True,
)
res.check_returncode()
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("**/*.template"):
print(f"INFO: Found template at {env_template}")
env_templates.add(env_template)
return env_templates
def compile_file(file_path: Path, bw_path: Path):
jinja_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(file_path.parent),
)
username = TemplateEnv(TemplateEnvType.USERNAME, bw_path)
password = TemplateEnv(TemplateEnvType.PASSWORD, bw_path)
template = jinja_env.get_template(file_path.name)
rendered_template = template.render(
dict(
username=username,
password=password,
),
)
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] = set()
for path in args.search_paths:
search_path = Path(path)
search_paths.add(search_path)
print(f"INFO: Will be searching {path}")
print("INFO: Searching templates")
template_files = find_templates(search_paths)
init_bw_session(bw_path)
sync_bw_session(bw_path)
for file in template_files:
print(f"INFO: Compiling {file}")
compile_file(file, bw_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())