secrets-engine/src/secrets_engine/provision.py

87 lines
3.2 KiB
Python
Raw Normal View History

"""Provisioning: get a secret value into OpenBao without it touching coordination
surfaces.
Modes:
- from-file: read a value from a mode-0600 file outside the repo, write it to
OpenBao, and (caller's choice) leave the source file for the operator to shred.
- generate: mint a random non-production value for build/test lanes only.
The value is held only in process memory and passed straight to the backend. It
is never logged, returned, or written to evidence.
"""
from __future__ import annotations
import secrets as _secrets
import string
from pathlib import Path
from secrets_engine.catalog import CatalogEntry
from secrets_engine.errors import ProvisioningError
from secrets_engine.openbao import OpenBaoClient
from secrets_engine.safe_paths import containing_git_worktree
def _read_value_file(path: Path) -> str:
if not path.exists():
raise ProvisioningError(f"value file not found: {path}")
st = path.stat()
if st.st_mode & 0o077:
raise ProvisioningError(
f"value file {path} is group/other-accessible "
f"(mode {oct(st.st_mode & 0o777)}); must be 0600"
)
worktree = containing_git_worktree(path)
if worktree is not None:
raise ProvisioningError(
f"value file {path} is inside a Git worktree ({worktree}); "
"keep secret material outside repos"
)
value = path.read_text(encoding="utf-8").strip()
if not value:
raise ProvisioningError(f"value file {path} is empty")
return value
def provision_from_file(
client: OpenBaoClient, entry: CatalogEntry, field: str, file_path: Path
) -> str:
"""Import a value from a strict-permission file. Returns the field name only."""
if not entry.stores_kv_value():
raise ProvisioningError(
f"lane '{entry.id}' is {entry.kind}; it has no KV value to provision"
)
if field not in entry.fields:
raise ProvisioningError(
f"field '{field}' not declared in lane '{entry.id}' fields {entry.fields}"
)
value = _read_value_file(Path(file_path))
if entry.manages_mount:
client.ensure_kv_mount(entry.mount)
client.kv_put(entry.mount, entry.path, field, value)
del value
return field
def provision_generated(client: OpenBaoClient, entry: CatalogEntry, field: str) -> str:
"""Generate a random NON-PRODUCTION value for build/test lanes only."""
if not entry.stores_kv_value():
raise ProvisioningError(
f"lane '{entry.id}' is {entry.kind}; it has no KV value to provision"
)
if entry.stage == "prod":
raise ProvisioningError(
f"refusing to generate a value for prod lane '{entry.id}'; "
"production values must be provisioned, not generated"
)
if field not in entry.fields:
raise ProvisioningError(
f"field '{field}' not declared in lane '{entry.id}' fields {entry.fields}"
)
alphabet = string.ascii_letters + string.digits
value = "test-" + "".join(_secrets.choice(alphabet) for _ in range(32))
if entry.manages_mount:
client.ensure_kv_mount(entry.mount)
client.kv_put(entry.mount, entry.path, field, value)
del value
return field