mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
125 lines
3.4 KiB
Python
125 lines
3.4 KiB
Python
"""1Password CLI integration for knoe secret management.
|
|
|
|
All secrets are stored in the 'knoey' vault so they are isolated from the
|
|
user's personal 1Password vaults. The 'administrator' item holds the
|
|
database master password.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import secrets
|
|
import shutil
|
|
import string
|
|
import subprocess
|
|
|
|
_VAULT = "knoey"
|
|
_ADMIN_ITEM = "administrator"
|
|
|
|
|
|
def op_available() -> bool:
|
|
return shutil.which("op") is not None
|
|
|
|
|
|
def _op(*args: str, check: bool = True) -> subprocess.CompletedProcess:
|
|
if not op_available():
|
|
raise RuntimeError(
|
|
"1Password CLI (op) not found. Install: brew install 1password-cli"
|
|
)
|
|
return subprocess.run(
|
|
["op", *args],
|
|
capture_output=True,
|
|
text=True,
|
|
check=check,
|
|
)
|
|
|
|
|
|
def ensure_op_signed_in() -> None:
|
|
"""Ensure the op CLI has an active session; trigger sign-in if not."""
|
|
if not op_available():
|
|
raise RuntimeError(
|
|
"1Password CLI (op) not found. Install: brew install 1password-cli"
|
|
)
|
|
result = subprocess.run(
|
|
["op", "whoami"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if result.returncode != 0:
|
|
subprocess.run(["op", "signin"], check=True)
|
|
|
|
|
|
def ensure_knoey_vault() -> None:
|
|
"""Create the 'knoey' vault if it does not already exist."""
|
|
result = _op("vault", "list", "--format", "json", check=True)
|
|
try:
|
|
vaults = json.loads(result.stdout or "[]")
|
|
except json.JSONDecodeError:
|
|
vaults = []
|
|
names = [v.get("name", "") for v in vaults]
|
|
if _VAULT not in names:
|
|
_op("vault", "create", _VAULT)
|
|
print(f"[INFO] Created 1Password vault '{_VAULT}'")
|
|
else:
|
|
print(f"[INFO] 1Password vault '{_VAULT}' already exists")
|
|
|
|
|
|
def get_secret(item: str, field: str = "password") -> str:
|
|
"""Retrieve a field value from an item in the knoey vault."""
|
|
result = _op(
|
|
"item", "get", item,
|
|
"--vault", _VAULT,
|
|
"--fields", field,
|
|
"--reveal",
|
|
check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
return ""
|
|
return result.stdout.strip()
|
|
|
|
|
|
def set_secret(item: str, field: str, value: str) -> None:
|
|
"""Set a field on an existing item, or create the item if absent."""
|
|
check_result = _op("item", "get", item, "--vault", _VAULT, check=False)
|
|
if check_result.returncode == 0:
|
|
_op(
|
|
"item", "edit", item,
|
|
"--vault", _VAULT,
|
|
f"{field}={value}",
|
|
)
|
|
else:
|
|
_op(
|
|
"item", "create",
|
|
"--category", "login",
|
|
"--title", item,
|
|
"--vault", _VAULT,
|
|
f"{field}={value}",
|
|
)
|
|
|
|
|
|
def get_administrator_password() -> str:
|
|
"""Return the administrator password from the knoey vault."""
|
|
return get_secret(_ADMIN_ITEM, "password")
|
|
|
|
|
|
def ensure_administrator_secret() -> str:
|
|
"""Return the administrator password, creating the item if absent."""
|
|
pw = get_secret(_ADMIN_ITEM, "password")
|
|
if pw:
|
|
return pw
|
|
pw = _generate_password()
|
|
_op(
|
|
"item", "create",
|
|
"--category", "login",
|
|
"--title", _ADMIN_ITEM,
|
|
"--vault", _VAULT,
|
|
f"password={pw}",
|
|
)
|
|
print(f"[INFO] Created 1Password item '{_ADMIN_ITEM}' in vault '{_VAULT}'")
|
|
return pw
|
|
|
|
|
|
def _generate_password(length: int = 32) -> str:
|
|
alphabet = string.ascii_letters + string.digits
|
|
return "".join(secrets.choice(alphabet) for _ in range(length))
|