forked from containers/youki
-
Notifications
You must be signed in to change notification settings - Fork 0
/
spec_json.rs
125 lines (118 loc) · 3.84 KB
/
spec_json.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use anyhow::Result;
use clap::Clap;
use nix;
use oci_spec::runtime::{
Linux, LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder,
LinuxNamespaceType, MountBuilder, Spec, SpecBuilder,
};
use path_clean;
use serde_json::to_writer_pretty;
use std::fs::File;
use std::path::PathBuf;
/// Command generates a config.json
#[derive(Clap, Debug)]
pub struct SpecJson {
/// Generate a configuration for a rootless container
#[clap(long)]
pub rootless: bool,
}
pub fn set_for_rootless(spec: &Spec) -> Result<Spec> {
let uid = nix::unistd::geteuid().as_raw();
let gid = nix::unistd::getegid().as_raw();
// Remove network from the default spec
let mut namespaces: Vec<LinuxNamespace> = spec
.linux()
.as_ref()
.unwrap_or(&Linux::default())
.namespaces()
.as_ref()
.unwrap_or(&vec![])
.iter()
.filter(|&ns| {
ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User
})
.cloned()
.collect();
// Add user namespace
namespaces.push(
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::User)
.build()?,
);
let linux_builder = LinuxBuilder::default()
.namespaces(namespaces)
.uid_mappings(vec![LinuxIdMappingBuilder::default()
.host_id(uid)
.container_id(0_u32)
.size(1_u32)
.build()?])
.gid_mappings(vec![LinuxIdMappingBuilder::default()
.host_id(gid)
.container_id(0_u32)
.size(1_u32)
.build()?]);
// Fix the mounts
let mut mounts = vec![];
for mount in spec.mounts().as_ref().unwrap().iter() {
let dest = mount.destination().clone();
// Use path_clean to reduce multiple slashes to a single slash
// and take care of '..' and '.' in dest path.
if path_clean::clean(dest.as_path().to_str().unwrap()) == "/sys" {
let mount = MountBuilder::default()
.destination(PathBuf::from("/sys"))
.source(PathBuf::from("/sys"))
.typ("none".to_string())
.options(vec![
"rbind".to_string(),
"nosuid".to_string(),
"noexec".to_string(),
"nodev".to_string(),
"ro".to_string(),
])
.build()?;
mounts.push(mount);
} else {
let options: Vec<String> = mount
.options()
.as_ref()
.unwrap_or(&vec![])
.iter()
.filter(|&o| !o.starts_with("gid=") && !o.starts_with("uid="))
.map(|o| o.to_string())
.collect();
let mount_builder = MountBuilder::default().options(options);
mounts.push(mount_builder.build()?);
}
}
let spec_builder = SpecBuilder::default()
.linux(linux_builder.build()?)
.mounts(mounts);
Ok(spec_builder.build()?)
}
/// spec Cli command
impl SpecJson {
pub fn exec(&self) -> Result<()> {
// get default values for Spec
let mut default_json: Spec = Default::default();
if self.rootless {
default_json = set_for_rootless(&default_json)?
};
// write data to config.json
to_writer_pretty(&File::create("config.json")?, &default_json)?;
Ok(())
}
}
#[cfg(test)]
// Tests become unstable if not serial. The cause is not known.
mod tests {
use super::*;
#[test]
fn test_spec_json() -> Result<()> {
let mut spec = Default::default();
spec = set_for_rootless(&spec)?;
let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().join("config.json");
to_writer_pretty(&File::create(path)?, &spec)?;
Ok(())
}
}