Compose cold-start resources under Runtime Supervisor ownership

This commit is contained in:
ms2k 2026-07-16 17:29:23 +09:00
commit 33ac0ba4ee

View file

@ -49,6 +49,8 @@ pub const MAX_ENROLLMENT_BYTES: usize = 8_192;
const MAX_CONTROL_BYTES: usize = 65_536;
const CONTROL_SOCKET_NAME: &str = "control.sock";
const PUBLICATION_IDENTITY_NAME: &str = "protocol-identity.postcard";
const STAGED_CONTROL_SOCKET_NAME: &str = ".control.sock.staged";
const STAGED_PUBLICATION_IDENTITY_NAME: &str = ".protocol-identity.staged";
#[derive(Debug)]
pub enum ControlError {
@ -207,6 +209,11 @@ pub struct TrustAnchor {
}
impl TrustAnchor {
#[must_use]
pub const fn generation_identity(&self) -> &GenerationIdentity {
&self.generation_identity
}
/// Creates one ephemeral generation Trust Anchor with role-separated Ed25519 keys.
///
/// # Errors
@ -412,7 +419,8 @@ pub struct SessionRegistry {
}
impl SessionRegistry {
fn new(
#[must_use]
pub fn new(
store: Arc<HostCoordinatorDomainStore>,
generation_identity: GenerationIdentity,
) -> Self {
@ -676,6 +684,8 @@ struct ControlPaths {
root_identity: FileIdentity,
socket: PathBuf,
identity: PathBuf,
staged_socket: PathBuf,
staged_identity: PathBuf,
}
impl ControlPaths {
@ -708,6 +718,8 @@ impl ControlPaths {
root_identity,
socket: root.join(CONTROL_SOCKET_NAME),
identity: root.join(PUBLICATION_IDENTITY_NAME),
staged_socket: root.join(STAGED_CONTROL_SOCKET_NAME),
staged_identity: root.join(STAGED_PUBLICATION_IDENTITY_NAME),
})
}
@ -725,15 +737,15 @@ impl ControlPaths {
Ok(())
}
fn create_identity(
fn create_named_identity(
&self,
name: &str,
bytes: &[u8],
uid: u32,
) -> Result<(File, FileIdentity), ControlError> {
self.validate_root(uid)?;
let fd = openat(
self.root_file.as_fd(),
PUBLICATION_IDENTITY_NAME,
name,
OFlags::CREATE | OFlags::EXCL | OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::RUSR | Mode::WUSR,
)
@ -749,10 +761,10 @@ impl ControlPaths {
self.root_file
.sync_all()
.map_err(|_| ControlError::InvalidPublication("directory sync failed"))?;
self.validate_identity(&file, identity, uid, bytes.len())
self.validate_identity_at(&self.root.join(name), &file, identity, uid, bytes.len())
})();
if let Err(error) = result {
remove_if_identity_matches(&self.identity, identity);
remove_if_identity_matches(&self.root.join(name), identity);
return Err(error);
}
Ok((file, identity))
@ -764,10 +776,21 @@ impl ControlPaths {
expected: FileIdentity,
uid: u32,
expected_length: usize,
) -> Result<(), ControlError> {
self.validate_identity_at(&self.identity, file, expected, uid, expected_length)
}
fn validate_identity_at(
&self,
path: &Path,
file: &File,
expected: FileIdentity,
uid: u32,
expected_length: usize,
) -> Result<(), ControlError> {
self.validate_root(uid)?;
let descriptor = file.metadata()?;
let current = std::fs::symlink_metadata(&self.identity)?;
let current = std::fs::symlink_metadata(path)?;
if FileIdentity::from_metadata(&descriptor) != expected
|| FileIdentity::from_metadata(&current) != expected
|| !descriptor.file_type().is_file()
@ -801,6 +824,150 @@ pub struct PublishedControlFace {
registry: Arc<SessionRegistry>,
}
pub struct StagedControlFace {
listener: Option<std::os::unix::net::UnixListener>,
paths: ControlPaths,
staged_socket_identity: FileIdentity,
identity_file: File,
identity_file_identity: FileIdentity,
publication_identity: ProtocolPublicationIdentity,
publication_bytes: Vec<u8>,
uid: u32,
anchor: Option<TrustAnchor>,
server_config: Arc<ServerConfig>,
registry: Arc<SessionRegistry>,
}
impl StagedControlFace {
/// Stages an undiscoverable private control face before durable publication.
///
/// # Errors
/// Refuses existing artifacts, changed paths, ownership/mode errors, or TLS failure.
pub fn stage(
runtime_domain_root: &Path,
uid: u32,
anchor: TrustAnchor,
registry: Arc<SessionRegistry>,
) -> Result<Self, ControlError> {
if registry.generation_identity != anchor.generation_identity {
return Err(ControlError::StaleGeneration);
}
let paths = ControlPaths::open(runtime_domain_root, uid)?;
if [
&paths.socket,
&paths.identity,
&paths.staged_socket,
&paths.staged_identity,
]
.into_iter()
.any(|path| path.try_exists().unwrap_or(true))
{
return Err(ControlError::InvalidPublication(
"publication path already exists",
));
}
let publication_identity = anchor.publication_identity();
let publication_bytes = encode(&publication_identity)?;
let server_config = Arc::new(anchor.server_config()?);
let (identity_file, identity_file_identity) = paths.create_named_identity(
STAGED_PUBLICATION_IDENTITY_NAME,
&publication_bytes,
uid,
)?;
let listener = std::os::unix::net::UnixListener::bind(&paths.staged_socket)
.map_err(|_| ControlError::InvalidPublication("staged control socket bind failed"))?;
std::fs::set_permissions(&paths.staged_socket, std::fs::Permissions::from_mode(0o600))?;
let socket_metadata = std::fs::symlink_metadata(&paths.staged_socket)?;
let staged_socket_identity = FileIdentity::from_metadata(&socket_metadata);
if !socket_metadata.file_type().is_socket()
|| socket_metadata.uid() != uid
|| socket_metadata.mode() & 0o7777 != 0o600
{
return Err(ControlError::InvalidPublication(
"staged control socket ownership or mode is invalid",
));
}
paths.root_file.sync_all()?;
Ok(Self {
listener: Some(listener),
paths,
staged_socket_identity,
identity_file,
identity_file_identity,
publication_identity,
publication_bytes,
uid,
anchor: Some(anchor),
server_config,
registry,
})
}
/// Exposes the staged face after the exact generation is durably Published.
///
/// # Errors
/// Refuses stale durable state, changed staged artifacts, or exposure failure.
pub fn expose(mut self) -> Result<PublishedControlFace, ControlError> {
self.registry.require_serviceable_generation()?;
self.paths.validate_root(self.uid)?;
let staged_socket = std::fs::symlink_metadata(&self.paths.staged_socket)?;
if FileIdentity::from_metadata(&staged_socket) != self.staged_socket_identity {
return Err(ControlError::InvalidPublication(
"staged control socket changed",
));
}
self.paths.validate_identity_at(
&self.paths.staged_identity,
&self.identity_file,
self.identity_file_identity,
self.uid,
self.publication_bytes.len(),
)?;
std::fs::rename(&self.paths.staged_socket, &self.paths.socket)?;
if let Err(error) = std::fs::rename(&self.paths.staged_identity, &self.paths.identity) {
remove_if_identity_matches(&self.paths.socket, self.staged_socket_identity);
return Err(error.into());
}
self.paths.root_file.sync_all()?;
let socket_identity =
FileIdentity::from_metadata(&std::fs::symlink_metadata(&self.paths.socket)?);
self.paths.validate_identity(
&self.identity_file,
self.identity_file_identity,
self.uid,
self.publication_bytes.len(),
)?;
let paths = ControlPaths::open(&self.paths.root, self.uid)?;
Ok(PublishedControlFace {
listener: self
.listener
.take()
.ok_or(ControlError::InvalidPublication(
"staged listener is absent",
))?,
paths,
socket_identity,
identity_file: self.identity_file.try_clone()?,
identity_file_identity: self.identity_file_identity,
publication_identity: self.publication_identity.clone(),
publication_bytes: self.publication_bytes.clone(),
uid: self.uid,
anchor: self.anchor.take().ok_or(ControlError::InvalidPublication(
"staged Trust Anchor is absent",
))?,
server_config: Arc::clone(&self.server_config),
registry: Arc::clone(&self.registry),
})
}
}
impl Drop for StagedControlFace {
fn drop(&mut self) {
remove_if_identity_matches(&self.paths.staged_socket, self.staged_socket_identity);
remove_if_identity_matches(&self.paths.staged_identity, self.identity_file_identity);
}
}
impl PublishedControlFace {
/// Publishes one generation control face in the fixed host-private runtime path.
///
@ -813,83 +980,11 @@ impl PublishedControlFace {
anchor: TrustAnchor,
store: Arc<HostCoordinatorDomainStore>,
) -> Result<Self, ControlError> {
let paths = ControlPaths::open(runtime_domain_root, uid)?;
if paths.socket.try_exists()? || paths.identity.try_exists()? {
return Err(ControlError::InvalidPublication(
"publication path already exists",
));
}
let registry = Arc::new(SessionRegistry::new(
store,
anchor.generation_identity.clone(),
));
registry.require_generation_authority()?;
let publication_identity = anchor.publication_identity();
let publication_bytes = encode(&publication_identity)?;
let server_config = Arc::new(anchor.server_config()?);
let (identity_file, identity_file_identity) =
paths.create_identity(&publication_bytes, uid)?;
let listener = match std::os::unix::net::UnixListener::bind(&paths.socket) {
Ok(listener) => listener,
Err(error) => {
let _ = std::fs::remove_file(&paths.identity);
return Err(ControlError::InvalidPublication(
if error.kind() == io::ErrorKind::PermissionDenied {
"control socket bind was denied"
} else {
"control socket bind failed"
},
));
}
};
let finalize = (|| {
std::fs::set_permissions(&paths.socket, std::fs::Permissions::from_mode(0o600))
.map_err(|_| ControlError::InvalidPublication("control socket chmod failed"))?;
let socket_metadata = std::fs::symlink_metadata(&paths.socket)?;
if !socket_metadata.file_type().is_socket()
|| socket_metadata.uid() != uid
|| socket_metadata.mode() & 0o7777 != 0o600
{
return Err(ControlError::InvalidPublication(
"control socket ownership or mode is invalid",
));
}
paths.validate_root(uid)?;
Ok(FileIdentity::from_metadata(&socket_metadata))
})();
let socket_identity = match finalize {
Ok(identity) => identity,
Err(error) => {
drop(listener);
let _ = std::fs::remove_file(&paths.socket);
let _ = std::fs::remove_file(&paths.identity);
return Err(error);
}
};
if let Err(error) = paths.validate_identity(
&identity_file,
identity_file_identity,
uid,
publication_bytes.len(),
) {
drop(listener);
let _ = std::fs::remove_file(&paths.socket);
remove_if_identity_matches(&paths.identity, identity_file_identity);
return Err(error);
}
Ok(Self {
listener,
paths,
socket_identity,
identity_file,
identity_file_identity,
publication_identity,
publication_bytes,
uid,
anchor,
server_config,
registry,
})
StagedControlFace::stage(runtime_domain_root, uid, anchor, registry)?.expose()
}
#[must_use]