Files
Unraid-AppOps-Gateway-Public/backend/tests/test_workmesh_secret_import.py
T

634 lines
21 KiB
Python

from __future__ import annotations
import io
import json
from datetime import datetime, timezone
import pytest
from sqlmodel import Session, SQLModel, create_engine, select
from backend.app.db.models import AuditEvent, Secret
from backend.app.services import workmesh_secret_import as importer
from backend.app.services.secret_store import get_secret, set_secret
REVISION = "a" * 40
CREDENTIAL = "W" * 48
PREFLIGHT_SHA = "b" * 64
BACKUP_SHA = "c" * 64
SECRET_REF = "WORKMESH_DEPLOYMENT_BROKER"
TARGET = "jens/workmesh"
CHECKS = {
"exactMain": "passed",
"pushValidation": "passed",
"controllerPreflight": "passed",
"dataBackup": "passed",
}
def _valid_preflight(revision: str = REVISION) -> dict[str, object]:
return {
"apiVersion": "itworx.workmesh-promotion/v2",
"status": "preflight_ok",
"repository": TARGET,
"revision": revision,
"preflightSha256": PREFLIGHT_SHA,
"backupInputsSha256": BACKUP_SHA,
"checks": dict(CHECKS),
"diagnostic": CREDENTIAL,
}
class FakeBroker:
def __init__(
self,
*,
response: object | None = None,
error: Exception | None = None,
base_url: str = "https://controller.example",
verify_tls: bool = True,
config_error: str | None = None,
before_response=None,
):
self.response = _valid_preflight() if response is None else response
self.error = error
self.base_url = base_url
self.verify_tls = verify_tls
self._config_error = config_error
self.before_response = before_response
self.preflight_calls: list[tuple[str, str]] = []
self.apply_calls = 0
def configuration_error(self):
return self._config_error
def workmesh_promotion_preflight(self, revision: str, *, secret: str):
self.preflight_calls.append((revision, secret))
if self.before_response is not None:
self.before_response()
if self.error is not None:
raise self.error
return self.response
def workmesh_promotion_apply(self, *args, **kwargs):
self.apply_calls += 1
raise AssertionError("the credential importer must not invoke promotion apply")
@pytest.fixture
def database(tmp_path, monkeypatch):
engine = create_engine(f"sqlite:///{tmp_path / 'workmesh-secret-import.db'}")
SQLModel.metadata.create_all(engine)
monkeypatch.setattr(
importer, "secret_storage_status", lambda: {"productionReady": True}
)
with Session(engine) as session:
yield engine, session
engine.dispose()
def _rows(session: Session) -> tuple[Secret | None, list[AuditEvent]]:
return (
session.get(Secret, SECRET_REF),
list(session.exec(select(AuditEvent)).all()),
)
def _database_dump(session: Session) -> str:
secret, audits = _rows(session)
return repr(
{
"secret": secret.model_dump() if secret is not None else None,
"audits": [event.model_dump() for event in audits],
}
)
def _import(
session: Session,
*,
credential: str = CREDENTIAL,
revision: str = REVISION,
broker: FakeBroker | None = None,
):
return importer.import_workmesh_deployment_secret(
session,
credential,
revision=revision,
broker=broker or FakeBroker(),
)
def test_import_creates_encrypted_secret_after_verified_fixed_preflight_and_audits(
database,
):
_, session = database
broker = FakeBroker()
result = _import(session, broker=broker)
row = session.get(Secret, SECRET_REF)
assert result["status"] == "imported"
assert result["secretRef"] == SECRET_REF
assert result["repository"] == TARGET
assert result["revision"] == REVISION
assert result["preflightSha256"] == PREFLIGHT_SHA
assert result["readback"] == "verified"
assert row is not None
assert row.encrypted_value != CREDENTIAL
assert get_secret(session, SECRET_REF, touch=False) == CREDENTIAL
assert broker.preflight_calls == [(REVISION, CREDENTIAL)]
assert broker.apply_calls == 0
audits = session.exec(select(AuditEvent)).all()
assert len(audits) == 1
audit = audits[0]
assert audit.target == TARGET
assert audit.result == "success"
assert audit.redacted_metadata["storeReference"] == SECRET_REF
assert audit.redacted_metadata["revision"] == REVISION
assert audit.redacted_metadata["preflightSha256"] == PREFLIGHT_SHA
assert CREDENTIAL not in _database_dump(session)
def test_identical_import_verifies_and_audits_without_mutating_secret(database):
_, session = database
assert _import(session)["status"] == "imported"
row = session.get(Secret, SECRET_REF)
assert row is not None
before = {
"encrypted_value": row.encrypted_value,
"created_at": row.created_at,
"updated_at": row.updated_at,
"last_used_at": row.last_used_at,
"used_by_json": list(row.used_by_json),
"metadata_json": dict(row.metadata_json),
}
result = _import(session)
session.refresh(row)
assert result["status"] == "already_present"
assert {
"encrypted_value": row.encrypted_value,
"created_at": row.created_at,
"updated_at": row.updated_at,
"last_used_at": row.last_used_at,
"used_by_json": list(row.used_by_json),
"metadata_json": dict(row.metadata_json),
} == before
assert len(session.exec(select(AuditEvent)).all()) == 2
@pytest.mark.parametrize("credential", ["W" * 32, "W" * 256])
def test_credential_length_contract_accepts_exact_boundaries(database, credential):
_, session = database
result = _import(session, credential=credential)
assert result["status"] == "imported"
assert get_secret(session, SECRET_REF, touch=False) == credential
def test_different_existing_value_is_rejected_before_controller_preflight(database):
_, session = database
set_secret(session, SECRET_REF, "D" * 48, metadata={"owner": "keep"})
original = session.get(Secret, SECRET_REF)
assert original is not None
before = original.model_dump()
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
session.rollback()
current = session.get(Secret, SECRET_REF)
assert current is not None and current.model_dump() == before
assert broker.preflight_calls == []
assert session.exec(select(AuditEvent)).all() == []
def test_undecryptable_existing_value_is_rejected_without_replacement(database):
_, session = database
set_secret(session, SECRET_REF, "D" * 48)
row = session.get(Secret, SECRET_REF)
assert row is not None
row.encrypted_value = "unreadable-ciphertext"
session.add(row)
session.commit()
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
assert session.get(Secret, SECRET_REF).encrypted_value == "unreadable-ciphertext"
assert broker.preflight_calls == []
def test_concurrent_identical_insert_winner_is_retained_and_audited(database):
engine, session = database
winner_timestamp = datetime(2026, 10, 4, 12, 0, 0, tzinfo=timezone.utc)
def concurrent_winner():
with Session(engine) as winner_session:
set_secret(
winner_session,
SECRET_REF,
CREDENTIAL,
used_by=["concurrent-owner"],
metadata={"owner": "concurrent-winner"},
)
winner = winner_session.get(Secret, SECRET_REF)
assert winner is not None
winner.created_at = winner_timestamp
winner.updated_at = winner_timestamp
winner_session.add(winner)
winner_session.commit()
broker = FakeBroker(before_response=concurrent_winner)
result = _import(session, broker=broker)
winner = session.get(Secret, SECRET_REF)
assert result["status"] == "already_present"
assert winner is not None
assert winner.created_at.replace(tzinfo=timezone.utc) == winner_timestamp
assert winner.updated_at.replace(tzinfo=timezone.utc) == winner_timestamp
assert winner.used_by_json == ["concurrent-owner"]
assert winner.metadata_json == {"owner": "concurrent-winner"}
assert get_secret(session, SECRET_REF, touch=False) == CREDENTIAL
assert len(session.exec(select(AuditEvent)).all()) == 1
def test_concurrent_different_insert_winner_is_never_overwritten(database):
engine, session = database
def concurrent_winner():
with Session(engine) as winner_session:
set_secret(
winner_session,
SECRET_REF,
"D" * 48,
metadata={"owner": "concurrent-winner"},
)
broker = FakeBroker(before_response=concurrent_winner)
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
session.rollback()
assert get_secret(session, SECRET_REF, touch=False) == "D" * 48
assert session.get(Secret, SECRET_REF).metadata_json == {
"owner": "concurrent-winner"
}
assert session.exec(select(AuditEvent)).all() == []
def test_production_secret_storage_must_be_ready(database, monkeypatch):
_, session = database
monkeypatch.setattr(
importer, "secret_storage_status", lambda: {"productionReady": False}
)
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
assert broker.preflight_calls == []
assert session.get(Secret, SECRET_REF) is None
@pytest.mark.parametrize(
"credential",
[
"W" * 31,
"W" * 257,
"W" * 31 + " ",
"W" * 31 + "\n",
"W" * 31 + "\u00e9",
"W" * 31 + "\x00",
],
)
def test_credential_must_be_bounded_printable_ascii_without_whitespace(
database, credential
):
_, session = database
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, credential=credential, broker=broker)
assert broker.preflight_calls == []
assert session.get(Secret, SECRET_REF) is None
@pytest.mark.parametrize("revision", ["main", "A" * 40, "a" * 39, "a" * 41])
def test_revision_must_be_an_exact_full_lowercase_commit(database, revision):
_, session = database
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, revision=revision, broker=broker)
assert broker.preflight_calls == []
assert session.get(Secret, SECRET_REF) is None
@pytest.mark.parametrize(
("base_url", "verify_tls", "config_error"),
[
("http://controller.example", True, None),
("https://controller.example", False, None),
("https://controller.example", True, "deployment_broker_url_invalid"),
],
)
def test_preflight_requires_verified_https_configuration(
database, base_url, verify_tls, config_error
):
_, session = database
broker = FakeBroker(
base_url=base_url,
verify_tls=verify_tls,
config_error=config_error,
)
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
assert broker.preflight_calls == []
assert session.get(Secret, SECRET_REF) is None
@pytest.mark.parametrize(
"mutate",
[
lambda value: value.update(status="preflight_failed"),
lambda value: value.update(apiVersion="wrong/v1"),
lambda value: value.update(repository="jens/other"),
lambda value: value.update(revision="f" * 40),
lambda value: value.update(preflightSha256="invalid"),
lambda value: value.update(backupInputsSha256="invalid"),
lambda value: value.update(checks={"exactMain": "passed"}),
lambda value: value["checks"].update(pushValidation="blocked"),
],
)
def test_invalid_fixed_preflight_never_writes_secret_or_audit(database, mutate):
_, session = database
preflight = _valid_preflight()
mutate(preflight)
broker = FakeBroker(response=preflight)
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
session.rollback()
assert session.get(Secret, SECRET_REF) is None
assert session.exec(select(AuditEvent)).all() == []
assert broker.apply_calls == 0
def test_non_mapping_preflight_is_rejected_without_storage_write(database):
_, session = database
broker = FakeBroker(response=["not", "a", "mapping"])
with pytest.raises(importer.WorkMeshSecretImportError):
_import(session, broker=broker)
session.rollback()
assert session.get(Secret, SECRET_REF) is None
def test_broker_exception_is_sanitized_and_never_persisted(database):
_, session = database
broker = FakeBroker(error=RuntimeError(f"upstream leaked {CREDENTIAL}"))
with pytest.raises(importer.WorkMeshSecretImportError) as error:
_import(session, broker=broker)
assert CREDENTIAL not in str(error.value)
assert broker.preflight_calls == [(REVISION, CREDENTIAL)]
session.rollback()
assert session.get(Secret, SECRET_REF) is None
assert session.exec(select(AuditEvent)).all() == []
def test_audit_insert_failure_rolls_back_secret_and_audit_together(database):
engine, session = database
def fail_audit_insert(conn, cursor, statement, parameters, context, executemany):
if statement.lstrip().lower().startswith("insert into auditevent"):
raise RuntimeError(f"database detail contained {CREDENTIAL}")
from sqlalchemy import event
event.listen(engine, "before_cursor_execute", fail_audit_insert)
try:
with pytest.raises(importer.WorkMeshSecretImportError) as error:
_import(session)
finally:
event.remove(engine, "before_cursor_execute", fail_audit_insert)
assert CREDENTIAL not in str(error.value)
session.rollback()
with Session(engine) as verify:
assert verify.get(Secret, SECRET_REF) is None
assert verify.exec(select(AuditEvent)).all() == []
def test_uncertain_readback_returns_blocked_without_replacing_committed_secret(
database, monkeypatch
):
engine, session = database
actual_get_secret = importer.get_secret
calls = 0
def fail_readback_once(db_session, name, *, touch=False):
nonlocal calls
calls += 1
if calls == 2:
return None
return actual_get_secret(db_session, name, touch=touch)
monkeypatch.setattr(importer, "get_secret", fail_readback_once)
broker = FakeBroker()
with pytest.raises(importer.WorkMeshSecretImportError) as error:
_import(session, broker=broker)
assert CREDENTIAL not in str(error.value)
monkeypatch.setattr(importer, "get_secret", actual_get_secret)
session.rollback()
with Session(engine) as verify:
row = verify.get(Secret, SECRET_REF)
assert row is not None
before = row.model_dump()
assert get_secret(verify, SECRET_REF, touch=False) == CREDENTIAL
assert len(verify.exec(select(AuditEvent)).all()) == 1
outcome = _import(session, broker=FakeBroker())
assert outcome["status"] == "already_present"
session.expire_all()
after = session.get(Secret, SECRET_REF)
assert after is not None and after.model_dump() == before
def test_uncertain_commit_returns_blocked_and_same_value_replay_does_not_replace(
database, monkeypatch
):
engine, session = database
actual_commit = session.commit
def commit_then_lose_acknowledgement():
if any(isinstance(item, AuditEvent) for item in session.new):
actual_commit()
raise RuntimeError(f"commit acknowledgement lost for {CREDENTIAL}")
actual_commit()
monkeypatch.setattr(session, "commit", commit_then_lose_acknowledgement)
with pytest.raises(importer.WorkMeshSecretImportError) as error:
_import(session)
assert CREDENTIAL not in str(error.value)
monkeypatch.setattr(session, "commit", actual_commit)
with Session(engine) as verify:
row = verify.get(Secret, SECRET_REF)
assert row is not None
before = row.model_dump()
assert get_secret(verify, SECRET_REF, touch=False) == CREDENTIAL
assert len(verify.exec(select(AuditEvent)).all()) == 1
result = _import(session, broker=FakeBroker())
assert result["status"] == "already_present"
session.expire_all()
after = session.get(Secret, SECRET_REF)
assert after is not None and after.model_dump() == before
assert len(session.exec(select(AuditEvent)).all()) == 2
def test_cli_imports_stdin_only_and_emits_sanitized_verification(database, monkeypatch):
engine, _ = database
broker = FakeBroker()
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", lambda: broker)
output = io.StringIO()
exit_code = importer.main(
[REVISION],
stdin=io.BytesIO(CREDENTIAL.encode("ascii")),
stdout=output,
)
result = json.loads(output.getvalue())
assert exit_code == 0
assert result["status"] == "imported"
assert result["secretRef"] == SECRET_REF
assert result["revision"] == REVISION
assert CREDENTIAL not in output.getvalue()
assert broker.preflight_calls == [(REVISION, CREDENTIAL)]
def test_cli_rejects_extra_arguments_before_reading_secret(database, monkeypatch):
engine, _ = database
broker = FakeBroker()
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", lambda: broker)
output = io.StringIO()
class UnreadableInput:
def read(self, _size):
pytest.fail("invalid command line must be rejected before reading stdin")
exit_code = importer.main(
[REVISION, CREDENTIAL],
stdin=UnreadableInput(),
stdout=output,
)
assert exit_code != 0
assert json.loads(output.getvalue()) == {
"reason": "invalid_arguments",
"status": "blocked",
}
assert broker.preflight_calls == []
def test_cli_rejects_non_ascii_stdin_without_calling_controller(database, monkeypatch):
engine, _ = database
broker = FakeBroker()
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", lambda: broker)
output = io.StringIO()
exit_code = importer.main(
[REVISION],
stdin=io.BytesIO(b"W" * 47 + b"\xff"),
stdout=output,
)
assert exit_code != 0
assert json.loads(output.getvalue()) == {
"reason": "invalid_input",
"status": "blocked",
}
assert broker.preflight_calls == []
def test_cli_rejects_oversized_stdin_without_calling_controller(database, monkeypatch):
engine, _ = database
broker = FakeBroker()
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", lambda: broker)
output = io.StringIO()
exit_code = importer.main(
[REVISION],
stdin=io.BytesIO(b"W" * 4097),
stdout=output,
)
assert exit_code != 0
assert json.loads(output.getvalue())["status"] == "blocked"
assert "W" * 48 not in output.getvalue()
assert broker.preflight_calls == []
def test_cli_unexpected_error_never_echoes_secret(database, monkeypatch):
engine, _ = database
broker = FakeBroker(error=RuntimeError(f"unexpected {CREDENTIAL}"))
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", lambda: broker)
output = io.StringIO()
exit_code = importer.main(
[REVISION],
stdin=io.BytesIO(CREDENTIAL.encode("ascii")),
stdout=output,
)
result = json.loads(output.getvalue())
assert exit_code != 0
assert result["status"] == "blocked"
assert CREDENTIAL not in output.getvalue()
assert "unexpected" not in output.getvalue()
assert "traceback" not in output.getvalue().lower()
@pytest.mark.parametrize("tampered", [False, True])
def test_cli_unknown_typed_error_code_is_normalized(database, monkeypatch, tampered):
engine, _ = database
monkeypatch.setattr(importer, "engine", engine)
monkeypatch.setattr(importer, "DeploymentBrokerClient", FakeBroker)
def fail_import(*args, **kwargs):
error = importer.WorkMeshSecretImportError(CREDENTIAL)
if tampered:
error.code = CREDENTIAL
raise error
monkeypatch.setattr(importer, "import_workmesh_deployment_secret", fail_import)
output = io.StringIO()
assert importer.main(
[REVISION], stdin=io.BytesIO(CREDENTIAL.encode("ascii")), stdout=output
) == 2
assert json.loads(output.getvalue()) == {
"status": "blocked", "reason": "import_failed"
}
assert CREDENTIAL not in output.getvalue()