Replace lexical Rust fragments with real modules

This commit is contained in:
ms2k 2026-07-19 05:12:57 +09:00
commit 247819250b
4 changed files with 3302 additions and 2671 deletions

View file

@ -1,15 +1,26 @@
struct ControlPaths {
root: PathBuf,
root_file: File,
root_identity: FileIdentity,
socket: PathBuf,
identity: PathBuf,
staged_socket: PathBuf,
staged_identity: PathBuf,
use super::{
Arc, AsFd, CONTROL_SOCKET_NAME, ContainerId, ControlError, File, FileIdentity,
InteractiveSessionBatch, InteractiveSessionEvent, InteractiveSessionRequest, MetadataExt, Mode,
NonInteractiveSessionMessage, NonInteractiveSessionRequest, OFlags, OpenOptions, OsStr,
OsString, OsStringExt, PUBLICATION_IDENTITY_NAME, Path, PathBuf, ProcessExit,
ProtocolPublicationIdentity, Read, RunscCommand, STAGED_CONTROL_SOCKET_NAME,
STAGED_PUBLICATION_IDENTITY_NAME, ServerConfig, SessionExit, SessionId, SessionRegistry,
TargetCommand, TrustAnchor, Write, child_broker, fchmod, fmt, io, openat,
remove_if_identity_matches,
};
pub(super) struct ControlPaths {
pub(super) root: PathBuf,
pub(super) root_file: File,
pub(super) root_identity: FileIdentity,
pub(super) socket: PathBuf,
pub(super) identity: PathBuf,
pub(super) staged_socket: PathBuf,
pub(super) staged_identity: PathBuf,
}
impl ControlPaths {
fn open(root: &Path, uid: u32) -> Result<Self, ControlError> {
pub(super) fn open(root: &Path, uid: u32) -> Result<Self, ControlError> {
if !root.is_absolute() {
return Err(ControlError::InvalidPath(
"runtime Domain root is not absolute",
@ -43,7 +54,7 @@ impl ControlPaths {
})
}
fn validate_root(&self, uid: u32) -> Result<(), ControlError> {
pub(super) fn validate_root(&self, uid: u32) -> Result<(), ControlError> {
let descriptor = self.root_file.metadata()?;
let current = std::fs::symlink_metadata(&self.root)?;
if FileIdentity::from_metadata(&descriptor) != self.root_identity
@ -57,7 +68,7 @@ impl ControlPaths {
Ok(())
}
fn create_named_identity(
pub(super) fn create_named_identity(
&self,
name: &str,
bytes: &[u8],
@ -90,7 +101,7 @@ impl ControlPaths {
Ok((file, identity))
}
fn validate_identity(
pub(super) fn validate_identity(
&self,
file: &File,
expected: FileIdentity,
@ -100,7 +111,7 @@ impl ControlPaths {
self.validate_identity_at(&self.identity, file, expected, uid, expected_length)
}
fn validate_identity_at(
pub(super) fn validate_identity_at(
&self,
path: &Path,
file: &File,
@ -131,23 +142,23 @@ impl ControlPaths {
}
pub struct PublishedControlFace {
listener: std::os::unix::net::UnixListener,
paths: ControlPaths,
socket_identity: FileIdentity,
identity_file: File,
identity_file_identity: FileIdentity,
publication_identity: ProtocolPublicationIdentity,
publication_bytes: Vec<u8>,
uid: u32,
anchor: TrustAnchor,
server_config: Arc<ServerConfig>,
registry: Arc<SessionRegistry>,
executor: Arc<dyn NonInteractiveSessionExecutor>,
interactive_executor: Arc<dyn InteractiveSessionExecutor>,
pub(super) listener: std::os::unix::net::UnixListener,
pub(super) paths: ControlPaths,
pub(super) socket_identity: FileIdentity,
pub(super) identity_file: File,
pub(super) identity_file_identity: FileIdentity,
pub(super) publication_identity: ProtocolPublicationIdentity,
pub(super) publication_bytes: Vec<u8>,
pub(super) uid: u32,
pub(super) anchor: TrustAnchor,
pub(super) server_config: Arc<ServerConfig>,
pub(super) registry: Arc<SessionRegistry>,
pub(super) executor: Arc<dyn NonInteractiveSessionExecutor>,
pub(super) interactive_executor: Arc<dyn InteractiveSessionExecutor>,
}
pub(crate) struct AcceptedControlConnection {
socket: std::os::unix::net::UnixStream,
pub(super) socket: std::os::unix::net::UnixStream,
}
impl AcceptedControlConnection {
@ -166,7 +177,7 @@ pub trait NonInteractiveSessionExecutor: Send + Sync {
) -> Result<SessionExit, ControlError>;
}
struct ExecutionUnavailable;
pub(super) struct ExecutionUnavailable;
impl NonInteractiveSessionExecutor for ExecutionUnavailable {
fn execute(
@ -196,7 +207,7 @@ pub trait InteractiveSessionExecutor: Send + Sync {
) -> Result<Box<dyn InteractiveControlSession>, ControlError>;
}
struct InteractiveExecutionUnavailable;
pub(super) struct InteractiveExecutionUnavailable;
impl InteractiveSessionExecutor for InteractiveExecutionUnavailable {
fn start(
@ -208,13 +219,13 @@ impl InteractiveSessionExecutor for InteractiveExecutionUnavailable {
}
pub struct RunscNonInteractiveExecutor {
program: OsString,
global_arguments: Vec<OsString>,
container: OsString,
runner: Arc<dyn NonInteractiveCommandRunner>,
pub(super) program: OsString,
pub(super) global_arguments: Vec<OsString>,
pub(super) container: OsString,
pub(super) runner: Arc<dyn NonInteractiveCommandRunner>,
}
trait NonInteractiveCommandRunner: Send + Sync {
pub(super) trait NonInteractiveCommandRunner: Send + Sync {
fn run(
&self,
target: &TargetCommand,
@ -343,7 +354,7 @@ fn emit_available(
}
}
fn hex_lower(bytes: &[u8]) -> String {
pub(super) fn hex_lower(bytes: &[u8]) -> String {
use fmt::Write as _;
bytes.iter().fold(String::new(), |mut output, byte| {
@ -351,4 +362,3 @@ fn hex_lower(bytes: &[u8]) -> String {
output
})
}

View file

@ -1,3 +1,20 @@
use super::{
AcceptedControlConnection, Arc, AsFd, AsRawFd, CONTROL_ALPN, CONTROL_SOCKET_NAME,
CertificateDigest, CertificateParams, ClientConfig, ClientConnection, ClientEnrollmentResponse,
ClientId, ConfinedPathPlan, ControlClient, ControlError, ControlPaths, ControlRequest,
ControlResponse, ControlSessionAdmission, Digest, DistinguishedName, DnType, Duration,
ExecutionUnavailable, ExtendedKeyUsagePurpose, File, FileExt, FileIdentity, FileTypeExt,
GenerationIdentity, HostCoordinatorDomainStore, InteractiveSessionBatch,
InteractiveSessionEvent, InteractiveSessionExecutor, InteractiveSessionMessage, IsCa,
KeyUsagePurpose, MAX_CONTROL_BYTES, MAX_ENROLLMENT_BYTES, MetadataExt, Mode,
NonInteractiveSessionExecutor, NonInteractiveSessionMessage, OFlags, OffsetDateTime,
PUBLICATION_IDENTITY_NAME, Path, PathBuf, PrivateKeyDer, ProtocolPublicationIdentity,
ProtocolVersion, PublicationOwnership, PublishedControlFace, Read, ServerConnection,
SessionBinding, SessionId, SessionPhase, SessionRegistrationResponse, SessionRegistry, Sha256,
SigningKey, StagedControlFace, TlsCredential, TrustAnchor, Write, decode, encode, fmt, io,
issue_session_capability, openat, publication_recovery_ownership, socket_peercred,
};
impl PublishedControlFace {
pub(crate) fn recovery_ownership(&self) -> Result<PublicationOwnership, ControlError> {
publication_recovery_ownership(&self.paths)
@ -511,9 +528,7 @@ impl PublishedControlFace {
/// # Errors
/// Returns framing, size, decoding, or TLS I/O failures.
pub fn receive<
Response: lazaret_core::records::SchemaType
+ serde::de::DeserializeOwned
+ serde::Serialize,
Response: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
>(
stream: &mut impl Read,
) -> Result<Response, ControlError> {
@ -552,7 +567,7 @@ fn remove_exact_if_identity_matches(
}
}
fn remove_if_identity_matches(path: &Path, expected: FileIdentity) {
pub(super) fn remove_if_identity_matches(path: &Path, expected: FileIdentity) {
if std::fs::symlink_metadata(path)
.ok()
.is_some_and(|metadata| FileIdentity::from_metadata(&metadata) == expected)
@ -632,7 +647,7 @@ fn require_interactive_batch(
Ok(())
}
fn leaf_parameters(
pub(super) fn leaf_parameters(
common_name: &str,
usage: ExtendedKeyUsagePurpose,
now: OffsetDateTime,
@ -650,13 +665,13 @@ fn leaf_parameters(
Ok(params)
}
fn random_signing_key() -> Result<SigningKey, ControlError> {
pub(super) fn random_signing_key() -> Result<SigningKey, ControlError> {
let mut bytes = [0_u8; 32];
getrandom::fill(&mut bytes).map_err(|_| ControlError::InvalidCertificateProfile)?;
Ok(SigningKey::from_bytes(&bytes))
}
fn enrollment_binding(
pub(super) fn enrollment_binding(
generation_identity: &GenerationIdentity,
cli_id: ClientId,
) -> Result<String, ControlError> {
@ -664,15 +679,15 @@ fn enrollment_binding(
.map_err(ControlError::from)
}
fn certificate_digest(certificate: impl AsRef<[u8]>) -> CertificateDigest {
pub(super) fn certificate_digest(certificate: impl AsRef<[u8]>) -> CertificateDigest {
CertificateDigest::new(sha256(certificate.as_ref()))
}
fn sha256(bytes: &[u8]) -> [u8; 32] {
pub(super) fn sha256(bytes: &[u8]) -> [u8; 32] {
Sha256::digest(bytes).into()
}
fn short_identity(bytes: &[u8; 32]) -> String {
pub(super) fn short_identity(bytes: &[u8; 32]) -> String {
hex(&bytes[..8])
}
@ -685,7 +700,7 @@ fn hex(bytes: &[u8]) -> String {
encoded
}
fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
pub(super) fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
if actual == expected {
Ok(())
} else {
@ -693,7 +708,7 @@ fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
}
}
fn is_live_session(phase: SessionPhase) -> bool {
pub(super) fn is_live_session(phase: SessionPhase) -> bool {
matches!(
phase,
SessionPhase::Registering
@ -704,9 +719,7 @@ fn is_live_session(phase: SessionPhase) -> bool {
}
fn read_frame<
Record: lazaret_core::records::SchemaType
+ serde::de::DeserializeOwned
+ serde::Serialize,
Record: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
>(
reader: &mut impl Read,
maximum: usize,

View file

@ -1,3 +1,13 @@
use super::{
Arc, CONTROL_SOCKET_NAME, ControlError, ControlPaths, DeviceId, ExecutionUnavailable, File,
FileIdentity, FileTypeExt, HostPath, InodeId, InteractiveExecutionUnavailable,
InteractiveSessionExecutor, MetadataExt, MountIdentity, NonInteractiveSessionExecutor,
PUBLICATION_IDENTITY_NAME, Path, PermissionsExt, ProtocolPublicationIdentity,
PublicationOwnership, PublishedControlFace, STAGED_CONTROL_SOCKET_NAME,
STAGED_PUBLICATION_IDENTITY_NAME, ServerConfig, SessionRegistry, TrustAnchor, encode,
mount_identity, remove_if_identity_matches,
};
pub struct StagedControlFace {
listener: Option<std::os::unix::net::UnixListener>,
paths: ControlPaths,
@ -194,7 +204,7 @@ impl Drop for StagedControlFace {
}
}
fn publication_recovery_ownership(
pub(super) fn publication_recovery_ownership(
paths: &ControlPaths,
) -> Result<PublicationOwnership, ControlError> {
let socket_path = if paths.staged_socket.exists() {
@ -233,4 +243,3 @@ fn publication_path_identity(
inode: InodeId::new(metadata.ino()),
})
}

View file

@ -1,193 +1,188 @@
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::sync::Arc;
use std::thread;
use std::ffi::OsString;
use std::sync::Arc;
use std::thread;
use lazaret_core::identity::{
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, SandboxId,
};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName};
use lazaret_core::identity::{
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, SandboxId,
};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName};
use super::*;
use super::*;
fn generation() -> GenerationIdentity {
GenerationIdentity {
agent_profile: "trusted".into(),
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
configuration_fingerprint: ConfigurationFingerprint::new([2; 32]),
coordinator_id: CoordinatorId::new([3; 32]),
sandbox_id: SandboxId::new([4; 32]),
}
}
fn client_config_with_certificate(
anchor: &TrustAnchor,
certificate: CertificateDer<'static>,
private_key: PrivatePkcs8KeyDer<'static>,
) -> ClientConfig {
let mut roots = RootCertStore::empty();
roots.add(anchor.tls_ca_der.clone()).unwrap();
let mut config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
.with_root_certificates(roots)
.with_client_auth_cert(vec![certificate], PrivateKeyDer::Pkcs8(private_key))
.unwrap();
config.alpn_protocols = vec![CONTROL_ALPN.to_vec()];
config.resumption = rustls::client::Resumption::disabled();
config
}
fn handshake_pair(
server_config: ServerConfig,
client_config: ClientConfig,
) -> (Result<(), rustls::Error>, Result<(), rustls::Error>) {
let (mut server_socket, mut client_socket) =
std::os::unix::net::UnixStream::pair().unwrap();
let server = thread::spawn(move || {
let mut connection = ServerConnection::new(Arc::new(server_config)).unwrap();
while connection.is_handshaking() {
connection
.complete_io(&mut server_socket)
.map_err(|error| rustls::Error::General(error.to_string()))?;
}
Ok(())
});
let mut connection = ClientConnection::new(
Arc::new(client_config),
ServerName::try_from(CONTROL_SERVER_NAME).unwrap(),
)
.unwrap();
let client = (|| {
while connection.is_handshaking() {
connection
.complete_io(&mut client_socket)
.map_err(|error| rustls::Error::General(error.to_string()))?;
}
Ok(())
})();
(server.join().unwrap(), client)
}
#[test]
fn exact_peer_uid_is_required() {
assert!(require_peer_uid(1_000, 1_000).is_ok());
assert!(matches!(
require_peer_uid(1_001, 1_000),
Err(ControlError::UnauthorizedPeer)
));
}
struct RecordingRunner {
observed: Mutex<Option<(OsString, Vec<OsString>)>>,
exit: ProcessExit,
}
impl NonInteractiveCommandRunner for RecordingRunner {
fn run(
&self,
target: &TargetCommand,
_session_id: SessionId,
_emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>,
) -> Result<ProcessExit, ControlError> {
*self.observed.lock().unwrap() =
Some((target.program().to_owned(), target.arguments().to_vec()));
Ok(self.exit)
}
}
#[test]
fn runsc_exec_uses_the_command_as_the_first_post_container_argument() {
let runner = Arc::new(RecordingRunner {
observed: Mutex::new(None),
exit: ProcessExit {
exit_code: Some(23),
terminating_signal: None,
},
});
let executor = RunscNonInteractiveExecutor {
program: "runsc".into(),
global_arguments: vec!["--root=/runtime".into()],
container: "container-id".into(),
runner: runner.clone(),
};
let session_id = SessionId::new([9; 32]);
let exit = executor
.execute(
&NonInteractiveSessionRequest {
protocol_version: ProtocolVersion::V1,
session_id,
argv: vec![b"printf".to_vec(), b"hello".to_vec()],
},
&mut |_| Ok(()),
)
.unwrap();
assert_eq!(exit.exit_code(), Some(23));
assert_eq!(
runner.observed.lock().unwrap().as_ref().unwrap(),
&(
OsString::from("runsc"),
vec![
OsString::from("--root=/runtime"),
OsString::from("exec"),
OsString::from("-env"),
OsString::from(format!(
"LAZARET_SESSION_ID={}",
hex_lower(session_id.as_bytes())
)),
OsString::from("container-id"),
OsString::from("printf"),
OsString::from("hello"),
]
)
);
}
#[test]
fn rustls_refuses_client_leaf_with_server_role() {
let anchor = TrustAnchor::create(generation()).unwrap();
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
let certificate = leaf_parameters(
"wrong-client-role.invalid",
ExtendedKeyUsagePurpose::ServerAuth,
OffsetDateTime::now_utc(),
)
.unwrap()
.signed_by(&key, &anchor.tls_ca)
.unwrap();
let client_config = client_config_with_certificate(
&anchor,
certificate.der().clone(),
PrivatePkcs8KeyDer::from(key.serialize_der()),
);
let (server, client) = handshake_pair(anchor.server_config().unwrap(), client_config);
assert!(server.is_err() || client.is_err());
}
#[test]
fn rustls_refuses_server_leaf_with_client_role() {
let anchor = TrustAnchor::create(generation()).unwrap();
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
let certificate = leaf_parameters(
CONTROL_SERVER_NAME,
ExtendedKeyUsagePurpose::ClientAuth,
OffsetDateTime::now_utc(),
)
.unwrap()
.signed_by(&key, &anchor.tls_ca)
.unwrap();
let server_config =
ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
.with_no_client_auth()
.with_single_cert(
vec![certificate.der().clone()],
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key.serialize_der())),
)
.unwrap();
let client_config =
PublishedControlFace::client_config(&anchor.publication_identity(), None).unwrap();
let (server, client) = handshake_pair(server_config, client_config);
assert!(server.is_err() || client.is_err());
fn generation() -> GenerationIdentity {
GenerationIdentity {
agent_profile: "trusted".into(),
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(7),
configuration_fingerprint: ConfigurationFingerprint::new([2; 32]),
coordinator_id: CoordinatorId::new([3; 32]),
sandbox_id: SandboxId::new([4; 32]),
}
}
fn client_config_with_certificate(
anchor: &TrustAnchor,
certificate: CertificateDer<'static>,
private_key: PrivatePkcs8KeyDer<'static>,
) -> ClientConfig {
let mut roots = RootCertStore::empty();
roots.add(anchor.tls_ca_der.clone()).unwrap();
let mut config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
.with_root_certificates(roots)
.with_client_auth_cert(vec![certificate], PrivateKeyDer::Pkcs8(private_key))
.unwrap();
config.alpn_protocols = vec![CONTROL_ALPN.to_vec()];
config.resumption = rustls::client::Resumption::disabled();
config
}
fn handshake_pair(
server_config: ServerConfig,
client_config: ClientConfig,
) -> (Result<(), rustls::Error>, Result<(), rustls::Error>) {
let (mut server_socket, mut client_socket) = std::os::unix::net::UnixStream::pair().unwrap();
let server = thread::spawn(move || {
let mut connection = ServerConnection::new(Arc::new(server_config)).unwrap();
while connection.is_handshaking() {
connection
.complete_io(&mut server_socket)
.map_err(|error| rustls::Error::General(error.to_string()))?;
}
Ok(())
});
let mut connection = ClientConnection::new(
Arc::new(client_config),
ServerName::try_from(CONTROL_SERVER_NAME).unwrap(),
)
.unwrap();
let client = (|| {
while connection.is_handshaking() {
connection
.complete_io(&mut client_socket)
.map_err(|error| rustls::Error::General(error.to_string()))?;
}
Ok(())
})();
(server.join().unwrap(), client)
}
#[test]
fn exact_peer_uid_is_required() {
assert!(require_peer_uid(1_000, 1_000).is_ok());
assert!(matches!(
require_peer_uid(1_001, 1_000),
Err(ControlError::UnauthorizedPeer)
));
}
struct RecordingRunner {
observed: Mutex<Option<(OsString, Vec<OsString>)>>,
exit: ProcessExit,
}
impl NonInteractiveCommandRunner for RecordingRunner {
fn run(
&self,
target: &TargetCommand,
_session_id: SessionId,
_emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>,
) -> Result<ProcessExit, ControlError> {
*self.observed.lock().unwrap() =
Some((target.program().to_owned(), target.arguments().to_vec()));
Ok(self.exit)
}
}
#[test]
fn runsc_exec_uses_the_command_as_the_first_post_container_argument() {
let runner = Arc::new(RecordingRunner {
observed: Mutex::new(None),
exit: ProcessExit {
exit_code: Some(23),
terminating_signal: None,
},
});
let executor = RunscNonInteractiveExecutor {
program: "runsc".into(),
global_arguments: vec!["--root=/runtime".into()],
container: "container-id".into(),
runner: runner.clone(),
};
let session_id = SessionId::new([9; 32]);
let exit = executor
.execute(
&NonInteractiveSessionRequest {
protocol_version: ProtocolVersion::V1,
session_id,
argv: vec![b"printf".to_vec(), b"hello".to_vec()],
},
&mut |_| Ok(()),
)
.unwrap();
assert_eq!(exit.exit_code(), Some(23));
assert_eq!(
runner.observed.lock().unwrap().as_ref().unwrap(),
&(
OsString::from("runsc"),
vec![
OsString::from("--root=/runtime"),
OsString::from("exec"),
OsString::from("-env"),
OsString::from(format!(
"LAZARET_SESSION_ID={}",
hex_lower(session_id.as_bytes())
)),
OsString::from("container-id"),
OsString::from("printf"),
OsString::from("hello"),
]
)
);
}
#[test]
fn rustls_refuses_client_leaf_with_server_role() {
let anchor = TrustAnchor::create(generation()).unwrap();
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
let certificate = leaf_parameters(
"wrong-client-role.invalid",
ExtendedKeyUsagePurpose::ServerAuth,
OffsetDateTime::now_utc(),
)
.unwrap()
.signed_by(&key, &anchor.tls_ca)
.unwrap();
let client_config = client_config_with_certificate(
&anchor,
certificate.der().clone(),
PrivatePkcs8KeyDer::from(key.serialize_der()),
);
let (server, client) = handshake_pair(anchor.server_config().unwrap(), client_config);
assert!(server.is_err() || client.is_err());
}
#[test]
fn rustls_refuses_server_leaf_with_client_role() {
let anchor = TrustAnchor::create(generation()).unwrap();
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
let certificate = leaf_parameters(
CONTROL_SERVER_NAME,
ExtendedKeyUsagePurpose::ClientAuth,
OffsetDateTime::now_utc(),
)
.unwrap()
.signed_by(&key, &anchor.tls_ca)
.unwrap();
let server_config = ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
.with_no_client_auth()
.with_single_cert(
vec![certificate.der().clone()],
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key.serialize_der())),
)
.unwrap();
let client_config =
PublishedControlFace::client_config(&anchor.publication_identity(), None).unwrap();
let (server, client) = handshake_pair(server_config, client_config);
assert!(server.is_err() || client.is_err());
}