Implement trusted-host configuration resolution

This commit is contained in:
ms2k 2026-07-15 00:43:42 +09:00
commit f9eb966341

View file

@ -1,8 +1,9 @@
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Debug;
use std::num::NonZeroU16;
use lazaret_core::codec::{CodecError, MAX_SCHEMA_RECORD_BYTES, decode, encode};
use lazaret_core::configuration::parse_trusted_configuration;
use lazaret_core::identity::{
BootId, BundleDigest, CapabilityId, CertificateDigest, ClientId, ConfigurationFingerprint,
ContainerId, CoordinatorId, DerivedPolicyDigest, DeviceId, DomainId, FileDescriptor,
@ -148,6 +149,25 @@ struct RawNetworkReadinessIdentity {
enforcement_namespace: NamespaceIdentity,
}
#[derive(Serialize)]
struct RawMailboxRequest {
protocol_version: ProtocolVersion,
session_binding: SessionBinding,
request_id: RequestId,
session_capability: SessionRequestCapability,
sandbox_credential_id: ProtocolCredentialId,
command_argv: Vec<Vec<u8>>,
justification: String,
}
#[derive(Serialize)]
struct RawTrustedHostConfigurationV1 {
schema_id: String,
storage: StorageConfigurationV1,
network: NetworkConfigurationV1,
profiles: BTreeMap<String, ProfileConfigurationV1>,
}
fn opaque(byte: u8) -> [u8; 32] {
[byte; 32]
}
@ -657,15 +677,15 @@ fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
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 request = MailboxRequest::new(
session_binding(),
RequestId::new(opaque(42)),
session_capability(),
ProtocolCredentialId::new(opaque(17)),
vec![b"/usr/bin/printf".to_vec(), b"value".to_vec()],
"build dependency".into(),
)
.unwrap();
let prepared_artifact = PreparedArtifact {
prepared_artifact_id: PreparedArtifactId::new(opaque(24)),
source_identity: SourceIdentity::new(vec![25, 26]),
@ -846,7 +866,7 @@ fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
request_id: RequestId::new(opaque(42)),
request_sha256: RequestDigest::new(opaque(59)),
decision: DeveloperDecisionKind::Approve,
approved_command_argv: request.command_argv.clone(),
approved_command_argv: request.command_argv().to_vec(),
},
&mut seen,
);
@ -1054,6 +1074,71 @@ fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
},
&mut seen,
);
verify_shape(
parse_trusted_configuration(
br#"schema = "lazaret-trusted-host-config-v1"
[profiles.trusted]
image = "example.invalid/agent:v1"
"#,
)
.unwrap(),
&mut seen,
);
verify_shape(
ConfigurationFingerprintRecordV1 {
selected_profile: "trusted".into(),
storage_root: HostPath::new(b"/data/lazaret".to_vec()),
runtime_root: HostPath::new(b"/run/user/1000/lazaret".to_vec()),
image: "example.invalid/agent:v1".into(),
isolate_gitignored: true,
persistent_dirs: vec!["/root".into()],
environment_references: vec![EnvironmentReference {
sandbox_name: "TOKEN".into(),
host_name: "HOST_TOKEN".into(),
}],
volumes: vec![ResolvedVolume {
canonical_host_source: HostPath::new(b"/data/source".to_vec()),
container_path: "/root/source".into(),
kind: VolumeKind::Directory,
access: VolumeAccess::ReadOnly,
create: false,
}],
effective_allow_prefixes: vec![([1, 1, 1, 1], 32)],
effective_deny_prefixes: vec![([10, 0, 0, 0], 8)],
escalation_enabled: true,
auto_approval_rules: vec![ResolvedAutoApprovalRule {
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
args: vec!["--version".into()],
allow_extra_args: false,
}],
},
&mut seen,
);
verify_shape(
EnvironmentReference {
sandbox_name: "TOKEN".into(),
host_name: "HOST_TOKEN".into(),
},
&mut seen,
);
verify_shape(
ResolvedVolume {
canonical_host_source: HostPath::new(b"/data/source".to_vec()),
container_path: "/root/source".into(),
kind: VolumeKind::Directory,
access: VolumeAccess::ReadWrite,
create: false,
},
&mut seen,
);
verify_shape(
ResolvedAutoApprovalRule {
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
args: vec![],
allow_extra_args: false,
},
&mut seen,
);
assert_eq!(seen.len(), IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES.len());
assert!(
@ -1296,27 +1381,27 @@ fn unknown_enums_and_protocol_versions_are_rejected() {
));
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(),
};
let request = MailboxRequest::new(
session_binding(),
RequestId::new(opaque(5)),
session_capability(),
ProtocolCredentialId::new(opaque(6)),
vec![b"/usr/bin/true".to_vec()],
"test".into(),
)
.unwrap();
assert_unknown_protocol_version(&request);
assert_unknown_protocol_version(&MailboxCancellation {
protocol_version: ProtocolVersion::V1,
session_binding: session_binding(),
request_id: request.request_id,
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_id: request.request_id(),
request_sha256: RequestDigest::new(opaque(7)),
session_capability: session_capability(),
sandbox_credential_id: ProtocolCredentialId::new(opaque(6)),
@ -1325,7 +1410,7 @@ fn unknown_enums_and_protocol_versions_are_rejected() {
assert_unknown_protocol_version(&DeveloperDecision {
protocol_version: ProtocolVersion::V1,
session_binding: session_binding(),
request_id: request.request_id,
request_id: request.request_id(),
request_sha256: RequestDigest::new(opaque(7)),
decision: DeveloperDecisionKind::Deny,
approved_command_argv: vec![],
@ -1333,7 +1418,7 @@ fn unknown_enums_and_protocol_versions_are_rejected() {
assert_unknown_protocol_version(&MailboxResult {
protocol_version: ProtocolVersion::V1,
session_binding: session_binding(),
request_id: request.request_id,
request_id: request.request_id(),
request_sha256: RequestDigest::new(opaque(7)),
coordinator_credential_id: ProtocolCredentialId::new(opaque(8)),
outcome: EscalationOutcome::Denied,
@ -1345,7 +1430,7 @@ fn unknown_enums_and_protocol_versions_are_rejected() {
assert_unknown_protocol_version(&MailboxResultChunk {
protocol_version: ProtocolVersion::V1,
session_binding: session_binding(),
request_id: request.request_id,
request_id: request.request_id(),
request_sha256: RequestDigest::new(opaque(7)),
coordinator_credential_id: ProtocolCredentialId::new(opaque(8)),
sequence: 0,
@ -2982,6 +3067,40 @@ fn network_document_schema_identities_are_fixed_to_v1() {
}
}
#[test]
fn trusted_host_configuration_postcard_dispatch_rejects_unknown_schema() {
let mut profiles = BTreeMap::new();
profiles.insert(
"default".into(),
ProfileConfigurationV1 {
image: "example.invalid/agent:v1".into(),
isolate_gitignored: true,
persistent_dirs: vec![],
allow_egress: vec![],
deny_egress: vec![],
env: BTreeMap::new(),
volumes: vec![],
escalation: EscalationConfigurationV1::default(),
},
);
for schema_id in [
"",
"lazaret-trusted-host-config-v0",
"lazaret-trusted-host-config-v2",
] {
let raw = RawTrustedHostConfigurationV1 {
schema_id: schema_id.into(),
storage: StorageConfigurationV1::default(),
network: NetworkConfigurationV1::default(),
profiles: profiles.clone(),
};
assert!(matches!(
decode::<TrustedHostConfigurationV1>(&encode(&raw).unwrap()),
Err(CodecError::MalformedPayload(_))
));
}
}
#[test]
fn zero_terminal_dimensions_are_rejected() {
assert!(NonZeroU16::new(0).is_none());
@ -3015,6 +3134,41 @@ fn generic_schema_record_limit_is_enforced_before_decode() {
));
}
#[test]
fn mailbox_request_command_vector_is_closed_and_direct() {
for command_argv in [
vec![],
vec![b"true".to_vec()],
vec![b"/usr/bin/true".to_vec(), vec![0xff]],
vec![b"/usr/bin/true".to_vec(), b"bad\0argument".to_vec()],
] {
let raw = RawMailboxRequest {
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,
justification: "test".into(),
};
assert!(matches!(
decode::<MailboxRequest>(&encode(&raw).unwrap()),
Err(CodecError::MalformedPayload(_))
));
}
assert!(
MailboxRequest::new(
session_binding(),
RequestId::new(opaque(42)),
session_capability(),
ProtocolCredentialId::new(opaque(17)),
vec![b"/bin/sh".to_vec(), b"-c".to_vec(), b"fixed".to_vec()],
"exact direct vector".into(),
)
.is_ok()
);
}
#[test]
fn session_exit_code_is_accepted_only_for_exited() {
let encoded = encode(&InvalidSessionExit {