2024-06-08 00:53:33 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
|
|
import getpass
|
|
|
|
import shutil
|
|
|
|
import subprocess as sp
|
|
|
|
from pathlib import Path
|
2024-07-23 17:13:11 +02:00
|
|
|
from typing import Literal
|
2024-06-08 00:53:33 +02:00
|
|
|
|
|
|
|
import jinja2
|
|
|
|
|
|
|
|
bitwarden_session = None
|
|
|
|
|
2024-07-23 17:13:11 +02:00
|
|
|
TemplateEnvType = Literal["password", "username"]
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_secret(bw_path: Path, secret_id: str, object_type: str = "password") -> str:
|
|
|
|
global bitwarden_session
|
|
|
|
res = sp.run(
|
|
|
|
[bw_path, "get", object_type, secret_id, "--session", bitwarden_session],
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
|
|
|
res.check_returncode()
|
|
|
|
return res.stdout
|
|
|
|
|
|
|
|
|
|
|
|
class TemplateEnv:
|
|
|
|
bw_path: Path
|
|
|
|
env_type: TemplateEnvType
|
|
|
|
cached_items: dict[str, str]
|
|
|
|
|
|
|
|
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):
|
|
|
|
print(f"{self.env_type} {self.cached_items}")
|
|
|
|
if cached_item := self.cached_items.get(item):
|
|
|
|
return cached_item
|
|
|
|
self.cached_items[item] = fetch_secret(
|
|
|
|
self.bw_path,
|
|
|
|
item,
|
|
|
|
self.env_type,
|
|
|
|
)
|
|
|
|
return self.cached_items[item]
|
|
|
|
|
2024-06-08 00:53:33 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-06-08 13:17:04 +02:00
|
|
|
def sync_bw_session(bw_path: Path):
|
|
|
|
global bitwarden_session
|
|
|
|
res = sp.run(
|
|
|
|
[bw_path, "sync", "--session", bitwarden_session], capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
|
|
|
res.check_returncode()
|
|
|
|
|
|
|
|
|
2024-06-08 00:53:33 +02:00
|
|
|
def find_templates(base_dirs: set[Path]) -> set[Path]:
|
|
|
|
env_templates: set[Path] = set()
|
|
|
|
for path in base_dirs:
|
2024-07-23 17:13:11 +02:00
|
|
|
for env_template in path.glob("**/*.template"):
|
2024-06-08 00:53:33 +02:00
|
|
|
env_templates.add(env_template)
|
|
|
|
return env_templates
|
|
|
|
|
|
|
|
|
2024-07-23 17:13:11 +02:00
|
|
|
# def secret_filter(bw_path: Path, secret_id: str) -> str:
|
|
|
|
# return fetch_secret(bw_path, secret_id)
|
2024-06-08 00:53:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
def compile_file(file_path: Path, bw_path: Path):
|
|
|
|
jinja_env = jinja2.Environment(
|
|
|
|
loader=jinja2.FileSystemLoader(file_path.parent),
|
|
|
|
)
|
2024-07-23 17:13:11 +02:00
|
|
|
username = TemplateEnv("username", bw_path)
|
|
|
|
password = TemplateEnv("password", bw_path)
|
2024-06-08 00:53:33 +02:00
|
|
|
template = jinja_env.get_template(file_path.name)
|
|
|
|
|
2024-07-23 17:13:11 +02:00
|
|
|
rendered_template = template.render(
|
|
|
|
dict(
|
|
|
|
username=username,
|
|
|
|
password=password,
|
|
|
|
),
|
|
|
|
)
|
2024-06-08 00:53:33 +02:00
|
|
|
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)
|
2024-06-12 10:55:52 +02:00
|
|
|
sync_bw_session(bw_path)
|
2024-06-08 00:53:33 +02:00
|
|
|
|
|
|
|
for file in template_files:
|
|
|
|
compile_file(file, bw_path)
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
raise SystemExit(main())
|