use landlock to further lock down proxy process

This commit is contained in:
Robin Appelman 2025-12-03 02:39:32 +01:00
commit 63a800e20f
4 changed files with 82 additions and 21 deletions

View file

@ -10,6 +10,12 @@ pub struct ForwardTarget {
pub addr: SocketAddr,
}
impl ForwardTarget {
pub fn port(&self) -> u16 {
self.addr.port()
}
}
impl From<ForwardTarget> for SocketAddr {
fn from(value: ForwardTarget) -> Self {
value.addr

View file

@ -13,11 +13,12 @@ use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use std::thread::spawn;
use landlock::{Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError, RulesetStatus, ABI};
use nix::errno::Errno;
use thiserror::Error;
use tokio::runtime::Builder;
use tokio::signal::ctrl_c;
use tracing::error;
use tracing::{error, info, warn};
use uzers::{get_group_by_name, get_user_by_name};
#[derive(Debug, Error)]
@ -144,31 +145,34 @@ pub fn proxy(
}
};
if let Some(listen_namespace) = listen_namespace
&& let Err(error) = setns(listen_namespace, CloneFlags::CLONE_NEWNET)
{
error!(%error, "Failed to join listen network namespace for proxy");
return Err(error.into());
}
let proxy = match Proxy::listen(&source) {
Ok(proxy) => proxy,
Err(error) => {
error!(%error, "Failed to listen to {source}");
rt.block_on(async {
if let Some(listen_namespace) = listen_namespace
&& let Err(error) = setns(listen_namespace, CloneFlags::CLONE_NEWNET)
{
error!(%error, "Failed to join listen network namespace for proxy");
return Err(error.into());
}
};
if let Err(error) = setns(target_namespace, CloneFlags::CLONE_NEWNET) {
error!(%error, "Failed to join target network namespace for proxy");
return Err(error.into());
}
let proxy = match Proxy::listen(&source) {
Ok(proxy) => proxy,
Err(error) => {
error!(%error, "Failed to listen to {source}");
return Err(error.into());
}
};
if let Err(error) = drop_to_nobody() {
error!(%error, "Failed to drop privileges");
}
if let Err(error) = setns(target_namespace, CloneFlags::CLONE_NEWNET) {
error!(%error, "Failed to join target network namespace for proxy");
return Err(error.into());
}
if let Err(error) = drop_to_nobody() {
error!(%error, "Failed to drop privileges");
}
if let Err(error) = landlock(target.port()) {
error!(%error, "Failed to apply landlock sandbox");
}
rt.block_on(async {
tokio::spawn(async move {
let _ = ctrl_c().await;
abort.abort();
@ -193,4 +197,22 @@ fn drop_to_nobody() -> Result<(), Errno> {
);
setgid(nobody_gid).and_then(|_| setuid(nobody_uid))
}
fn landlock(port: u16) -> Result<(), RulesetError> {
let abi = ABI::V6;
let status = Ruleset::default()
.handle_access(AccessNet::ConnectTcp)?
.handle_access(AccessNet::BindTcp)?
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rule(NetPort::new(port, AccessNet::ConnectTcp))?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => info!("Fully sandboxed."),
RulesetStatus::PartiallyEnforced => warn!("Partially sandboxed."),
RulesetStatus::NotEnforced => error!("Not sandboxed! Please update your kernel."),
}
Ok(())
}