Compose cold-start resources under Runtime Supervisor ownership
This commit is contained in:
parent
510dfccf5a
commit
33ac0ba4ee
1 changed files with 1827 additions and 139 deletions
|
|
@ -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(¤t) != 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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue