Implement fail-closed ignored workspace routing
This commit is contained in:
parent
ba6a075839
commit
95104fa4e7
1 changed files with 6024 additions and 6 deletions
99
crates/lazaret-ignored-workspace/src/sentry.rs
Normal file
99
crates/lazaret-ignored-workspace/src/sentry.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustix::fs::{Mode, OFlags};
|
||||
use rustix::mount::{MountFlags, UnmountFlags, mount_bind, mount_remount, unmount};
|
||||
|
||||
use crate::backing::BackingRoot;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GitSentryConfiguration {
|
||||
pub mounted_workspace: PathBuf,
|
||||
pub host_git_common_directory: PathBuf,
|
||||
pub transient_hooks: PathBuf,
|
||||
}
|
||||
|
||||
pub struct GitSentry {
|
||||
config_target: File,
|
||||
hook_target: File,
|
||||
}
|
||||
|
||||
impl GitSentry {
|
||||
/// # Errors
|
||||
/// Refuses linked or missing sources and targets and incomplete protective mounts.
|
||||
pub fn install(configuration: &GitSentryConfiguration) -> io::Result<Self> {
|
||||
let _workspace = BackingRoot::open(&configuration.mounted_workspace)?;
|
||||
let common = BackingRoot::open(&configuration.host_git_common_directory)?;
|
||||
let transient = BackingRoot::open(&configuration.transient_hooks)?;
|
||||
|
||||
let hook_source = transient.duplicate()?;
|
||||
let hook_target = common.open_entry(
|
||||
Path::new("hooks"),
|
||||
OFlags::RDONLY | OFlags::DIRECTORY,
|
||||
Mode::empty(),
|
||||
)?;
|
||||
descriptor_bind(&hook_source, &hook_target)?;
|
||||
let hook_target = common.open_entry(
|
||||
Path::new("hooks"),
|
||||
OFlags::RDONLY | OFlags::DIRECTORY,
|
||||
Mode::empty(),
|
||||
)?;
|
||||
|
||||
let config_source =
|
||||
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
|
||||
let config_target =
|
||||
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
|
||||
descriptor_bind(&config_source, &config_target)?;
|
||||
let config_target =
|
||||
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
|
||||
if let Err(error) = descriptor_remount_read_only(&config_target) {
|
||||
let _ = descriptor_unmount(&hook_target);
|
||||
return Err(error);
|
||||
}
|
||||
Ok(Self {
|
||||
config_target,
|
||||
hook_target,
|
||||
})
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Returns an error unless both exact protective mountpoints are detached.
|
||||
pub fn unmount(self) -> io::Result<()> {
|
||||
let config = descriptor_unmount(&self.config_target);
|
||||
let hooks = descriptor_unmount(&self.hook_target);
|
||||
config.and(hooks)
|
||||
}
|
||||
}
|
||||
|
||||
fn descriptor_bind(source: &File, target: &File) -> io::Result<()> {
|
||||
Ok(mount_bind(
|
||||
proc_fd(source.as_raw_fd()),
|
||||
proc_fd(target.as_raw_fd()),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn descriptor_remount_read_only(target: &File) -> io::Result<()> {
|
||||
let result = mount_remount(
|
||||
proc_fd(target.as_raw_fd()),
|
||||
MountFlags::BIND
|
||||
| MountFlags::RDONLY
|
||||
| MountFlags::NOSUID
|
||||
| MountFlags::NODEV
|
||||
| MountFlags::NOEXEC,
|
||||
"",
|
||||
);
|
||||
if result.is_err() {
|
||||
let _ = unmount(proc_fd(target.as_raw_fd()), UnmountFlags::DETACH);
|
||||
}
|
||||
Ok(result?)
|
||||
}
|
||||
|
||||
fn descriptor_unmount(target: &File) -> io::Result<()> {
|
||||
Ok(unmount(proc_fd(target.as_raw_fd()), UnmountFlags::DETACH)?)
|
||||
}
|
||||
|
||||
fn proc_fd(fd: RawFd) -> PathBuf {
|
||||
PathBuf::from(format!("/proc/self/fd/{fd}"))
|
||||
}
|
||||
Loading…
Reference in a new issue