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

32
Cargo.lock generated
View file

@ -235,6 +235,26 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "enumflags2"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
dependencies = [
"enumflags2_derive",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -455,6 +475,17 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "landlock"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088"
dependencies = [
"enumflags2",
"libc",
"thiserror",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -564,6 +595,7 @@ dependencies = [
"either", "either",
"futures", "futures",
"futures-concurrency", "futures-concurrency",
"landlock",
"main_error", "main_error",
"neli", "neli",
"nix", "nix",

View file

@ -22,6 +22,7 @@ neli = "0.7.1"
either = "1.15.0" either = "1.15.0"
uzers = "0.12.1" uzers = "0.12.1"
sysinfo = "0.37.2" sysinfo = "0.37.2"
landlock = "0.4.4"
[dev-dependencies] [dev-dependencies]
serde_test = "1.0.177" serde_test = "1.0.177"

View file

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

View file

@ -13,11 +13,12 @@ use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command}; use std::process::{Child, Command};
use std::thread::spawn; use std::thread::spawn;
use landlock::{Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError, RulesetStatus, ABI};
use nix::errno::Errno; use nix::errno::Errno;
use thiserror::Error; use thiserror::Error;
use tokio::runtime::Builder; use tokio::runtime::Builder;
use tokio::signal::ctrl_c; use tokio::signal::ctrl_c;
use tracing::error; use tracing::{error, info, warn};
use uzers::{get_group_by_name, get_user_by_name}; use uzers::{get_group_by_name, get_user_by_name};
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -144,6 +145,7 @@ pub fn proxy(
} }
}; };
rt.block_on(async {
if let Some(listen_namespace) = listen_namespace if let Some(listen_namespace) = listen_namespace
&& let Err(error) = setns(listen_namespace, CloneFlags::CLONE_NEWNET) && let Err(error) = setns(listen_namespace, CloneFlags::CLONE_NEWNET)
{ {
@ -167,8 +169,10 @@ pub fn proxy(
if let Err(error) = drop_to_nobody() { if let Err(error) = drop_to_nobody() {
error!(%error, "Failed to drop privileges"); 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 { tokio::spawn(async move {
let _ = ctrl_c().await; let _ = ctrl_c().await;
abort.abort(); abort.abort();
@ -194,3 +198,21 @@ fn drop_to_nobody() -> Result<(), Errno> {
setgid(nobody_gid).and_then(|_| setuid(nobody_uid)) 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(())
}