rotate replaces one declared KV field through the merge-safe patch path and never prints the value. Overlay states active/suspended/deactivated/ compromised live under the evidence directory. compromise/reactivate and successful suspend/deactivate/revoke update that overlay; exec/wrap/ handoff/provision refuse non-active lanes. Provider-side rotation stays with rotation.owner. Production remains fail-closed. Assistant: grok Assistant-Session: 01a05f07-ae72-7781-9fcb-19efd61add00
43 lines
1.6 KiB
Python
43 lines
1.6 KiB
Python
"""Native KV rotation: replace a declared field without touching siblings.
|
|
|
|
This updates OpenBao custody only. Catalog ``rotation.owner`` remains the
|
|
provider/workload owner; this engine does not roll consumers or revoke a
|
|
provider credential. Auth-capability lanes rotate via wrap/handoff, not here.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
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.provision import provision_from_file
|
|
|
|
|
|
def render_rotate_plan(entry: CatalogEntry, field: str) -> str:
|
|
owner = str((entry.rotation or {}).get("owner") or "")
|
|
lines = [
|
|
f"Rotate plan for lane '{entry.id}'",
|
|
f" field: {field}",
|
|
f" target: {entry.mount}/{entry.path}",
|
|
f" owner: {owner or '-'}",
|
|
" siblings: preserved (CAS patch)",
|
|
" workload delivery: not mutated",
|
|
]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def rotate_from_file(
|
|
client: OpenBaoClient, entry: CatalogEntry, field: str, file_path: Path
|
|
) -> str:
|
|
"""Replace one declared KV field. Returns the field name only."""
|
|
if not entry.stores_kv_value():
|
|
raise ProvisioningError(
|
|
f"lane '{entry.id}' is {entry.kind}; rotate native KV via this "
|
|
"command, or wrap/handoff for auth-capability material"
|
|
)
|
|
if field not in entry.fields:
|
|
raise ProvisioningError(
|
|
f"field '{field}' not declared in lane '{entry.id}' fields {entry.fields}"
|
|
)
|
|
return provision_from_file(client, entry, field, file_path)
|