Files
Unraid-AppOps-Gateway-Public/backend/app/services/workmesh_secret_import.py
T

328 lines
11 KiB
Python

"""Fixed, write-only import of the existing WorkMesh controller credential."""
from __future__ import annotations
import hmac
import json
import re
import sys
from typing import Any, TextIO
from urllib.parse import urlparse
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
from sqlmodel import Session
from ..core.time import utc_now
from ..db.models import Secret
from ..db.session import engine
from . import secret_store
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .secret_store import get_secret, secret_storage_status
from .workmesh_promotion import _safe_preflight
SECRET_REF = "WORKMESH_DEPLOYMENT_BROKER"
TARGET = "jens/workmesh"
_ACTION = "deployment.secret.import_workmesh_controller"
_PURPOSE = "fixed_workmesh_controller_promotion"
_USED_BY = ["deployment:workmesh_promotion"]
_API_VERSION = "itworx.workmesh-controller-secret-import/v1"
_REVISION = re.compile(r"^[0-9a-f]{40}$")
_AUDIT_ID = re.compile(r"^audit-[0-9a-f]{12}$")
_MIN_CREDENTIAL_BYTES = 32
_MAX_CREDENTIAL_BYTES = 256
_FAILURE_CODES = frozenset({
"revision_invalid", "credential_invalid", "secret_reference_unreadable",
"secret_reference_conflict", "database_unavailable", "secret_storage_unavailable",
"session_not_clean", "controller_https_unverified", "controller_preflight_unavailable",
"controller_preflight_rejected", "audit_commit_failed", "secret_readback_failed",
"import_failed",
})
def _safe_error_code(code: object) -> str:
return code if isinstance(code, str) and code in _FAILURE_CODES else "import_failed"
class WorkMeshSecretImportError(ValueError):
"""Sanitized failure with a fixed operator-visible reason code."""
def __init__(self, code: str):
self.code = _safe_error_code(code)
super().__init__(self.code)
def _reject(code: str) -> None:
raise WorkMeshSecretImportError(code)
def _rollback_quietly(session: Session) -> bool:
try:
session.rollback()
except Exception: # noqa: BLE001 - rollback errors must not reach output.
return False
return True
def _validate_input(credential: object, revision: object) -> tuple[str, str]:
if (
not isinstance(revision, str)
or len(revision) != 40
or not _REVISION.fullmatch(revision)
):
_reject("revision_invalid")
if not isinstance(credential, str):
_reject("credential_invalid")
if not _MIN_CREDENTIAL_BYTES <= len(credential) <= _MAX_CREDENTIAL_BYTES:
_reject("credential_invalid")
try:
raw = credential.encode("ascii")
except UnicodeEncodeError:
_reject("credential_invalid")
if (
not _MIN_CREDENTIAL_BYTES <= len(raw) <= _MAX_CREDENTIAL_BYTES
or not credential.isprintable()
or any(character.isspace() for character in credential)
):
_reject("credential_invalid")
return credential, revision
def _controller_is_verified_https(broker: DeploymentBrokerClient) -> bool:
try:
return (
isinstance(broker.base_url, str)
and urlparse(broker.base_url).scheme == "https"
and broker.verify_tls is True
and broker.configuration_error() is None
)
except Exception: # noqa: BLE001 - config errors may include sensitive values.
return False
def _read_existing(session: Session, credential: str) -> bool:
"""Check the fixed row without touching its timestamps or metadata."""
try:
row = session.get(Secret, SECRET_REF)
if row is None:
session.commit()
return False
existing = get_secret(session, SECRET_REF, touch=False)
if existing is None:
_reject("secret_reference_unreadable")
if not hmac.compare_digest(existing, credential):
_reject("secret_reference_conflict")
session.commit()
return True
except WorkMeshSecretImportError:
_rollback_quietly(session)
raise
except Exception: # noqa: BLE001 - database errors are not operator output.
_rollback_quietly(session)
_reject("database_unavailable")
def _store_if_absent(
session: Session,
credential: str,
*,
revision: str,
preflight_sha256: str,
) -> bool:
"""Atomically add the fixed row; a concurrent value is never replaced."""
try:
encrypted = secret_store._fernet().encrypt(credential.encode("ascii")).decode("ascii")
now = utc_now()
metadata = {
"purpose": _PURPOSE,
"repository": TARGET,
"revision": revision,
"preflightSha256": preflight_sha256,
}
insert = (
sqlite_insert(Secret)
.values(
id=SECRET_REF,
name=SECRET_REF,
encrypted_value=encrypted,
created_at=now,
updated_at=now,
last_used_at=None,
used_by_json=list(_USED_BY),
metadata_json=metadata,
)
.on_conflict_do_nothing(index_elements=[Secret.id])
)
inserted = session.exec(insert).rowcount == 1
row = session.get(Secret, SECRET_REF)
if row is None:
_reject("database_unavailable")
stored = get_secret(session, SECRET_REF, touch=False)
if stored is None:
_reject("secret_reference_unreadable")
if not hmac.compare_digest(stored, credential):
_reject("secret_reference_conflict")
return inserted
except WorkMeshSecretImportError:
_rollback_quietly(session)
raise
except Exception: # noqa: BLE001 - crypto/database details stay private.
_rollback_quietly(session)
_reject("database_unavailable")
def import_workmesh_deployment_secret(
session: Session,
credential: str,
*,
revision: str,
broker: DeploymentBrokerClient | None = None,
) -> dict[str, Any]:
"""Bind an existing target credential after exact fixed-target preflight."""
value, requested_revision = _validate_input(credential, revision)
try:
if secret_storage_status().get("productionReady") is not True:
_reject("secret_storage_unavailable")
except WorkMeshSecretImportError:
raise
except Exception: # noqa: BLE001 - storage configuration may contain secrets.
_reject("secret_storage_unavailable")
if session.new or session.dirty or session.deleted:
_reject("session_not_clean")
_read_existing(session, value)
client = DeploymentBrokerClient() if broker is None else broker
if not _controller_is_verified_https(client):
_reject("controller_https_unverified")
try:
preflight = client.workmesh_promotion_preflight(
requested_revision,
secret=value,
)
except Exception: # noqa: BLE001 - transport errors may contain the key.
_reject("controller_preflight_unavailable")
if not isinstance(preflight, dict):
_reject("controller_preflight_rejected")
try:
safe = _safe_preflight(preflight, requested_revision)
except Exception: # noqa: BLE001 - reject malformed or hostile response objects.
safe = None
if safe is None:
_reject("controller_preflight_rejected")
preflight_sha256 = safe["preflightSha256"]
inserted = _store_if_absent(
session,
value,
revision=requested_revision,
preflight_sha256=preflight_sha256,
)
outcome = "imported" if inserted else "already_present"
try:
audit = log_event(
session,
source="deployment",
identity="operator",
action_id=_ACTION,
target=TARGET,
risk="high",
policy_decision="operator_import",
reason="Imported the fixed WorkMesh controller credential after verified preflight.",
result="success",
execution_plan={
"storeReference": SECRET_REF,
"repository": TARGET,
"revision": requested_revision,
"preflightSha256": preflight_sha256,
},
after_state={
"storeReference": SECRET_REF,
"status": "configured",
"repository": TARGET,
"revision": requested_revision,
"preflightSha256": preflight_sha256,
"outcome": outcome,
},
redacted_metadata={
"purpose": _PURPOSE,
"storeReference": SECRET_REF,
"repository": TARGET,
"revision": requested_revision,
"preflightSha256": preflight_sha256,
"outcome": outcome,
},
)
except Exception: # noqa: BLE001 - commit/audit failures must not echo details.
_rollback_quietly(session)
_reject("audit_commit_failed")
audit_id = audit.id if isinstance(audit.id, str) else ""
if not _AUDIT_ID.fullmatch(audit_id):
_reject("audit_commit_failed")
try:
readback = get_secret(session, SECRET_REF, touch=False)
except Exception: # noqa: BLE001 - do not expose ciphertext/decryption details.
_reject("secret_readback_failed")
if readback is None or not hmac.compare_digest(readback, value):
_reject("secret_readback_failed")
return {
"apiVersion": _API_VERSION,
"status": outcome,
"secretRef": SECRET_REF,
"repository": TARGET,
"revision": requested_revision,
"preflightSha256": preflight_sha256,
"auditId": audit_id,
"readback": "verified",
}
def _write_result(stream: TextIO, value: dict[str, str]) -> None:
stream.write(json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n")
stream.flush()
def main(
argv: list[str] | None = None,
*,
stdin=None,
stdout: TextIO | None = None,
) -> int:
"""Import the single fixed reference from bounded stdin."""
arguments = sys.argv[1:] if argv is None else argv
output = sys.stdout if stdout is None else stdout
if len(arguments) != 1 or not isinstance(arguments[0], str) or not _REVISION.fullmatch(arguments[0]):
_write_result(output, {"status": "blocked", "reason": "invalid_arguments"})
return 2
source = getattr(sys.stdin, "buffer", sys.stdin) if stdin is None else stdin
try:
raw = source.read(_MAX_CREDENTIAL_BYTES + 1)
if not isinstance(raw, bytes) or len(raw) > _MAX_CREDENTIAL_BYTES:
_write_result(output, {"status": "blocked", "reason": "invalid_input"})
return 2
try:
credential = raw.decode("ascii")
except UnicodeDecodeError:
_write_result(output, {"status": "blocked", "reason": "invalid_input"})
return 2
with Session(engine) as session:
result = import_workmesh_deployment_secret(
session,
credential,
revision=arguments[0],
broker=DeploymentBrokerClient(),
)
_write_result(output, {key: str(value) for key, value in result.items()})
return 0
except WorkMeshSecretImportError as error:
_write_result(output, {"status": "blocked", "reason": _safe_error_code(error.code)})
return 2
except Exception: # noqa: BLE001 - never print traceback or arbitrary values.
_write_result(output, {"status": "blocked", "reason": "import_failed"})
return 2
if __name__ == "__main__":
raise SystemExit(main())