From 372fb193d7a7b74b45402726113b8aae55b6d5a8 Mon Sep 17 00:00:00 2001 From: aidankhogg Date: Tue, 30 Jun 2026 16:50:33 +0100 Subject: [PATCH] Add authority world manifest model --- netengine/spec/__init__.py | 4 ++ netengine/spec/authority.py | 90 +++++++++++++++++++++++++++++++++++- tests/test_authority_spec.py | 65 ++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/netengine/spec/__init__.py b/netengine/spec/__init__.py index 0f64019..62a0a65 100644 --- a/netengine/spec/__init__.py +++ b/netengine/spec/__init__.py @@ -8,7 +8,9 @@ BoundaryPolicy, ResolverPolicy, TrustBundle, + WorldManifest, default_authorities_for_spec, + world_manifest_from_spec, resolver_policy_from_boundary, ) @@ -20,6 +22,8 @@ "BoundaryPolicy", "ResolverPolicy", "TrustBundle", + "WorldManifest", "default_authorities_for_spec", + "world_manifest_from_spec", "resolver_policy_from_boundary", ] diff --git a/netengine/spec/authority.py b/netengine/spec/authority.py index f8c501b..5e06824 100644 --- a/netengine/spec/authority.py +++ b/netengine/spec/authority.py @@ -2,11 +2,11 @@ from enum import Enum from pydantic import Field, model_validator -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from netengine.spec.models import CrossWorldPeer, ServiceMirror, SpecModel -from netengine.spec.types import GatewayCrossWorldMode, GatewayRealInternetMode +from netengine.spec.types import GatewayCrossWorldMode, GatewayRealInternetMode, Lifecycle if TYPE_CHECKING: from netengine.spec.models import NetEngineSpec @@ -105,6 +105,34 @@ def validate_trust_bundle(self) -> "TrustBundle": return self +class WorldManifest(SpecModel): + """Authority-layer manifest for a NetEngine world. + + The manifest provides a compact authority/spec-domain view of the + governance, naming, identity, trust, registry, numbering, and boundary + surfaces that define a world. + """ + + world_id: str = Field(...) + world_name: str = Field(...) + lifecycle: Lifecycle = Field(...) + authority_model: str = Field(...) + authorities: list[Authority] = Field(default_factory=list) + dns_root_authority: str = Field(...) + ca_trust_authority: str = Field(...) + platform_identity_issuer: str = Field(...) + inworld_identity_issuer: str = Field(...) + world_registry_authority: str = Field(...) + domain_registry_authority: str = Field(...) + numbering_authority: str = Field(...) + transit_boundary_authority: str = Field(...) + real_internet_posture: GatewayRealInternetMode = Field(...) + cross_world_posture: GatewayCrossWorldMode = Field(...) + exported_authority_metadata: dict[str, Any] = Field(default_factory=dict) + importable_authority_metadata: dict[str, Any] = Field(default_factory=dict) + trust_bundles: list[TrustBundle] = Field(default_factory=list) + + class BoundaryPolicy(SpecModel): """Authority-layer policy for traffic and trust at the world boundary.""" @@ -307,3 +335,61 @@ def default_authorities_for_spec(spec: "NetEngineSpec") -> list[Authority]: description="Authority over the application service catalog.", ), ] + + +def _trust_bundles_from_spec(spec: "NetEngineSpec") -> list[TrustBundle]: + """Derive importable trust bundles from configured cross-world peers.""" + + bundles: list[TrustBundle] = [] + for peer in spec.gateway_portal.cross_world.peers: + mode = peer.mode + if mode == GatewayCrossWorldMode.NONE: + continue + + if mode == GatewayCrossWorldMode.FEDERATED and not ( + peer.trust_anchor_cert or peer.trust_bundle + ): + continue + + bundles.append( + TrustBundle( + peer_id=peer.name, + peer_name=peer.name, + mode=mode, + dns_suffixes=[peer.name], + peer_root_ca=peer.trust_anchor_cert, + oidc_issuer=peer.trust_bundle, + ) + ) + + return bundles + + +def world_manifest_from_spec(spec: "NetEngineSpec") -> WorldManifest: + """Build an authority-layer world manifest from a parsed NetEngine spec. + + NetEngine specs do not currently expose a dedicated stronger world-id field, + so the manifest intentionally seeds both identity fields from + ``spec.metadata.name``. + """ + + authorities = default_authorities_for_spec(spec) + + return WorldManifest( + world_id=spec.metadata.name, + world_name=spec.metadata.name, + lifecycle=spec.metadata.lifecycle, + authority_model="default", + authorities=authorities, + dns_root_authority="root-naming", + ca_trust_authority="trust-root", + platform_identity_issuer="platform-identity", + inworld_identity_issuer="inworld-identity", + world_registry_authority="world-root", + domain_registry_authority="domain-registry", + numbering_authority="numbering", + transit_boundary_authority="transit-boundary", + real_internet_posture=spec.gateway_portal.real_internet.mode, + cross_world_posture=spec.gateway_portal.cross_world.mode, + trust_bundles=_trust_bundles_from_spec(spec), + ) diff --git a/tests/test_authority_spec.py b/tests/test_authority_spec.py index 9abc1ac..3d7326e 100644 --- a/tests/test_authority_spec.py +++ b/tests/test_authority_spec.py @@ -11,6 +11,7 @@ BoundaryPolicy, ResolverPolicy, TrustBundle, + default_authorities_for_spec, resolver_policy_from_boundary, ) from netengine.spec.models import CrossWorldPeer, ServiceMirror @@ -307,3 +308,67 @@ def test_default_authorities_for_spec_maps_spec_sections(minimal_spec) -> None: ] assert by_id["mail-authority"].controls == ["world_services.mail"] assert by_id["service-catalog"].controls == ["org_apps.catalog"] + + +def test_world_manifest_from_spec_uses_metadata_name_and_default_authorities(minimal_spec) -> None: + from netengine.spec import WorldManifest, world_manifest_from_spec + + manifest = world_manifest_from_spec(minimal_spec) + + assert isinstance(manifest, WorldManifest) + assert manifest.world_id == minimal_spec.metadata.name + assert manifest.world_name == minimal_spec.metadata.name + assert manifest.lifecycle == minimal_spec.metadata.lifecycle + assert manifest.authority_model == "default" + assert [authority.id for authority in manifest.authorities] == [ + authority.id for authority in default_authorities_for_spec(minimal_spec) + ] + assert manifest.dns_root_authority == "root-naming" + assert manifest.ca_trust_authority == "trust-root" + assert manifest.platform_identity_issuer == "platform-identity" + assert manifest.inworld_identity_issuer == "inworld-identity" + assert manifest.world_registry_authority == "world-root" + assert manifest.domain_registry_authority == "domain-registry" + assert manifest.numbering_authority == "numbering" + assert manifest.transit_boundary_authority == "transit-boundary" + assert manifest.real_internet_posture == minimal_spec.gateway_portal.real_internet.mode + assert manifest.cross_world_posture == minimal_spec.gateway_portal.cross_world.mode + assert manifest.exported_authority_metadata == {} + assert manifest.importable_authority_metadata == {} + assert manifest.trust_bundles == [] + + +def test_world_manifest_from_spec_derives_cross_world_trust_bundles(minimal_spec) -> None: + from netengine.spec import world_manifest_from_spec + + spec = minimal_spec.model_copy( + update={ + "gateway_portal": minimal_spec.gateway_portal.model_copy( + update={ + "cross_world": minimal_spec.gateway_portal.cross_world.model_copy( + update={ + "mode": GatewayCrossWorldMode.FEDERATED, + "peers": [ + CrossWorldPeer( + name="world-b.internal", + endpoint="10.99.0.1:8443", + mode=GatewayCrossWorldMode.FEDERATED, + trust_anchor_cert="-----BEGIN CERTIFICATE-----...", + ) + ], + } + ) + } + ) + } + ) + + manifest = world_manifest_from_spec(spec) + + assert manifest.cross_world_posture == GatewayCrossWorldMode.FEDERATED + assert len(manifest.trust_bundles) == 1 + assert manifest.trust_bundles[0].peer_id == "world-b.internal" + assert manifest.trust_bundles[0].peer_name == "world-b.internal" + assert manifest.trust_bundles[0].mode == GatewayCrossWorldMode.FEDERATED + assert manifest.trust_bundles[0].dns_suffixes == ["world-b.internal"] + assert manifest.trust_bundles[0].peer_root_ca == "-----BEGIN CERTIFICATE-----..."