328 lines
11 KiB
Python
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())
|