mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
feat(access-control-prover): record authority review ceremony
This commit is contained in:
@@ -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"}
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user