feat(access-control-prover): record authority review ceremony

This commit is contained in:
Marius Ciepluch
2026-11-01 17:01:44 +01:00
parent a5ebc69a6e
commit 56fb129b7c
7 changed files with 418 additions and 1 deletions
+1 -1
View File
@@ -352,7 +352,7 @@ its final resolution, dominance, fact-acquisition and measurement semantics with
- _Requirements: 13.2, 13.3_ - _Requirements: 13.2, 13.3_
- _Boundary: Installed Fact Authority Wire_ - _Boundary: Installed Fact Authority Wire_
- _Depends: 14.2_ - _Depends: 14.2_
- [ ] 14.4 Record the approved repo-local review ceremony - [x] 14.4 Record the approved repo-local review ceremony
- Write the three closed canonical identity manifests with the exact schema, role, name and approval-basis values declared by the design. - Write the three closed canonical identity manifests with the exact schema, role, name and approval-basis values declared by the design.
- Bind bootstrap reviewer admission, custody policy, authority review and pack evidence identities to those manifest digests while keeping all trust/store admission outside `authority.json`. - Bind bootstrap reviewer admission, custody policy, authority review and pack evidence identities to those manifest digests while keeping all trust/store admission outside `authority.json`.
- Done when the approved manifests and bootstrap load through Task 14.3, all identity bindings validate, and changing any field or approval basis invalidates authentication. - Done when the approved manifests and bootstrap load through Task 14.3, all identity bindings validate, and changing any field or approval basis invalidates authentication.
@@ -0,0 +1,201 @@
"""Approved repo-local review ceremony for installed entry authority."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from openultrasast.core import canonical
from openultrasast.mapping._installed_fact_authority_codec import parse_record, text
from openultrasast.mapping.fact_authority import (
EntryAuthorityBootstrapPolicy,
EntryFactAuthorityBundle,
derive_entry_authority_bootstrap_policy_digest,
)
from openultrasast.mapping.installed_fact_authority import decode_bootstrap_bytes
AUTHORITY_IDENTITY_SCHEMA = "openultrasast.access-control.authority-identity.v1"
INSTALLED_ARTIFACT_STORE_IDENTITY = "openultrasast-installed-entry-artifacts-v1"
_IDENTITY_FIELDS = ("schema_version", "role", "name", "approval_basis")
_MAXIMUM_BUNDLE_BYTES = 1_048_576
class AuthorityCeremonyError(ValueError):
"""Installed authority identities do not match the approved ceremony."""
@dataclass(frozen=True)
class AuthorityIdentityManifest:
"""Closed canonical identity whose digest is used at authority boundaries."""
schema_version: str
role: str
name: str
approval_basis: str
@property
def identity_digest(self) -> str:
return canonical.bytes_digest(identity_manifest_bytes(self))
APPROVED_IDENTITY_MANIFESTS = (
AuthorityIdentityManifest(
AUTHORITY_IDENTITY_SCHEMA,
"producer",
"openultrasast-entry-pack-generator-v1",
"generated-from-reviewed-entry-data",
),
AuthorityIdentityManifest(
AUTHORITY_IDENTITY_SCHEMA,
"reviewer",
"openultrasast-maintainer",
"maintainer-session-approval:2026-11-01",
),
AuthorityIdentityManifest(
AUTHORITY_IDENTITY_SCHEMA,
"custodian",
"openultrasast-installed-entry-custodian-v1",
"checked-in-artifact-custody",
),
)
@dataclass(frozen=True)
class InstalledAuthorityCeremony:
"""Approved identities and the external host bootstrap policy."""
producer: AuthorityIdentityManifest
reviewer: AuthorityIdentityManifest
custodian: AuthorityIdentityManifest
bootstrap: EntryAuthorityBootstrapPolicy
def identity_manifest_bytes(value: AuthorityIdentityManifest) -> bytes:
"""Encode an identity manifest using the closed canonical schema."""
if not isinstance(value, AuthorityIdentityManifest):
raise AuthorityCeremonyError("identity manifest must use the typed contract")
record: dict[str, canonical.JsonValue] = {
"schema_version": value.schema_version,
"role": value.role,
"name": value.name,
"approval_basis": value.approval_basis,
}
try:
return canonical.canonical_record_bytes(record, known_schema_versions=(AUTHORITY_IDENTITY_SCHEMA,))
except (TypeError, ValueError) as exc:
raise AuthorityCeremonyError(f"invalid identity manifest: {exc}") from exc
def decode_identity_manifest_bytes(content: bytes) -> AuthorityIdentityManifest:
"""Decode only canonical bytes with the exact identity field set."""
try:
row = parse_record(content, AUTHORITY_IDENTITY_SCHEMA, _IDENTITY_FIELDS, "authority identity")
value = AuthorityIdentityManifest(
text(row["schema_version"], "schema_version"),
text(row["role"], "role"),
text(row["name"], "name"),
text(row["approval_basis"], "approval_basis"),
)
if identity_manifest_bytes(value) != content:
raise AuthorityCeremonyError("identity manifest does not round-trip canonically")
return value
except AuthorityCeremonyError:
raise
except (KeyError, TypeError, ValueError) as exc:
raise AuthorityCeremonyError(f"invalid authority identity: {exc}") from exc
def _approved_bootstrap(reviewer_digest: str) -> EntryAuthorityBootstrapPolicy:
reviewers = (reviewer_digest,)
forbidden: tuple[tuple[str, str], ...] = ()
stores = (INSTALLED_ARTIFACT_STORE_IDENTITY,)
digest = derive_entry_authority_bootstrap_policy_digest(
reviewers,
forbidden,
stores,
_MAXIMUM_BUNDLE_BYTES,
)
return EntryAuthorityBootstrapPolicy(reviewers, forbidden, stores, _MAXIMUM_BUNDLE_BYTES, digest)
def load_installed_authority_ceremony(entries_root: Path) -> InstalledAuthorityCeremony:
"""Load and pin the checked-in manifests plus external bootstrap policy."""
identity_root = entries_root / "identities"
filenames = ("generator.json", "maintainer-reviewer.json", "custodian.json")
try:
manifests = tuple(decode_identity_manifest_bytes((identity_root / name).read_bytes()) for name in filenames)
bootstrap = decode_bootstrap_bytes((entries_root / "bootstrap.json").read_bytes())
except OSError as exc:
raise AuthorityCeremonyError("installed ceremony record is unavailable") from exc
except ValueError as exc:
raise AuthorityCeremonyError(f"installed ceremony record is invalid: {exc}") from exc
if manifests != APPROVED_IDENTITY_MANIFESTS:
raise AuthorityCeremonyError("installed identity fields differ from the approved ceremony")
expected_bootstrap = _approved_bootstrap(manifests[1].identity_digest)
if bootstrap != expected_bootstrap:
raise AuthorityCeremonyError("installed bootstrap differs from the approved ceremony")
return InstalledAuthorityCeremony(*manifests, bootstrap)
def _require_binding(condition: bool, label: str) -> None:
if not condition:
raise AuthorityCeremonyError(f"{label} identity binding does not match the approved ceremony")
def _validate_pack_bindings(
bundle: EntryFactAuthorityBundle,
producer_digest: str,
reviewer_digest: str,
) -> None:
for pack in bundle.packs:
_require_binding(pack.review_identity == reviewer_digest, "pack review")
for evidence in pack.review_evidence:
_require_binding(evidence.producer_identity == producer_digest, "pack producer")
_require_binding(evidence.reviewer_identity == reviewer_digest, "pack reviewer")
_require_binding(evidence.independence.producer_identity == producer_digest, "pack attestation producer")
_require_binding(evidence.independence.reviewer_identity == reviewer_digest, "pack attestation reviewer")
def validate_authority_ceremony_bindings(
ceremony: InstalledAuthorityCeremony,
bundle: EntryFactAuthorityBundle,
) -> None:
"""Require every authority and pack identity to use the manifest digests."""
producer = ceremony.producer.identity_digest
reviewer = ceremony.reviewer.identity_digest
custodian = ceremony.custodian.identity_digest
bootstrap = ceremony.bootstrap
_require_binding(bootstrap.reviewed_bundle_reviewer_digests == (reviewer,), "bootstrap reviewer admission")
_require_binding(bootstrap.trusted_store_identities == (INSTALLED_ARTIFACT_STORE_IDENTITY,), "bootstrap store admission")
expected_policy = derive_entry_authority_bootstrap_policy_digest(
bootstrap.reviewed_bundle_reviewer_digests,
bootstrap.forbidden_identity_pairs,
bootstrap.trusted_store_identities,
bootstrap.maximum_bundle_bytes,
)
_require_binding(bootstrap.policy_digest == expected_policy, "bootstrap policy")
_require_binding(producer != reviewer, "producer/reviewer separation")
_require_binding((producer, reviewer) not in bootstrap.forbidden_identity_pairs, "forbidden pair")
_require_binding(bundle.review_policy.reviewed_reviewer_digests == (reviewer,), "review policy reviewer admission")
_require_binding(
bundle.review_policy.trusted_store_identities == bootstrap.trusted_store_identities,
"review policy store admission",
)
_require_binding(bundle.custody_policy.reviewed_custodian_digests == (custodian,), "custody")
_require_binding(bundle.authority_review.producer_identity == producer, "authority producer")
_require_binding(bundle.authority_review.reviewer_identity == reviewer, "authority reviewer")
_validate_pack_bindings(bundle, producer, reviewer)
__all__ = [
"APPROVED_IDENTITY_MANIFESTS",
"AUTHORITY_IDENTITY_SCHEMA",
"INSTALLED_ARTIFACT_STORE_IDENTITY",
"AuthorityCeremonyError",
"AuthorityIdentityManifest",
"InstalledAuthorityCeremony",
"decode_identity_manifest_bytes",
"identity_manifest_bytes",
"load_installed_authority_ceremony",
"validate_authority_ceremony_bindings",
]
@@ -0,0 +1 @@
{"forbidden_identity_pairs":[],"maximum_bundle_bytes":1048576,"policy_digest":"61045d1142ded9756329bc2a0fad3d656bc13b971f4c7c615017eecd86da128b","reviewed_bundle_reviewer_digests":["f12a7e897cbaf3059619517e1f8a725b90049a0edf3af08efa12fb1ef7fdee43"],"schema_version":"openultrasast.access-control.entry-authority-bootstrap.v2","trusted_store_identities":["openultrasast-installed-entry-artifacts-v1"]}
@@ -0,0 +1 @@
{"approval_basis":"checked-in-artifact-custody","name":"openultrasast-installed-entry-custodian-v1","role":"custodian","schema_version":"openultrasast.access-control.authority-identity.v1"}
@@ -0,0 +1 @@
{"approval_basis":"generated-from-reviewed-entry-data","name":"openultrasast-entry-pack-generator-v1","role":"producer","schema_version":"openultrasast.access-control.authority-identity.v1"}
@@ -0,0 +1 @@
{"approval_basis":"maintainer-session-approval:2026-11-01","name":"openultrasast-maintainer","role":"reviewer","schema_version":"openultrasast.access-control.authority-identity.v1"}
+212
View File
@@ -0,0 +1,212 @@
"""Approved repo-local identities for installed entry-fact authority."""
from __future__ import annotations
import json
from dataclasses import replace
from pathlib import Path
import pytest
from test_fact_authority import _case
from openultrasast.core import canonical, provenance
from openultrasast.core.factpack import (
derive_review_evidence_digest,
derive_review_independence_policy_digest,
)
from openultrasast.mapping.authority_ceremony import (
APPROVED_IDENTITY_MANIFESTS,
INSTALLED_ARTIFACT_STORE_IDENTITY,
AuthorityCeremonyError,
InstalledAuthorityCeremony,
decode_identity_manifest_bytes,
identity_manifest_bytes,
load_installed_authority_ceremony,
validate_authority_ceremony_bindings,
)
from openultrasast.mapping.fact_authority import EntryFactAuthorityBundle
from openultrasast.mapping.installed_fact_authority import decode_bootstrap_bytes
_ENTRIES = Path(__file__).parents[1] / "src" / "openultrasast" / "ruleset" / "entries"
def _canonical_json(value: object) -> bytes:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
def _bound_bundle(ceremony: InstalledAuthorityCeremony) -> EntryFactAuthorityBundle:
bundle = _case().bundle
producer = ceremony.producer.identity_digest
reviewer = ceremony.reviewer.identity_digest
custodian = ceremony.custodian.identity_digest
packs = []
for pack in bundle.packs:
evidence = tuple(
replace(
item,
producer_identity=producer,
reviewer_identity=reviewer,
independence=replace(
item.independence,
producer_identity=producer,
reviewer_identity=reviewer,
),
)
for item in pack.review_evidence
)
packs.append(
replace(
pack,
review_identity=reviewer,
review_evidence=evidence,
review_evidence_digest=derive_review_evidence_digest(evidence),
)
)
custody = provenance.PopulationCustodyPolicy(
(custodian,),
provenance.derive_population_custody_policy_digest((custodian,)),
)
stores = ceremony.bootstrap.trusted_store_identities
policy = replace(
bundle.review_policy,
reviewed_reviewer_digests=(reviewer,),
trusted_store_identities=stores,
)
policy = replace(
policy,
policy_digest=derive_review_independence_policy_digest(
policy.reviewed_reviewer_digests,
policy.forbidden_identity_pairs,
policy.trusted_store_identities,
policy.maximum_evidence_bytes,
),
)
packs = [replace(pack, review_policy_digest=policy.policy_digest) for pack in packs]
return replace(
bundle,
packs=tuple(packs),
custody_policy=custody,
review_policy=policy,
authority_review=replace(
bundle.authority_review,
producer_identity=producer,
reviewer_identity=reviewer,
),
)
def test_installed_ceremony_loads_canonical_nonempty_records() -> None:
paths = (
_ENTRIES / "identities" / "generator.json",
_ENTRIES / "identities" / "maintainer-reviewer.json",
_ENTRIES / "identities" / "custodian.json",
_ENTRIES / "bootstrap.json",
)
assert all(path.stat().st_size > 0 for path in paths)
ceremony = load_installed_authority_ceremony(_ENTRIES)
assert (ceremony.producer, ceremony.reviewer, ceremony.custodian) == APPROVED_IDENTITY_MANIFESTS
assert ceremony.bootstrap == decode_bootstrap_bytes((_ENTRIES / "bootstrap.json").read_bytes())
assert ceremony.bootstrap.reviewed_bundle_reviewer_digests == (ceremony.reviewer.identity_digest,)
assert ceremony.bootstrap.trusted_store_identities == (INSTALLED_ARTIFACT_STORE_IDENTITY,)
@pytest.mark.parametrize("mutation", ("unknown", "missing", "whitespace"))
def test_identity_decoder_rejects_open_or_noncanonical_records(mutation: str) -> None:
raw = json.loads(identity_manifest_bytes(APPROVED_IDENTITY_MANIFESTS[0]))
if mutation == "unknown":
raw["extra"] = "not-approved"
content = _canonical_json(raw)
elif mutation == "missing":
del raw["approval_basis"]
content = _canonical_json(raw)
else:
content = json.dumps(raw, indent=2).encode()
with pytest.raises(AuthorityCeremonyError):
decode_identity_manifest_bytes(content)
def test_identity_decoder_rejects_changed_schema_field() -> None:
raw = json.loads(identity_manifest_bytes(APPROVED_IDENTITY_MANIFESTS[0]))
raw["schema_version"] = "openultrasast.access-control.authority-identity.changed"
with pytest.raises(AuthorityCeremonyError):
decode_identity_manifest_bytes(_canonical_json(raw))
def test_synthetic_authority_and_pack_identities_bind_to_ceremony() -> None:
ceremony = load_installed_authority_ceremony(_ENTRIES)
validate_authority_ceremony_bindings(ceremony, _bound_bundle(ceremony))
@pytest.mark.parametrize(
("part", "field"),
(
("producer", "name"),
("producer", "role"),
("producer", "approval_basis"),
("reviewer", "name"),
("reviewer", "role"),
("reviewer", "approval_basis"),
("custodian", "name"),
("custodian", "role"),
("custodian", "approval_basis"),
),
)
def test_changed_identity_field_changes_digest_and_invalidates_binding(part: str, field: str) -> None:
ceremony = load_installed_authority_ceremony(_ENTRIES)
bundle = _bound_bundle(ceremony)
manifest = getattr(ceremony, part)
changed = replace(manifest, **{field: f"changed-{getattr(manifest, field)}"})
decoded = decode_identity_manifest_bytes(identity_manifest_bytes(changed))
changed_ceremony = replace(ceremony, **{part: decoded})
assert decoded.identity_digest != manifest.identity_digest
with pytest.raises(AuthorityCeremonyError, match="identity binding"):
validate_authority_ceremony_bindings(changed_ceremony, bundle)
@pytest.mark.parametrize(
"binding",
(
"bootstrap",
"bootstrap-store",
"review-policy-reviewer",
"review-policy-store",
"custody",
"authority-producer",
"authority-reviewer",
"pack-producer",
"pack-reviewer",
),
)
def test_every_ceremony_binding_is_required(binding: str) -> None:
ceremony = load_installed_authority_ceremony(_ENTRIES)
bundle = _bound_bundle(ceremony)
other = canonical.content_digest(f"other-{binding}")
if binding == "bootstrap":
changed = replace(ceremony.bootstrap, reviewed_bundle_reviewer_digests=(other,))
ceremony = replace(ceremony, bootstrap=changed)
elif binding == "bootstrap-store":
changed = replace(ceremony.bootstrap, trusted_store_identities=("other-store",))
ceremony = replace(ceremony, bootstrap=changed)
elif binding == "review-policy-reviewer":
bundle = replace(bundle, review_policy=replace(bundle.review_policy, reviewed_reviewer_digests=(other,)))
elif binding == "review-policy-store":
bundle = replace(bundle, review_policy=replace(bundle.review_policy, trusted_store_identities=("other-store",)))
elif binding == "custody":
bundle = replace(bundle, custody_policy=replace(bundle.custody_policy, reviewed_custodian_digests=(other,)))
elif binding.startswith("authority-"):
field = binding.removeprefix("authority-") + "_identity"
bundle = replace(bundle, authority_review=replace(bundle.authority_review, **{field: other}))
else:
field = binding.removeprefix("pack-") + "_identity"
pack = bundle.packs[0]
evidence = replace(pack.review_evidence[0], **{field: other})
bundle = replace(bundle, packs=(replace(pack, review_evidence=(evidence,)), *bundle.packs[1:]))
with pytest.raises(AuthorityCeremonyError, match="identity binding"):
validate_authority_ceremony_bindings(ceremony, bundle)