Skip to content

Commit 87307ea

Browse files
committed
Move cgroup device controller to a module
1 parent ce537da commit 87307ea

File tree

5 files changed

+132
-47
lines changed

5 files changed

+132
-47
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ license = "MIT OR Apache-2.0"
1010
anyhow = { version = "1", features = ["backtrace"] }
1111
log = "0.4"
1212
env_logger = "0.11"
13-
clap = { version = "4", features = ["default", "derive", "unicode", "wrap_help"] }
13+
clap = { version = "4", features = [
14+
"default",
15+
"derive",
16+
"unicode",
17+
"wrap_help",
18+
] }
1419
clap-verbosity-flag = "2"
1520
humantime = "2"
1621
bytes = "1"
@@ -24,3 +29,4 @@ udev = "0.8"
2429
bollard = "0.16"
2530
futures = "0.3"
2631
rustix = { version = "0.38", features = ["fs", "stdio", "termios"] }
32+
bitflags = "2"

src/docker/cgroup.rs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use anyhow::{ensure, Result};
2+
use std::path::PathBuf;
3+
4+
// The numerical representation below needs to match BPF_DEVCG constants.
5+
#[allow(unused)]
6+
#[repr(u32)]
7+
pub enum DeviceType {
8+
Block = 1,
9+
Character = 2,
10+
}
11+
12+
bitflags::bitflags! {
13+
pub struct Access: u32 {
14+
const MKNOD = 1;
15+
const READ = 2;
16+
const WRITE = 4;
17+
}
18+
}
19+
20+
pub trait DeviceAccessController {
21+
/// Set the permission for a specific device.
22+
fn set_permission(
23+
&mut self,
24+
ty: DeviceType,
25+
major: u32,
26+
minor: u32,
27+
access: Access,
28+
) -> Result<()>;
29+
}
30+
31+
pub struct DeviceAccessControllerV1 {
32+
cgroup: PathBuf,
33+
}
34+
35+
impl DeviceAccessControllerV1 {
36+
pub fn new(id: &str) -> Result<Self> {
37+
let cgroup: PathBuf = format!("/sys/fs/cgroup/devices/docker/{id}").into();
38+
39+
ensure!(
40+
cgroup.is_dir(),
41+
"cgroup {} does not exist",
42+
cgroup.display()
43+
);
44+
45+
Ok(Self { cgroup })
46+
}
47+
}
48+
49+
impl DeviceAccessController for DeviceAccessControllerV1 {
50+
fn set_permission(
51+
&mut self,
52+
ty: DeviceType,
53+
major: u32,
54+
minor: u32,
55+
access: Access,
56+
) -> Result<()> {
57+
let mut denied = String::with_capacity(3);
58+
let mut allowed = String::with_capacity(3);
59+
60+
let ty = match ty {
61+
DeviceType::Character => 'c',
62+
DeviceType::Block => 'b',
63+
};
64+
65+
if access.contains(Access::READ) {
66+
allowed.push('r');
67+
} else {
68+
denied.push('r');
69+
}
70+
71+
if access.contains(Access::WRITE) {
72+
allowed.push('w');
73+
} else {
74+
denied.push('w');
75+
}
76+
77+
if access.contains(Access::MKNOD) {
78+
allowed.push('m');
79+
} else {
80+
denied.push('m');
81+
}
82+
83+
if !denied.is_empty() {
84+
std::fs::write(
85+
self.cgroup.join("devices.deny"),
86+
format!("{ty} {major}:{minor} {denied}"),
87+
)?;
88+
}
89+
90+
if !allowed.is_empty() {
91+
std::fs::write(
92+
self.cgroup.join("devices.allow"),
93+
format!("{ty} {major}:{minor} {allowed}"),
94+
)?;
95+
}
96+
97+
Ok(())
98+
}
99+
}

src/docker/container.rs

+24-46
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
use std::pin::pin;
2+
use std::sync::{Arc, Mutex};
23
use std::time::Duration;
34

4-
use super::{IoStream, IoStreamSource};
5-
65
use anyhow::{anyhow, Context, Error, Result};
76
use bollard::service::EventMessage;
87
use futures::future::{BoxFuture, Shared};
98
use futures::FutureExt;
10-
use tokio::io::AsyncWriteExt;
119
use tokio::signal::unix::{signal, SignalKind};
1210
use tokio::task::{spawn, JoinHandle};
1311
use tokio_stream::StreamExt;
1412

13+
use super::cgroup::{Access, DeviceAccessController, DeviceAccessControllerV1, DeviceType};
14+
use super::{IoStream, IoStreamSource};
15+
1516
#[derive(Clone)]
1617
pub struct Container {
1718
id: String,
1819
docker: bollard::Docker,
1920
remove_event: Shared<BoxFuture<'static, Option<EventMessage>>>,
21+
cgroup_device_filter: Arc<Mutex<Box<dyn DeviceAccessController + Send>>>,
2022
}
2123

2224
impl Container {
@@ -37,10 +39,14 @@ impl Container {
3739
.boxed()
3840
.shared();
3941

42+
let cgroup_device_filter: Box<dyn DeviceAccessController + Send> =
43+
Box::new(DeviceAccessControllerV1::new(id)?);
44+
4045
Ok(Self {
4146
id: id.to_owned(),
4247
docker: docker.clone(),
4348
remove_event: remove_evevnt,
49+
cgroup_device_filter: Arc::new(Mutex::new(cgroup_device_filter)),
4450
})
4551
}
4652

@@ -76,6 +82,7 @@ impl Container {
7682
.await
7783
.context("no destroy event")?;
7884
}
85+
7986
Ok(())
8087
}
8188

@@ -219,24 +226,21 @@ impl Container {
219226
(major, minor): (u32, u32),
220227
(r, w, m): (bool, bool, bool),
221228
) -> Result<()> {
222-
let mut permissions = String::new();
223-
if r {
224-
permissions.push('r');
225-
}
226-
if w {
227-
permissions.push('w');
228-
}
229-
if m {
230-
permissions.push('m');
231-
}
232-
233-
deny_device_cgroup1(&self.id, major, minor, "rwm").await?;
229+
let controller = self.cgroup_device_filter.clone();
230+
tokio::task::spawn_blocking(move || -> Result<()> {
231+
let mut controller = controller.lock().unwrap();
232+
controller.set_permission(
233+
DeviceType::Character,
234+
major,
235+
minor,
236+
if r { Access::READ } else { Access::empty() }
237+
| if w { Access::WRITE } else { Access::empty() }
238+
| if m { Access::MKNOD } else { Access::empty() },
239+
)?;
234240

235-
if permissions != "" {
236-
allow_device_cgroup1(&self.id, major, minor, permissions.as_ref()).await?;
237-
}
238-
239-
Ok(())
241+
Ok(())
242+
})
243+
.await?
240244
}
241245

242246
pub async fn pipe_signals(&self) -> JoinHandle<Result<()>> {
@@ -270,32 +274,6 @@ impl Container {
270274
}
271275
}
272276

273-
async fn allow_device_cgroup1(
274-
container_id: &str,
275-
major: u32,
276-
minor: u32,
277-
permissions: &str,
278-
) -> Result<()> {
279-
let path = format!("/sys/fs/cgroup/devices/docker/{container_id}/devices.allow");
280-
let mut file = tokio::fs::OpenOptions::new().write(true).open(path).await?;
281-
let mut data = bytes::Bytes::from(format!("c {major}:{minor} {permissions}"));
282-
file.write_all_buf(&mut data).await?;
283-
Ok(())
284-
}
285-
286-
async fn deny_device_cgroup1(
287-
container_id: &str,
288-
major: u32,
289-
minor: u32,
290-
permissions: &str,
291-
) -> Result<()> {
292-
let path = format!("/sys/fs/cgroup/devices/docker/{container_id}/devices.deny");
293-
let mut file = tokio::fs::OpenOptions::new().write(true).open(path).await?;
294-
let mut data = bytes::Bytes::from(format!("c {major}:{minor} {permissions}"));
295-
file.write_all_buf(&mut data).await?;
296-
Ok(())
297-
}
298-
299277
fn signal_stream(kind: SignalKind) -> impl tokio_stream::Stream<Item = Result<SignalKind>> {
300278
async_stream::try_stream! {
301279
let sig_kind = SignalKind::hangup();

src/docker/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cgroup;
12
mod container;
23
mod docker;
34
mod iostream;

0 commit comments

Comments
 (0)