1247 lines
40 KiB
Rust
1247 lines
40 KiB
Rust
|
|
use std::collections::HashSet;
|
||
|
|
use std::fmt::Debug;
|
||
|
|
use std::num::NonZeroU16;
|
||
|
|
|
||
|
|
use lazaret_core::codec::{CodecError, MAX_SCHEMA_RECORD_BYTES, decode, encode};
|
||
|
|
use lazaret_core::identity::{
|
||
|
|
BundleDigest, CapabilityId, CertificateDigest, ClientId, ConfigurationFingerprint, ContainerId,
|
||
|
|
CoordinatorId, DerivedPolicyDigest, DeviceId, DomainId, FileDescriptor, GenerationId,
|
||
|
|
GuestProcessGroupId, HostPath, IdMapDigest, InodeId, MountId, MountIdentity, NamespaceIdentity,
|
||
|
|
NetworkSidecarId, PolicyId, PrepareOperationId, PreparedArtifactId, ProcessId, ProcessIdentity,
|
||
|
|
ProcessStartTime, ProtocolCredentialId, ReadinessFingerprint, RequestDigest, RequestId,
|
||
|
|
SandboxId, SelectedManifestDigest, SessionAnchorId, SessionId, SourceIdentity, StreamDigest,
|
||
|
|
TrustManifestDigest,
|
||
|
|
};
|
||
|
|
use lazaret_core::records::*;
|
||
|
|
use serde::de::DeserializeOwned;
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct RawParticipant {
|
||
|
|
generation_id: GenerationId,
|
||
|
|
role: ParticipantRole,
|
||
|
|
state: ParticipantState,
|
||
|
|
ownership: ParticipantOwnership,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct RawTlsCredential {
|
||
|
|
generation_identity: GenerationIdentity,
|
||
|
|
role: TlsRole,
|
||
|
|
subject_id: TlsSubjectId,
|
||
|
|
certificate_der: Vec<u8>,
|
||
|
|
certificate_sha256: CertificateDigest,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct RawProtocolCredential {
|
||
|
|
protocol_version: ProtocolVersion,
|
||
|
|
credential_id: ProtocolCredentialId,
|
||
|
|
generation_identity: GenerationIdentity,
|
||
|
|
role: ProtocolCredentialRole,
|
||
|
|
subject_id: ProtocolSubjectId,
|
||
|
|
public_key: Vec<u8>,
|
||
|
|
issuer_signature: Vec<u8>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct RawSandboxCredentialBundle {
|
||
|
|
protocol_version: ProtocolVersion,
|
||
|
|
generation_identity: GenerationIdentity,
|
||
|
|
trust_manifest: TrustManifest,
|
||
|
|
coordinator_protocol_credential: ProtocolCredential,
|
||
|
|
sandbox_protocol_credential: ProtocolCredential,
|
||
|
|
sandbox_private_key: Vec<u8>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct RawMetadata {
|
||
|
|
schema_id: String,
|
||
|
|
schema_version: u32,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct MissingVersion {
|
||
|
|
schema_id: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawDomainStore {
|
||
|
|
metadata: StoreMetadata,
|
||
|
|
domain: Domain,
|
||
|
|
generations: Vec<Generation>,
|
||
|
|
participants: Vec<Participant>,
|
||
|
|
sessions: Vec<Session>,
|
||
|
|
session_terminals: Vec<SessionTerminal>,
|
||
|
|
mailboxes: Vec<Mailbox>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawStorageCatalog {
|
||
|
|
metadata: StoreMetadata,
|
||
|
|
prepared_artifacts: Vec<PreparedArtifact>,
|
||
|
|
artifact_leases: Vec<ArtifactLease>,
|
||
|
|
prepare_operations: Vec<PrepareOperation>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawTerminalResize {
|
||
|
|
session_id: SessionId,
|
||
|
|
rows: u16,
|
||
|
|
columns: u16,
|
||
|
|
pixel_width: u16,
|
||
|
|
pixel_height: u16,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Deserialize, Serialize)]
|
||
|
|
struct InvalidSessionExit {
|
||
|
|
session_id: SessionId,
|
||
|
|
outcome: SessionExitOutcome,
|
||
|
|
exit_code: Option<i32>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawDerivedNetworkPolicy {
|
||
|
|
schema_id: String,
|
||
|
|
address_families: Vec<AddressFamilyDisposition>,
|
||
|
|
default_actions: Vec<DefaultAction>,
|
||
|
|
prefix_rules: Vec<PrefixRule>,
|
||
|
|
resolvers: Vec<Resolver>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawNetworkReadinessAttestation {
|
||
|
|
schema_id: String,
|
||
|
|
domain_id: DomainId,
|
||
|
|
generation_id: GenerationId,
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint,
|
||
|
|
network_sidecar_id: NetworkSidecarId,
|
||
|
|
derived_policy_digest: DerivedPolicyDigest,
|
||
|
|
enforcement_namespace: NamespaceIdentity,
|
||
|
|
runtime_supervisor: ProcessIdentity,
|
||
|
|
connectivity_helper: ProcessIdentity,
|
||
|
|
observation: NetworkObservation,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct RawNetworkReadinessIdentity {
|
||
|
|
schema_id: String,
|
||
|
|
domain_id: DomainId,
|
||
|
|
generation_id: GenerationId,
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint,
|
||
|
|
network_sidecar_id: NetworkSidecarId,
|
||
|
|
derived_policy_digest: DerivedPolicyDigest,
|
||
|
|
enforcement_namespace: NamespaceIdentity,
|
||
|
|
}
|
||
|
|
|
||
|
|
fn opaque(byte: u8) -> [u8; 32] {
|
||
|
|
[byte; 32]
|
||
|
|
}
|
||
|
|
|
||
|
|
fn domain_id() -> DomainId {
|
||
|
|
DomainId::new(opaque(1))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn generation_id() -> GenerationId {
|
||
|
|
GenerationId::new(7)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn coordinator_id() -> CoordinatorId {
|
||
|
|
CoordinatorId::new(opaque(2))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sidecar_id() -> NetworkSidecarId {
|
||
|
|
NetworkSidecarId::new(opaque(3))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sandbox_id() -> SandboxId {
|
||
|
|
SandboxId::new(opaque(4))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn session_id() -> SessionId {
|
||
|
|
SessionId::new(opaque(5))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn process(seed: u32) -> ProcessIdentity {
|
||
|
|
ProcessIdentity {
|
||
|
|
pid: ProcessId::new(seed),
|
||
|
|
start_time: ProcessStartTime::new(u64::from(seed) * 10),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn namespace() -> NamespaceIdentity {
|
||
|
|
NamespaceIdentity {
|
||
|
|
device: DeviceId::new(8),
|
||
|
|
inode: InodeId::new(9),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn mount() -> MountIdentity {
|
||
|
|
MountIdentity {
|
||
|
|
mount_id: MountId::new(10),
|
||
|
|
device: DeviceId::new(11),
|
||
|
|
inode: InodeId::new(12),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn generation_identity() -> GenerationIdentity {
|
||
|
|
GenerationIdentity {
|
||
|
|
agent_profile: "trusted".into(),
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(6)),
|
||
|
|
coordinator_id: coordinator_id(),
|
||
|
|
sandbox_id: sandbox_id(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn trust_manifest() -> TrustManifest {
|
||
|
|
TrustManifest {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
tls_ca_sha256: CertificateDigest::new(opaque(7)),
|
||
|
|
protocol_issuer_public_key: vec![8; 32],
|
||
|
|
issuer_signature: vec![9; 64],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn protocol_credential(role: ProtocolCredentialRole) -> ProtocolCredential {
|
||
|
|
match role {
|
||
|
|
ProtocolCredentialRole::SessionCoordinator => ProtocolCredential::session_coordinator(
|
||
|
|
ProtocolCredentialId::new(opaque(10)),
|
||
|
|
generation_identity(),
|
||
|
|
coordinator_id(),
|
||
|
|
vec![11; 32],
|
||
|
|
vec![12; 64],
|
||
|
|
),
|
||
|
|
ProtocolCredentialRole::AgentSandbox => ProtocolCredential::agent_sandbox(
|
||
|
|
ProtocolCredentialId::new(opaque(10)),
|
||
|
|
generation_identity(),
|
||
|
|
sandbox_id(),
|
||
|
|
vec![11; 32],
|
||
|
|
vec![12; 64],
|
||
|
|
),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn session_binding() -> SessionBinding {
|
||
|
|
SessionBinding {
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
cli_id: ClientId::new(opaque(13)),
|
||
|
|
session_id: session_id(),
|
||
|
|
session_anchor_public_key: vec![14; 32],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn session_capability() -> SessionRequestCapability {
|
||
|
|
SessionRequestCapability {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
capability_id: CapabilityId::new(opaque(15)),
|
||
|
|
session_binding: session_binding(),
|
||
|
|
coordinator_credential_id: ProtocolCredentialId::new(opaque(16)),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(17)),
|
||
|
|
coordinator_signature: vec![18; 64],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn network_observation() -> NetworkObservation {
|
||
|
|
NetworkObservation {
|
||
|
|
policy_delivery: PolicyDelivery::SealedMemfd,
|
||
|
|
helper_ready: true,
|
||
|
|
observed_routes: vec![IpNetwork {
|
||
|
|
address: IpAddress::V4([10, 0, 0, 0]),
|
||
|
|
prefix_length: 8,
|
||
|
|
}],
|
||
|
|
routes_attested: true,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn readiness_attestation() -> NetworkReadinessAttestation {
|
||
|
|
NetworkReadinessAttestation::v1(NetworkReadinessAttestationFields {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(19)),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
derived_policy_digest: DerivedPolicyDigest::new(opaque(20)),
|
||
|
|
enforcement_namespace: namespace(),
|
||
|
|
runtime_supervisor: process(21),
|
||
|
|
connectivity_helper: process(22),
|
||
|
|
observation: network_observation(),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
fn readiness_identity() -> NetworkReadinessIdentity {
|
||
|
|
NetworkReadinessIdentity::v1(NetworkReadinessIdentityFields {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(19)),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
derived_policy_digest: DerivedPolicyDigest::new(opaque(20)),
|
||
|
|
enforcement_namespace: namespace(),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
fn reachability_probe() -> NetworkReachabilityProbe {
|
||
|
|
NetworkReachabilityProbe {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
enforcement_namespace: namespace(),
|
||
|
|
denied_destinations: vec![DeniedDestinationObservation {
|
||
|
|
destination: IpAddress::V4([10, 1, 2, 3]),
|
||
|
|
unreachable: true,
|
||
|
|
}],
|
||
|
|
permitted_destinations: vec![PermittedDestinationObservation {
|
||
|
|
destination: IpAddress::V4([1, 1, 1, 1]),
|
||
|
|
reachable: true,
|
||
|
|
}],
|
||
|
|
dns_observations: vec![DnsObservation {
|
||
|
|
resolver: IpAddress::V4([9, 9, 9, 9]),
|
||
|
|
query_name: "example.invalid".into(),
|
||
|
|
resolved_addresses: vec![IpAddress::V6([1; 16])],
|
||
|
|
}],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn readiness_evidence() -> NetworkReadinessEvidence {
|
||
|
|
NetworkReadinessEvidence {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
readiness_fingerprint: ReadinessFingerprint::new(opaque(23)),
|
||
|
|
attestation: readiness_attestation(),
|
||
|
|
readiness_identity: readiness_identity(),
|
||
|
|
reachability_probe: reachability_probe(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn prepared_ownership() -> PreparedLowerOwnership {
|
||
|
|
PreparedLowerOwnership {
|
||
|
|
prepared_artifact_id: PreparedArtifactId::new(opaque(24)),
|
||
|
|
source_identity: SourceIdentity::new(vec![25, 26]),
|
||
|
|
lease_state: ParticipantState::LeaseHeld,
|
||
|
|
path: HostPath::new(vec![b'/', 0xff]),
|
||
|
|
device: DeviceId::new(27),
|
||
|
|
inode: InodeId::new(28),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn network_ownership() -> NetworkOwnership {
|
||
|
|
NetworkOwnership {
|
||
|
|
derived_policy_digest: DerivedPolicyDigest::new(opaque(20)),
|
||
|
|
readiness_fingerprint: ReadinessFingerprint::new(opaque(23)),
|
||
|
|
readiness_evidence: readiness_evidence(),
|
||
|
|
namespace: namespace(),
|
||
|
|
runtime_supervisor: process(21),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn verify_shape<T>(record: T, seen: &mut HashSet<&'static str>)
|
||
|
|
where
|
||
|
|
T: Clone + Debug + DeserializeOwned + Eq + SchemaType + Serialize,
|
||
|
|
{
|
||
|
|
assert!(seen.insert(T::NAME), "duplicate test for {}", T::NAME);
|
||
|
|
let encoded = encode(&record).expect("representative record encodes");
|
||
|
|
assert_eq!(encode(&record).unwrap(), encoded);
|
||
|
|
assert_eq!(decode::<T>(&encoded).unwrap(), record);
|
||
|
|
|
||
|
|
let mut truncated = encoded.clone();
|
||
|
|
truncated.pop();
|
||
|
|
assert!(decode::<T>(&truncated).is_err());
|
||
|
|
|
||
|
|
let mut trailing = encoded;
|
||
|
|
trailing.push(0);
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<T>(&trailing),
|
||
|
|
Err(CodecError::TrailingPayload)
|
||
|
|
));
|
||
|
|
drop(record);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
#[allow(clippy::too_many_lines)]
|
||
|
|
fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
|
||
|
|
let terminal = SessionTerminal {
|
||
|
|
generation_id: generation_id(),
|
||
|
|
session_id: session_id(),
|
||
|
|
state: SessionTerminalState::Running,
|
||
|
|
container_id: ContainerId::new(opaque(29)),
|
||
|
|
host_coordinator: process(30),
|
||
|
|
pty_master: PtyIdentity {
|
||
|
|
master_fd: FileDescriptor::new(31),
|
||
|
|
device: DeviceId::new(32),
|
||
|
|
inode: InodeId::new(33),
|
||
|
|
},
|
||
|
|
exec_client: process(34),
|
||
|
|
guest_process: GuestProcessIdentity {
|
||
|
|
leader_pid: ProcessId::new(35),
|
||
|
|
leader_start_time: ProcessStartTime::new(36),
|
||
|
|
process_group_id: GuestProcessGroupId::new(37),
|
||
|
|
},
|
||
|
|
};
|
||
|
|
let domain = Domain {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
profile_name: "trusted".into(),
|
||
|
|
canonical_workspace: HostPath::new(vec![b'/', b'w', 0xfe]),
|
||
|
|
};
|
||
|
|
let generation = Generation {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(6)),
|
||
|
|
coordinator_id: coordinator_id(),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
sandbox_id: sandbox_id(),
|
||
|
|
durable_decision: DurableDecision::Preparing,
|
||
|
|
};
|
||
|
|
let participant = Participant::new(
|
||
|
|
generation_id(),
|
||
|
|
ParticipantState::LeaseHeld,
|
||
|
|
ParticipantOwnership::PreparedLower(prepared_ownership()),
|
||
|
|
);
|
||
|
|
let session = Session {
|
||
|
|
generation_id: generation_id(),
|
||
|
|
session_id: session_id(),
|
||
|
|
cli_id: ClientId::new(opaque(13)),
|
||
|
|
session_anchor_id: SessionAnchorId::new(opaque(38)),
|
||
|
|
session_phase: SessionPhase::Attached,
|
||
|
|
};
|
||
|
|
let mailbox = Mailbox {
|
||
|
|
generation_id: generation_id(),
|
||
|
|
identity_path: HostPath::new(b"identity".to_vec()),
|
||
|
|
inbox_path: HostPath::new(b"inbox".to_vec()),
|
||
|
|
outbox_path: HostPath::new(b"outbox".to_vec()),
|
||
|
|
state: ParticipantState::Committed,
|
||
|
|
};
|
||
|
|
let metadata = StoreMetadata::domain_store_v1();
|
||
|
|
let tls_credential = TlsCredential::session_coordinator(
|
||
|
|
generation_identity(),
|
||
|
|
coordinator_id(),
|
||
|
|
vec![39, 40],
|
||
|
|
CertificateDigest::new(opaque(41)),
|
||
|
|
);
|
||
|
|
let request = MailboxRequest {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(17)),
|
||
|
|
command_argv: vec![b"printf".to_vec(), vec![0xff]],
|
||
|
|
justification: "build dependency".into(),
|
||
|
|
};
|
||
|
|
let prepared_artifact = PreparedArtifact {
|
||
|
|
prepared_artifact_id: PreparedArtifactId::new(opaque(24)),
|
||
|
|
source_identity: SourceIdentity::new(vec![25, 26]),
|
||
|
|
selected_manifest_digest: SelectedManifestDigest::new(opaque(43)),
|
||
|
|
policy_id: PolicyId::new(opaque(44)),
|
||
|
|
id_map_digest: IdMapDigest::new(opaque(45)),
|
||
|
|
path: HostPath::new(b"artifact".to_vec()),
|
||
|
|
device: DeviceId::new(46),
|
||
|
|
inode: InodeId::new(47),
|
||
|
|
state: ArtifactState::Available,
|
||
|
|
};
|
||
|
|
let mut seen = HashSet::new();
|
||
|
|
|
||
|
|
verify_shape(metadata.clone(), &mut seen);
|
||
|
|
verify_shape(domain.clone(), &mut seen);
|
||
|
|
verify_shape(generation.clone(), &mut seen);
|
||
|
|
verify_shape(participant.clone(), &mut seen);
|
||
|
|
verify_shape(ParticipantState::ReleaseIntent, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
ParticipantOwnership::Network(Box::new(network_ownership())),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(session.clone(), &mut seen);
|
||
|
|
verify_shape(terminal.clone(), &mut seen);
|
||
|
|
verify_shape(SessionTerminalState::ReleaseIntent, &mut seen);
|
||
|
|
verify_shape(mailbox.clone(), &mut seen);
|
||
|
|
verify_shape(terminal.pty_master, &mut seen);
|
||
|
|
verify_shape(process(48), &mut seen);
|
||
|
|
verify_shape(terminal.guest_process, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
SessionTerminalEvent::Input(TerminalInput {
|
||
|
|
session_id: session_id(),
|
||
|
|
bytes: vec![49],
|
||
|
|
}),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
TerminalInput {
|
||
|
|
session_id: session_id(),
|
||
|
|
bytes: vec![50],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
TerminalResize {
|
||
|
|
session_id: session_id(),
|
||
|
|
rows: NonZeroU16::new(24).unwrap(),
|
||
|
|
columns: NonZeroU16::new(80).unwrap(),
|
||
|
|
pixel_width: 640,
|
||
|
|
pixel_height: 480,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
TerminalSignal {
|
||
|
|
session_id: session_id(),
|
||
|
|
signal: TerminalSignalKind::Suspend,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(TerminalSignalKind::Interrupt, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
SessionClose {
|
||
|
|
session_id: session_id(),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
SessionTerminalMessage::Output(TerminalOutput {
|
||
|
|
session_id: session_id(),
|
||
|
|
bytes: vec![51],
|
||
|
|
}),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
TerminalOutput {
|
||
|
|
session_id: session_id(),
|
||
|
|
bytes: vec![52],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(SessionExit::exited(session_id(), 3), &mut seen);
|
||
|
|
verify_shape(SessionExitOutcome::RuntimeLost, &mut seen);
|
||
|
|
verify_shape(generation_identity(), &mut seen);
|
||
|
|
verify_shape(trust_manifest(), &mut seen);
|
||
|
|
verify_shape(tls_credential.clone(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
ProtocolPublicationIdentity {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
tls_ca_certificate_der: vec![53],
|
||
|
|
trust_manifest_sha256: TrustManifestDigest::new(opaque(54)),
|
||
|
|
coordinator_tls_certificate_sha256: CertificateDigest::new(opaque(55)),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
SandboxCredentialBundle::new(
|
||
|
|
generation_identity(),
|
||
|
|
trust_manifest(),
|
||
|
|
protocol_credential(ProtocolCredentialRole::SessionCoordinator),
|
||
|
|
protocol_credential(ProtocolCredentialRole::AgentSandbox),
|
||
|
|
vec![56; 32],
|
||
|
|
)
|
||
|
|
.unwrap(),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(TlsRole::ClientCli, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
protocol_credential(ProtocolCredentialRole::AgentSandbox),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(ProtocolCredentialRole::SessionCoordinator, &mut seen);
|
||
|
|
verify_shape(session_binding(), &mut seen);
|
||
|
|
verify_shape(session_capability(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
SignedProtocolRecord {
|
||
|
|
payload_bytes: vec![57],
|
||
|
|
signature: vec![58; 64],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(request.clone(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
MailboxCancellation {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(17)),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
MailboxResultAck {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
request_sha256: RequestDigest::new(opaque(59)),
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(17)),
|
||
|
|
highest_contiguous_sequence: 4,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
DeveloperDecision {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
request_sha256: RequestDigest::new(opaque(59)),
|
||
|
|
decision: DeveloperDecisionKind::Approve,
|
||
|
|
approved_command_argv: request.command_argv.clone(),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(DeveloperDecisionKind::Deny, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
MailboxResult {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
request_sha256: RequestDigest::new(opaque(59)),
|
||
|
|
coordinator_credential_id: ProtocolCredentialId::new(opaque(16)),
|
||
|
|
outcome: EscalationOutcome::Completed,
|
||
|
|
exit_code: Some(0),
|
||
|
|
chunk_count: 2,
|
||
|
|
stdout_sha256: StreamDigest::new(opaque(60)),
|
||
|
|
stderr_sha256: StreamDigest::new(opaque(61)),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
MailboxResultChunk {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(42)),
|
||
|
|
request_sha256: RequestDigest::new(opaque(59)),
|
||
|
|
coordinator_credential_id: ProtocolCredentialId::new(opaque(16)),
|
||
|
|
sequence: 1,
|
||
|
|
stream: ResultStream::Stderr,
|
||
|
|
bytes: vec![62],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(ResultStream::Stdout, &mut seen);
|
||
|
|
verify_shape(EscalationOutcome::TimedOut, &mut seen);
|
||
|
|
verify_shape(namespace(), &mut seen);
|
||
|
|
verify_shape(mount(), &mut seen);
|
||
|
|
verify_shape(prepared_ownership(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
StateAnchorOwnership {
|
||
|
|
lower_artifact_id: PreparedArtifactId::new(opaque(24)),
|
||
|
|
upper_path: HostPath::new(b"upper".to_vec()),
|
||
|
|
work_path: HostPath::new(b"work".to_vec()),
|
||
|
|
merged_path: HostPath::new(b"merged".to_vec()),
|
||
|
|
daemon: process(63),
|
||
|
|
mount: mount(),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
GitRoutingOwnership {
|
||
|
|
mountpoint: HostPath::new(b"workspace".to_vec()),
|
||
|
|
host_root: HostPath::new(b"host".to_vec()),
|
||
|
|
isolated_root: HostPath::new(b"isolated".to_vec()),
|
||
|
|
daemon: process(64),
|
||
|
|
mount: mount(),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(network_ownership(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
HelperOwnership {
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
connectivity_helper: process(65),
|
||
|
|
runtime_supervisor: process(21),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
SandboxOwnership {
|
||
|
|
container_id: ContainerId::new(opaque(29)),
|
||
|
|
runtime_root: HostPath::new(b"runtime".to_vec()),
|
||
|
|
bundle_digest: BundleDigest::new(opaque(66)),
|
||
|
|
host_coordinator: process(30),
|
||
|
|
runtime_supervisor: process(21),
|
||
|
|
attached_runsc: process(67),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
PublicationOwnership {
|
||
|
|
socket_path: HostPath::new(b"socket".to_vec()),
|
||
|
|
identity_path: HostPath::new(b"identity".to_vec()),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(prepared_artifact.clone(), &mut seen);
|
||
|
|
verify_shape(ArtifactState::Reclaiming, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
ArtifactLease {
|
||
|
|
prepared_artifact_id: PreparedArtifactId::new(opaque(24)),
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
PrepareOperation {
|
||
|
|
operation_id: PrepareOperationId::new(opaque(68)),
|
||
|
|
expected_artifact_id: PreparedArtifactId::new(opaque(24)),
|
||
|
|
source_identity: SourceIdentity::new(vec![25, 26]),
|
||
|
|
stage_path: HostPath::new(b"stage".to_vec()),
|
||
|
|
device: DeviceId::new(69),
|
||
|
|
inode: InodeId::new(70),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
AddressFamilyDisposition {
|
||
|
|
family: AddressFamily::Ipv6,
|
||
|
|
disposition: AddressFamilyDispositionValue::Disabled,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
DefaultAction {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
action: PolicyAction::Allow,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
PrefixRule {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
network: IpNetwork {
|
||
|
|
address: IpAddress::V4([192, 168, 0, 0]),
|
||
|
|
prefix_length: 16,
|
||
|
|
},
|
||
|
|
action: PolicyAction::Deny,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
Resolver {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
address: IpAddress::V4([9, 9, 9, 9]),
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(AddressFamily::Ipv4, &mut seen);
|
||
|
|
verify_shape(PolicyAction::Deny, &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
DerivedNetworkPolicy::v1(DerivedNetworkPolicyFields {
|
||
|
|
address_families: vec![AddressFamilyDisposition {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
disposition: AddressFamilyDispositionValue::Enabled,
|
||
|
|
}],
|
||
|
|
default_actions: vec![DefaultAction {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
action: PolicyAction::Allow,
|
||
|
|
}],
|
||
|
|
prefix_rules: vec![],
|
||
|
|
resolvers: vec![Resolver {
|
||
|
|
family: AddressFamily::Ipv4,
|
||
|
|
address: IpAddress::V4([9, 9, 9, 9]),
|
||
|
|
}],
|
||
|
|
}),
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(readiness_evidence(), &mut seen);
|
||
|
|
verify_shape(readiness_attestation(), &mut seen);
|
||
|
|
verify_shape(network_observation(), &mut seen);
|
||
|
|
verify_shape(PolicyDelivery::SealedMemfd, &mut seen);
|
||
|
|
verify_shape(readiness_identity(), &mut seen);
|
||
|
|
verify_shape(reachability_probe(), &mut seen);
|
||
|
|
verify_shape(
|
||
|
|
DeniedDestinationObservation {
|
||
|
|
destination: IpAddress::V4([10, 0, 0, 1]),
|
||
|
|
unreachable: true,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
PermittedDestinationObservation {
|
||
|
|
destination: IpAddress::V4([1, 0, 0, 1]),
|
||
|
|
reachable: true,
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
DnsObservation {
|
||
|
|
resolver: IpAddress::V4([9, 9, 9, 9]),
|
||
|
|
query_name: "example.invalid".into(),
|
||
|
|
resolved_addresses: vec![IpAddress::V4([203, 0, 113, 1])],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
DomainStore {
|
||
|
|
metadata: DomainStoreMetadata::v1(),
|
||
|
|
domain,
|
||
|
|
generations: vec![generation],
|
||
|
|
participants: vec![participant],
|
||
|
|
sessions: vec![session],
|
||
|
|
session_terminals: vec![terminal],
|
||
|
|
mailboxes: vec![mailbox],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
verify_shape(
|
||
|
|
StorageCatalog {
|
||
|
|
metadata: StorageCatalogMetadata::v1(),
|
||
|
|
prepared_artifacts: vec![prepared_artifact],
|
||
|
|
artifact_leases: vec![],
|
||
|
|
prepare_operations: vec![],
|
||
|
|
},
|
||
|
|
&mut seen,
|
||
|
|
);
|
||
|
|
|
||
|
|
assert_eq!(seen.len(), ALL_SCHEMA_TYPE_NAMES.len());
|
||
|
|
assert!(ALL_SCHEMA_TYPE_NAMES.iter().all(|name| seen.contains(name)));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn shape_registry_matches_the_normative_schema_classes() {
|
||
|
|
let documented = include_str!("../../../docs/schema.md")
|
||
|
|
.lines()
|
||
|
|
.filter_map(|line| line.strip_prefix(" class "))
|
||
|
|
.map(|name| name.split_whitespace().next().unwrap_or(name))
|
||
|
|
.collect::<HashSet<_>>();
|
||
|
|
let registered = ALL_SCHEMA_TYPE_NAMES
|
||
|
|
.iter()
|
||
|
|
.copied()
|
||
|
|
.collect::<HashSet<_>>();
|
||
|
|
|
||
|
|
assert_eq!(documented, registered);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn assert_unknown_protocol_version<T>(record: &T)
|
||
|
|
where
|
||
|
|
T: DeserializeOwned + Serialize,
|
||
|
|
{
|
||
|
|
let mut encoded = encode(record).unwrap();
|
||
|
|
encoded[0] = u8::MAX;
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<T>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn trailing_and_nonminimal_postcard_encodings_are_rejected() {
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<ParticipantState>(&[0, 0]),
|
||
|
|
Err(CodecError::TrailingPayload)
|
||
|
|
));
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<ParticipantState>(&[0x80, 0]),
|
||
|
|
Err(CodecError::NonMinimalPayload)
|
||
|
|
));
|
||
|
|
|
||
|
|
let input = TerminalInput {
|
||
|
|
session_id: session_id(),
|
||
|
|
bytes: vec![1],
|
||
|
|
};
|
||
|
|
let mut overlong_length = encode(&input).unwrap();
|
||
|
|
overlong_length[32] = 0x81;
|
||
|
|
overlong_length.insert(33, 0);
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<TerminalInput>(&overlong_length),
|
||
|
|
Err(CodecError::NonMinimalPayload)
|
||
|
|
));
|
||
|
|
|
||
|
|
let mut overlong_protocol_version = encode(&trust_manifest()).unwrap();
|
||
|
|
overlong_protocol_version[0] = 0x80;
|
||
|
|
overlong_protocol_version.insert(1, 0);
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<TrustManifest>(&overlong_protocol_version),
|
||
|
|
Err(CodecError::NonMinimalPayload)
|
||
|
|
));
|
||
|
|
|
||
|
|
let mut overlong_store_id = encode(&StoreMetadata::domain_store_v1()).unwrap();
|
||
|
|
overlong_store_id[0] |= 0x80;
|
||
|
|
overlong_store_id.insert(1, 0);
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<StoreMetadata>(&overlong_store_id),
|
||
|
|
Err(CodecError::NonMinimalPayload)
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn unknown_enums_and_protocol_versions_are_rejected() {
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<ParticipantState>(&[u8::MAX]),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
assert_unknown_protocol_version(&trust_manifest());
|
||
|
|
assert_unknown_protocol_version(&ProtocolPublicationIdentity {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
tls_ca_certificate_der: vec![1],
|
||
|
|
trust_manifest_sha256: TrustManifestDigest::new(opaque(2)),
|
||
|
|
coordinator_tls_certificate_sha256: CertificateDigest::new(opaque(3)),
|
||
|
|
});
|
||
|
|
assert_unknown_protocol_version(
|
||
|
|
&SandboxCredentialBundle::new(
|
||
|
|
generation_identity(),
|
||
|
|
trust_manifest(),
|
||
|
|
protocol_credential(ProtocolCredentialRole::SessionCoordinator),
|
||
|
|
protocol_credential(ProtocolCredentialRole::AgentSandbox),
|
||
|
|
vec![4; 32],
|
||
|
|
)
|
||
|
|
.unwrap(),
|
||
|
|
);
|
||
|
|
assert_unknown_protocol_version(&protocol_credential(
|
||
|
|
ProtocolCredentialRole::SessionCoordinator,
|
||
|
|
));
|
||
|
|
assert_unknown_protocol_version(&session_capability());
|
||
|
|
|
||
|
|
let request = MailboxRequest {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: RequestId::new(opaque(5)),
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(6)),
|
||
|
|
command_argv: vec![b"true".to_vec()],
|
||
|
|
justification: "test".into(),
|
||
|
|
};
|
||
|
|
assert_unknown_protocol_version(&request);
|
||
|
|
assert_unknown_protocol_version(&MailboxCancellation {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: request.request_id,
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(6)),
|
||
|
|
});
|
||
|
|
assert_unknown_protocol_version(&MailboxResultAck {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: request.request_id,
|
||
|
|
request_sha256: RequestDigest::new(opaque(7)),
|
||
|
|
session_capability: session_capability(),
|
||
|
|
sandbox_credential_id: ProtocolCredentialId::new(opaque(6)),
|
||
|
|
highest_contiguous_sequence: 0,
|
||
|
|
});
|
||
|
|
assert_unknown_protocol_version(&DeveloperDecision {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: request.request_id,
|
||
|
|
request_sha256: RequestDigest::new(opaque(7)),
|
||
|
|
decision: DeveloperDecisionKind::Deny,
|
||
|
|
approved_command_argv: vec![],
|
||
|
|
});
|
||
|
|
assert_unknown_protocol_version(&MailboxResult {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: request.request_id,
|
||
|
|
request_sha256: RequestDigest::new(opaque(7)),
|
||
|
|
coordinator_credential_id: ProtocolCredentialId::new(opaque(8)),
|
||
|
|
outcome: EscalationOutcome::Denied,
|
||
|
|
exit_code: None,
|
||
|
|
chunk_count: 0,
|
||
|
|
stdout_sha256: StreamDigest::new(opaque(9)),
|
||
|
|
stderr_sha256: StreamDigest::new(opaque(10)),
|
||
|
|
});
|
||
|
|
assert_unknown_protocol_version(&MailboxResultChunk {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
session_binding: session_binding(),
|
||
|
|
request_id: request.request_id,
|
||
|
|
request_sha256: RequestDigest::new(opaque(7)),
|
||
|
|
coordinator_credential_id: ProtocolCredentialId::new(opaque(8)),
|
||
|
|
sequence: 0,
|
||
|
|
stream: ResultStream::Stdout,
|
||
|
|
bytes: vec![],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn participant_and_credential_role_mismatches_are_rejected() {
|
||
|
|
let participant = Participant::new(
|
||
|
|
generation_id(),
|
||
|
|
ParticipantState::Prepared,
|
||
|
|
ParticipantOwnership::PreparedLower(prepared_ownership()),
|
||
|
|
);
|
||
|
|
assert_eq!(participant.role(), ParticipantRole::PreparedLower);
|
||
|
|
let mismatched_participant = encode(&RawParticipant {
|
||
|
|
generation_id: generation_id(),
|
||
|
|
role: ParticipantRole::Network,
|
||
|
|
state: ParticipantState::Prepared,
|
||
|
|
ownership: ParticipantOwnership::PreparedLower(prepared_ownership()),
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<Participant>(&mismatched_participant),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
let tls = TlsCredential::session_coordinator(
|
||
|
|
generation_identity(),
|
||
|
|
coordinator_id(),
|
||
|
|
vec![1],
|
||
|
|
CertificateDigest::new(opaque(2)),
|
||
|
|
);
|
||
|
|
assert_eq!(tls.role(), TlsRole::SessionCoordinator);
|
||
|
|
let mismatched_tls = encode(&RawTlsCredential {
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
role: TlsRole::SessionCoordinator,
|
||
|
|
subject_id: TlsSubjectId::ClientCli(ClientId::new(opaque(3))),
|
||
|
|
certificate_der: vec![1],
|
||
|
|
certificate_sha256: CertificateDigest::new(opaque(2)),
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<TlsCredential>(&mismatched_tls),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
let mismatched_protocol = encode(&RawProtocolCredential {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
credential_id: ProtocolCredentialId::new(opaque(4)),
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
role: ProtocolCredentialRole::AgentSandbox,
|
||
|
|
subject_id: ProtocolSubjectId::SessionCoordinator(coordinator_id()),
|
||
|
|
public_key: vec![5; 32],
|
||
|
|
issuer_signature: vec![6; 64],
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<ProtocolCredential>(&mismatched_protocol),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
let coordinator = protocol_credential(ProtocolCredentialRole::SessionCoordinator);
|
||
|
|
let sandbox = protocol_credential(ProtocolCredentialRole::AgentSandbox);
|
||
|
|
assert!(
|
||
|
|
SandboxCredentialBundle::new(
|
||
|
|
generation_identity(),
|
||
|
|
trust_manifest(),
|
||
|
|
sandbox.clone(),
|
||
|
|
coordinator.clone(),
|
||
|
|
vec![7; 32],
|
||
|
|
)
|
||
|
|
.is_err()
|
||
|
|
);
|
||
|
|
|
||
|
|
let swapped_bundle = encode(&RawSandboxCredentialBundle {
|
||
|
|
protocol_version: ProtocolVersion::V1,
|
||
|
|
generation_identity: generation_identity(),
|
||
|
|
trust_manifest: trust_manifest(),
|
||
|
|
coordinator_protocol_credential: sandbox,
|
||
|
|
sandbox_protocol_credential: coordinator,
|
||
|
|
sandbox_private_key: vec![7; 32],
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<SandboxCredentialBundle>(&swapped_bundle),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn store_identity_and_versions_dispatch_before_rows() {
|
||
|
|
for metadata in [
|
||
|
|
RawMetadata {
|
||
|
|
schema_id: String::new(),
|
||
|
|
schema_version: STORE_SCHEMA_VERSION,
|
||
|
|
},
|
||
|
|
RawMetadata {
|
||
|
|
schema_id: DOMAIN_STORE_SCHEMA_ID.into(),
|
||
|
|
schema_version: 0,
|
||
|
|
},
|
||
|
|
RawMetadata {
|
||
|
|
schema_id: DOMAIN_STORE_SCHEMA_ID.into(),
|
||
|
|
schema_version: STORE_SCHEMA_VERSION + 1,
|
||
|
|
},
|
||
|
|
RawMetadata {
|
||
|
|
schema_id: "unknown-store".into(),
|
||
|
|
schema_version: STORE_SCHEMA_VERSION,
|
||
|
|
},
|
||
|
|
] {
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<StoreMetadata>(&encode(&metadata).unwrap()),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<StoreMetadata>(
|
||
|
|
&postcard::to_stdvec(&MissingVersion {
|
||
|
|
schema_id: DOMAIN_STORE_SCHEMA_ID.into(),
|
||
|
|
})
|
||
|
|
.unwrap()
|
||
|
|
),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<DomainStoreMetadata>(&encode(&StoreMetadata::storage_catalog_v1()).unwrap()),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<StorageCatalogMetadata>(&encode(&StoreMetadata::domain_store_v1()).unwrap()),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
let wrong_domain_store = postcard::to_stdvec(&RawDomainStore {
|
||
|
|
metadata: StoreMetadata::storage_catalog_v1(),
|
||
|
|
domain: Domain {
|
||
|
|
domain_id: domain_id(),
|
||
|
|
profile_name: "trusted".into(),
|
||
|
|
canonical_workspace: HostPath::new(b"workspace".to_vec()),
|
||
|
|
},
|
||
|
|
generations: vec![],
|
||
|
|
participants: vec![],
|
||
|
|
sessions: vec![],
|
||
|
|
session_terminals: vec![],
|
||
|
|
mailboxes: vec![],
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<DomainStore>(&wrong_domain_store),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
|
||
|
|
let wrong_catalog = postcard::to_stdvec(&RawStorageCatalog {
|
||
|
|
metadata: StoreMetadata::domain_store_v1(),
|
||
|
|
prepared_artifacts: vec![],
|
||
|
|
artifact_leases: vec![],
|
||
|
|
prepare_operations: vec![],
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<StorageCatalog>(&wrong_catalog),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn network_document_schema_identities_are_fixed_to_v1() {
|
||
|
|
let invalid_ids = |schema_id: &str, wrong_document_id: &str| {
|
||
|
|
[
|
||
|
|
String::new(),
|
||
|
|
schema_id.replace("-v1", "-v0"),
|
||
|
|
schema_id.replace("-v1", "-v2"),
|
||
|
|
wrong_document_id.into(),
|
||
|
|
]
|
||
|
|
};
|
||
|
|
|
||
|
|
let policy = DerivedNetworkPolicy::v1(DerivedNetworkPolicyFields {
|
||
|
|
address_families: vec![],
|
||
|
|
default_actions: vec![],
|
||
|
|
prefix_rules: vec![],
|
||
|
|
resolvers: vec![],
|
||
|
|
});
|
||
|
|
assert_eq!(policy.schema_id(), DERIVED_NETWORK_POLICY_SCHEMA_ID);
|
||
|
|
for schema_id in invalid_ids(
|
||
|
|
DERIVED_NETWORK_POLICY_SCHEMA_ID,
|
||
|
|
NETWORK_READINESS_ATTESTATION_SCHEMA_ID,
|
||
|
|
) {
|
||
|
|
let encoded = postcard::to_stdvec(&RawDerivedNetworkPolicy {
|
||
|
|
schema_id,
|
||
|
|
address_families: vec![],
|
||
|
|
default_actions: vec![],
|
||
|
|
prefix_rules: vec![],
|
||
|
|
resolvers: vec![],
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<DerivedNetworkPolicy>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
let attestation = readiness_attestation();
|
||
|
|
assert_eq!(
|
||
|
|
attestation.schema_id(),
|
||
|
|
NETWORK_READINESS_ATTESTATION_SCHEMA_ID
|
||
|
|
);
|
||
|
|
for schema_id in invalid_ids(
|
||
|
|
NETWORK_READINESS_ATTESTATION_SCHEMA_ID,
|
||
|
|
NETWORK_READINESS_IDENTITY_SCHEMA_ID,
|
||
|
|
) {
|
||
|
|
let encoded = postcard::to_stdvec(&RawNetworkReadinessAttestation {
|
||
|
|
schema_id,
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(1)),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
derived_policy_digest: DerivedPolicyDigest::new(opaque(2)),
|
||
|
|
enforcement_namespace: namespace(),
|
||
|
|
runtime_supervisor: process(3),
|
||
|
|
connectivity_helper: process(4),
|
||
|
|
observation: network_observation(),
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<NetworkReadinessAttestation>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
let identity = readiness_identity();
|
||
|
|
assert_eq!(identity.schema_id(), NETWORK_READINESS_IDENTITY_SCHEMA_ID);
|
||
|
|
for schema_id in invalid_ids(
|
||
|
|
NETWORK_READINESS_IDENTITY_SCHEMA_ID,
|
||
|
|
NETWORK_READINESS_ATTESTATION_SCHEMA_ID,
|
||
|
|
) {
|
||
|
|
let encoded = postcard::to_stdvec(&RawNetworkReadinessIdentity {
|
||
|
|
schema_id,
|
||
|
|
domain_id: domain_id(),
|
||
|
|
generation_id: generation_id(),
|
||
|
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(1)),
|
||
|
|
network_sidecar_id: sidecar_id(),
|
||
|
|
derived_policy_digest: DerivedPolicyDigest::new(opaque(2)),
|
||
|
|
enforcement_namespace: namespace(),
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<NetworkReadinessIdentity>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn zero_terminal_dimensions_are_rejected() {
|
||
|
|
assert!(NonZeroU16::new(0).is_none());
|
||
|
|
|
||
|
|
for (rows, columns) in [(0, 80), (24, 0)] {
|
||
|
|
let encoded = postcard::to_stdvec(&RawTerminalResize {
|
||
|
|
session_id: session_id(),
|
||
|
|
rows,
|
||
|
|
columns,
|
||
|
|
pixel_width: 0,
|
||
|
|
pixel_height: 0,
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<TerminalResize>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn generic_schema_record_limit_is_enforced_before_decode() {
|
||
|
|
let oversized = vec![0; MAX_SCHEMA_RECORD_BYTES + 1];
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<Vec<u8>>(&oversized),
|
||
|
|
Err(CodecError::RecordTooLarge { .. })
|
||
|
|
));
|
||
|
|
assert!(matches!(
|
||
|
|
encode(&oversized),
|
||
|
|
Err(CodecError::RecordTooLarge { .. })
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn session_exit_code_is_accepted_only_for_exited() {
|
||
|
|
let encoded = encode(&InvalidSessionExit {
|
||
|
|
session_id: session_id(),
|
||
|
|
outcome: SessionExitOutcome::Terminated,
|
||
|
|
exit_code: Some(1),
|
||
|
|
})
|
||
|
|
.unwrap();
|
||
|
|
assert!(matches!(
|
||
|
|
decode::<SessionExit>(&encoded),
|
||
|
|
Err(CodecError::MalformedPayload(_))
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn record_encoding_has_stable_byte_order() {
|
||
|
|
assert_eq!(encode(&ParticipantState::Intent).unwrap(), [0]);
|
||
|
|
}
|