diff --git a/.golangci.yml b/.golangci.yml index 4abf2c1c01..e7e1d38700 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -97,14 +97,16 @@ linters: # - wsl linters-settings: funlen: - lines: 150 + lines: 155 statements: 50 varnamelen: min-name-length: 1 cyclop: - max-complexity: 30 + max-complexity: 35 gocognit: - min-complexity: 45 + min-complexity: 50 + gocyclo: + min-complexity: 50 nestif: min-complexity: 15 errcheck: diff --git a/conmon-rs/common/proto/conmon.capnp b/conmon-rs/common/proto/conmon.capnp index 20ec1271e4..cc2ffd5993 100644 --- a/conmon-rs/common/proto/conmon.capnp +++ b/conmon-rs/common/proto/conmon.capnp @@ -128,6 +128,8 @@ interface Conmon { namespaces @1 :List(Namespace); # The list of namespaces to unshare. uidMappings @2 :List(Text); # User ID mappings when unsharing the user namespace. gidMappings @3 :List(Text); # Group ID mappings when unsharing the user namespace. + basePath @4 :Text; # The root path for storing the namespaces. + podId @5 :Text; # The pod identifier. } enum Namespace { diff --git a/conmon-rs/server/src/config.rs b/conmon-rs/server/src/config.rs index 502d15f931..3355e89015 100644 --- a/conmon-rs/server/src/config.rs +++ b/conmon-rs/server/src/config.rs @@ -136,16 +136,35 @@ pub struct Config { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Subcommand)] /// Possible subcommands. pub enum Commands { - /// Run pause instead of the server. + /// Run pause, which bind mounts selected namespaces to the local file system. + /// + /// If a namespace is not selected by one of the flags, then it will fallback to the host + /// namespace and still create the bind mount to it. All namespaces are mounted to + /// /var/run/[ipc,pid,net,user,uts]ns/$POD_ID, whereas the POD_ID is being passed from the + /// client. + /// + /// Tracking of the pause PID will be done by using a file in /var/run/conmonrs/$POD_ID.pid, + /// which gets removed together with the mounted namespaces if `conmonrs pause` terminates. + /// + /// UID and GID mappings are required if unsharing of the user namespace (via `--user`) is + /// selected. Pause { #[clap( - env(concat!(prefix!(), "PAUSE_PATH")), - long("path"), + default_value("/var/run"), + env(concat!(prefix!(), "PAUSE_BASE_PATH")), + long("base-path"), short('p'), value_name("PATH") )] /// The base path for pinning the namespaces. - path: PathBuf, + base_path: PathBuf, + + #[clap( + env(concat!(prefix!(), "PAUSE_POD_ID")), + long("pod-id"), + )] + /// The unique pod identifier for referring to the namespaces. + pod_id: String, #[clap(long("ipc"))] /// Unshare the IPC namespace. diff --git a/conmon-rs/server/src/pause.rs b/conmon-rs/server/src/pause.rs index a3a13fcbcf..21b83645f5 100644 --- a/conmon-rs/server/src/pause.rs +++ b/conmon-rs/server/src/pause.rs @@ -20,14 +20,16 @@ use std::{ process::{exit, Command}, }; use strum::{AsRefStr, Display, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr}; -use tracing::{debug, error, info}; -use uuid::Uuid; +use tracing::{debug, info, trace, warn}; /// The main structure for this module. #[derive(Debug, CopyGetters, Getters)] pub struct Pause { #[get = "pub"] - path: PathBuf, + base_path: PathBuf, + + #[get = "pub"] + pod_id: String, #[get = "pub"] namespaces: Vec, @@ -39,21 +41,18 @@ pub struct Pause { /// The global shared multiple pause instance. static PAUSE: OnceCell = OnceCell::new(); -/// The global path for storing bin mounted namespaces. -const PAUSE_PATH: &str = "/var/run/conmonrs"; - -/// The file path for storing the pause PID. -const PAUSE_PID_FILE: &str = ".pause_pid"; - impl Pause { /// Retrieve the global instance of pause pub fn init_shared( + base_path: &str, + pod_id: &str, namespaces: Reader, uid_mappings: Vec, gid_mappings: Vec, ) -> Result<&'static Pause> { PAUSE.get_or_try_init(|| { - Self::init(namespaces, uid_mappings, gid_mappings).context("init pause") + Self::init(base_path, pod_id, namespaces, uid_mappings, gid_mappings) + .context("init pause") }) } @@ -66,34 +65,39 @@ impl Pause { pub fn stop(&self) { info!("Stopping pause"); for namespace in self.namespaces() { - if let Err(e) = namespace.umount(self.path()) { + if let Err(e) = namespace.umount(self.base_path(), self.pod_id()) { debug!("Unable to umount namespace {namespace}: {:#}", e); } } - if let Err(e) = fs::remove_dir_all(self.path()) { - error!( - "Unable to remove pause path {}: {:#}", - self.path().display(), - e - ); - } info!("Killing pause PID: {}", self.pid()); if let Err(e) = kill(self.pid(), Signal::SIGTERM) { - error!("Unable to kill pause PID {}: {:#}", self.pid(), e); + warn!("Unable to kill pause PID {}: {:#}", self.pid(), e); + } + + let pause_pid_path = Self::pause_pid_path(self.base_path(), self.pod_id()); + if let Err(e) = fs::remove_file(&pause_pid_path) { + debug!( + "Unable to remove pause PID path {}: {:#}", + pause_pid_path.display(), + e + ); } } /// Initialize a new pause instance. fn init( + base_path: &str, + pod_id: &str, init_namespaces: Reader, uid_mappings: Vec, gid_mappings: Vec, ) -> Result { debug!("Initializing pause"); - let mut args: Vec = vec![]; + let mut args: Vec = vec![format!("--pod-id={pod_id}")]; let mut namespaces = vec![]; + for namespace in init_namespaces.iter() { match namespace? { conmon::Namespace::Ipc => { @@ -138,15 +142,15 @@ impl Pause { debug!("Pause namespaces: {:?}", namespaces); debug!("Pause args: {:?}", args); - let path = PathBuf::from(PAUSE_PATH).join(Uuid::new_v4().to_string()); - fs::create_dir_all(&path).context("create base path")?; - debug!("Pause base path: {}", path.display()); + let base_path = PathBuf::from(base_path); + fs::create_dir_all(&base_path).context("create base path")?; + debug!("Pause base path: {}", base_path.display()); let program = env::args().next().context("no args set")?; let mut child = Command::new(program) .arg("pause") - .arg("--path") - .arg(&path) + .arg("--base-path") + .arg(&base_path) .args(args) .spawn() .context("run pause")?; @@ -156,7 +160,7 @@ impl Pause { bail!("exit status not ok: {status}") } - let pid = fs::read_to_string(path.join(PAUSE_PID_FILE)) + let pid = fs::read_to_string(Self::pause_pid_path(&base_path, pod_id)) .context("read pause PID path")? .trim() .parse::() @@ -164,16 +168,26 @@ impl Pause { info!("Pause PID is: {pid}"); Ok(Self { - path, + base_path, + pod_id: pod_id.to_owned(), namespaces: Namespace::iter().collect(), pid: Pid::from_raw(pid as pid_t), }) } + /// Retrieve the pause PID path for a base and pod ID. + fn pause_pid_path>(base_path: T, pod_id: &str) -> PathBuf { + base_path + .as_ref() + .join("conmonrs") + .join(format!("{pod_id}.pid")) + } + #[allow(clippy::too_many_arguments)] /// Run a new pause instance. pub fn run + Copy>( - path: T, + base_path: T, + pod_id: &str, ipc: bool, pid: bool, net: bool, @@ -209,16 +223,22 @@ impl Pause { let (mut sock_parent, mut sock_child) = UnixStream::pair().context("create unix socket pair")?; const MSG: &[u8] = &[1]; + let mut res = [0]; match unsafe { fork().context("forking process")? } { ForkResult::Parent { child } => { - let mut file = File::create(path.as_ref().join(PAUSE_PID_FILE)) - .context("create pause PID file")?; + let pause_pid_path = Self::pause_pid_path(base_path, pod_id); + fs::create_dir_all( + pause_pid_path + .parent() + .context("no parent for pause PID path")?, + ) + .context("create pause PID parent path")?; + let mut file = File::create(pause_pid_path).context("create pause PID file")?; write!(file, "{child}").context("write child to pause file")?; if user { // Wait for user namespace creation - let mut res = [0]; sock_parent.read_exact(&mut res)?; // Write mappings @@ -229,6 +249,9 @@ impl Pause { sock_parent.write_all(MSG)?; } + // Wait for mounts to be created + sock_parent.read_exact(&mut res)?; + exit(0); } @@ -239,7 +262,6 @@ impl Pause { sock_child.write_all(MSG)?; // Wait for the mappings to be written - let mut res = [0]; sock_child.read_exact(&mut res)?; // Set the UID and GID @@ -258,12 +280,15 @@ impl Pause { // We bind all namespaces, if not unshared then we use the host namespace. for namespace in Namespace::iter() { - namespace.bind(path.as_ref()).context(format!( + namespace.bind(base_path, pod_id).context(format!( "bind namespace to path: {}", - namespace.path(path).display(), + namespace.path(base_path, pod_id).display(), ))?; } + // Notify that all mounts are created + sock_child.write_all(MSG)?; + let mut signals = Signals::new(TERM_SIGNALS).context("register signals")?; signals.forever().next().context("no signal number")?; Ok(()) @@ -323,9 +348,15 @@ pub enum Namespace { } impl Namespace { - /// Bind the namespace to the provided base path. - pub fn bind>(&self, path: T) -> Result<()> { - let bind_path = self.path(path); + /// Bind the namespace to the provided base path and pod ID. + pub fn bind>(&self, path: T, pod_id: &str) -> Result<()> { + let bind_path = self.path(path, pod_id); + fs::create_dir_all( + bind_path + .parent() + .context("no parent namespace bind path")?, + ) + .context("create namespace parent path")?; File::create(&bind_path).context("create namespace bind path")?; let source_path = PathBuf::from("/proc/self/ns").join(self.as_ref()); @@ -342,14 +373,17 @@ impl Namespace { } /// Umount the namespace. - pub fn umount>(&self, path: T) -> Result<()> { - let bind_path = self.path(path); - umount(&bind_path).context("umount namespace") + pub fn umount>(&self, path: T, pod_id: &str) -> Result<()> { + let bind_path = self.path(path, pod_id); + if let Err(e) = umount(&bind_path) { + trace!("Unable to umount namespace {self}: {:#}", e); + } + fs::remove_file(&bind_path).context("remove namespace bind path") } - /// Retrieve the bind path of the namespace for the provided base path. - pub fn path>(&self, path: T) -> PathBuf { - path.as_ref().join(self.as_ref()) + /// Retrieve the bind path of the namespace for the provided base path and pod ID. + pub fn path>(&self, path: T, pod_id: &str) -> PathBuf { + path.as_ref().join(format!("{self}ns")).join(pod_id) } pub fn to_capnp_namespace(self) -> conmon::Namespace { diff --git a/conmon-rs/server/src/rpc.rs b/conmon-rs/server/src/rpc.rs index 02cc716721..1c6c8ccdba 100644 --- a/conmon-rs/server/src/rpc.rs +++ b/conmon-rs/server/src/rpc.rs @@ -360,15 +360,19 @@ impl conmon::Server for Server { ) -> Promise<(), capnp::Error> { debug!("Got a create namespaces request"); let req = pry!(pry!(params.get()).get_request()); + let pod_id = pry_err!(req.get_pod_id()); - let span = debug_span!( - "create_namespaces", - uuid = Uuid::new_v4().to_string().as_str() - ); + if pod_id.is_empty() { + return Promise::err(Error::failed("no pod ID provided".into())); + } + + let span = new_root_span!("create_namespaces", pod_id); let _enter = span.enter(); pry_err!(Telemetry::set_parent_context(pry!(req.get_metadata()))); let pause = pry_err!(Pause::init_shared( + pry!(req.get_base_path()), + pod_id, pry!(req.get_namespaces()), capnp_vec_str!(req.get_uid_mappings()), capnp_vec_str!(req.get_gid_mappings()), @@ -380,7 +384,12 @@ impl conmon::Server for Server { for (idx, namespace) in pause.namespaces().iter().enumerate() { let mut ns = namespaces.reborrow().get(pry_err!(idx.try_into())); - ns.set_path(&namespace.path(pause.path()).display().to_string()); + ns.set_path( + &namespace + .path(pause.base_path(), pod_id) + .display() + .to_string(), + ); ns.set_type(namespace.to_capnp_namespace()); } diff --git a/conmon-rs/server/src/server.rs b/conmon-rs/server/src/server.rs index 1b80ae26b6..1c651c213b 100644 --- a/conmon-rs/server/src/server.rs +++ b/conmon-rs/server/src/server.rs @@ -64,7 +64,8 @@ impl Server { } if let Some(Commands::Pause { - path, + base_path, + pod_id, ipc, pid, net, @@ -75,7 +76,8 @@ impl Server { }) = server.config().command() { Pause::run( - path, + base_path, + pod_id, *ipc, *pid, *net, diff --git a/internal/proto/conmon.capnp.go b/internal/proto/conmon.capnp.go index cda0dfe56f..059694f9ac 100644 --- a/internal/proto/conmon.capnp.go +++ b/internal/proto/conmon.capnp.go @@ -2022,12 +2022,12 @@ type Conmon_CreateNamespacesRequest capnp.Struct const Conmon_CreateNamespacesRequest_TypeID = 0x8b5b1693940f607e func NewConmon_CreateNamespacesRequest(s *capnp.Segment) (Conmon_CreateNamespacesRequest, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 4}) + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 6}) return Conmon_CreateNamespacesRequest(st), err } func NewRootConmon_CreateNamespacesRequest(s *capnp.Segment) (Conmon_CreateNamespacesRequest, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 4}) + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 6}) return Conmon_CreateNamespacesRequest(st), err } @@ -2145,13 +2145,48 @@ func (s Conmon_CreateNamespacesRequest) NewGidMappings(n int32) (capnp.TextList, err = capnp.Struct(s).SetPtr(3, l.ToPtr()) return l, err } +func (s Conmon_CreateNamespacesRequest) BasePath() (string, error) { + p, err := capnp.Struct(s).Ptr(4) + return p.Text(), err +} + +func (s Conmon_CreateNamespacesRequest) HasBasePath() bool { + return capnp.Struct(s).HasPtr(4) +} + +func (s Conmon_CreateNamespacesRequest) BasePathBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(4) + return p.TextBytes(), err +} + +func (s Conmon_CreateNamespacesRequest) SetBasePath(v string) error { + return capnp.Struct(s).SetText(4, v) +} + +func (s Conmon_CreateNamespacesRequest) PodId() (string, error) { + p, err := capnp.Struct(s).Ptr(5) + return p.Text(), err +} + +func (s Conmon_CreateNamespacesRequest) HasPodId() bool { + return capnp.Struct(s).HasPtr(5) +} + +func (s Conmon_CreateNamespacesRequest) PodIdBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(5) + return p.TextBytes(), err +} + +func (s Conmon_CreateNamespacesRequest) SetPodId(v string) error { + return capnp.Struct(s).SetText(5, v) +} // Conmon_CreateNamespacesRequest_List is a list of Conmon_CreateNamespacesRequest. type Conmon_CreateNamespacesRequest_List = capnp.StructList[Conmon_CreateNamespacesRequest] // NewConmon_CreateNamespacesRequest creates a new list of Conmon_CreateNamespacesRequest. func NewConmon_CreateNamespacesRequest_List(s *capnp.Segment, sz int32) (Conmon_CreateNamespacesRequest_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 4}, sz) + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 6}, sz) return capnp.StructList[Conmon_CreateNamespacesRequest](l), err } @@ -3674,180 +3709,184 @@ func (p Conmon_createNamespaces_Results_Future) Response() Conmon_CreateNamespac return Conmon_CreateNamespacesResponse_Future{Future: p.Future.Field(0, nil)} } -const schema_ffaaf7385bc4adad = "x\xda\xacX\x7fp\x14\xe5\xf9\x7f\x9ew\xef\xb2w\x98" + - "p\xd9\xecE!#\xdf\xf8\xcd\xa0\x83A\xe4G\xb0b" + - "\x06&\x09\x90\xa1X\xb4\xd9\x1cj\x0b\xea\xb0\xdc-\xc9" + - "an\xf7\xb2\xbb'\x04K\x11\x95\xa9`\xb5\x85\xc2h" + - "\x1c\x99\x81*\x8ePS\xb5\x8aU\xda\xdaAeT\x94" + - "\xd6\xf0\x07\xadN\x7fYJ\x15\xa7\xa3u\xb4St\xb4" + - "\xdby\xde\xbd\xfd\x91\xcb\x8d^\x92\xfeuw\xcf~\xee" + - "y\xde}\xde\xe7\xfd<\x9f\xe7\x9d\xf3\xa9\xd8\x1e\x99[" + - "\xf3\xebj`\xca\xeeh\x95c\xbd3`>\xbaw\xd9" + - "\x9d \xcdD\x80(\x8a\x00-\x9dUM\x0cP\xbe\xa9" + - "\xaa\x0d\xd0\xf9\xd7#\xaf.\xba\x7f\xe7\x87;\xc2\x80\xcd" + - ".`\x0f\x07\xfcaA\xf3\xba}\xc2\x8a{\xc2\x80\xe7" + - "\xab\x1a\x080\xcc\x01\xdf]\x93\xd8\xfd\xa3\xf3Ws\x80" + - "\xf3Q\xcb\xba?\x0e\xbew\xe5\xcf!\x1a!\xe0GU" + - "\x0dL\x96D\x11@\xae\x11\xdf\x05t^\xff\xbfM7" + - "$\x1e\xfd\xde\x03%`\xee\xf5\xffcML\xee\x88\x11" + - "xQ\x8c\x1a?" + - "\x84\xf2[\xf1\x0b\x00\xe4w\xe2O\x00:\xb7\x0d\xbf\xff" + - "\xd8}\xf7t\x1c.\xf5\xcd\x08=0\x891y\xcf$" + - "\xf2\xbds\x12\xa5\xdc\x7f.M\x17\x9c\xa1\xa1\x97W/" + - "\xf8\xf7!\x07\x00[\xb2\xe7\xad\xc2\x96\xcd\xe7]\x80\x14" + - "\xa3\xfa\x15\x94q\xb2\x08\xe0\x1c\x7f\xf6`\xebg\xa77" + - "\x1c)\xf5N\xcbm9[S\xc7\xe48\xe1Z\xa2\x93" + - "\x0d\x06\xe8\xd4\xae\xfe\xed\xa2\x7f\xdc\xfc\xf7c\xe1|\xa1" + - "\xc4\xebc\xaaD\xf9zW\xfd\x05\xeb<\xd1\xf7J\x18" + - "\xb0H\xba\x9a\x00\xdfv\x01\x7f\xfb\xcf\xfa\x9e\xfc\xec7" + - "\xc2\x80\x01\xe9$\x02\xca\xf7r\xc0\xb9\xfa\x17\xeeoX" + - "x\xe47a\xc0\x90\x1b\xe2\x18\x074t\x0c\xcfO\xe8" + - "\xcb\xde,\xa9*\x9e\x8f\xb3\xd2_Q\x8e\xd6Q>\xb0" + - "\x8er\xfd\xf0\xc7\x8f\xad9\xbc3y\x0a\xa4\x99\xa1T" + - "\x03\xb6\xa8u\x87P\x1e\xe0\xc8B\xdd\x16@\xe7\xf3m" + - "\x0bo\x9f6\xed\xd4[e\xf3|\xb0\xae\x99\xc9\xafq" + - "\xf4\xb1:\xca\xf3\x8337\xe4o^\xdb\xfa\xa7\x124" + - "/\x8c\xfdr\x03\x93\x8f\xca\xbc\xcadZ\xf1\xe7\xad\x9f" + - "\xbf\xb0oa\xfe\xcf\xa5\xae9\xfa\x1d\xf98\xca_\x10" + - "\xba\xe5S\xb9\x11\x01\x9d\xeb\xf2\xcb\xa4K\xba'\xff%" + - "\x9c\x81i\xf5\xdd\x94\x81\xab\xea\xc9\xdf\x9c\xdb\x96\x1d\xbc" + - "9+\x9f\x0e\x03n\xaa\x7f\x9br\xd8\xcf\x01_\x93_" + - "zR\xdf\xf9\xfe\x990`O}3y\x18\xe2\x80\xa3" + - "\xab[\xba~w\xfa\x92\x7f\x82t\x05\x0b\xea\x11\xb0\xe5" + - "D\xfdI\x94\xcf\xd6\xd3\xda\xcf\xd47\x02:\xc3\x1f4" + - ">\xfe\xfa\x99o|\\\xb6@\xce\xd4\xbf\x8d2\x9eO" + - "_\xbf\xa8\xbf\x92\x0a\xe4\xd1\xfe\x87\x7fx\xaeI\xfa\xa4" + - "\xb4\xb6\x05~b\xa641\xb9\x7f\x0a}\xcdM\xe1\xaf" + - "\xfa\xdc\x83\xbb\x7f\xf0\xf2\xbce\x9f\x84\x17:8\x95\x9f" + - "\xbf\xa7\xa6\xd2B\x7f\x89\x87\xce\xbbq\xfd{\xe7\xc2\x80" + - "\xe1\xa9\xfcM\xcer\xc0\xb9\xfd?i\xb9\xfd\xc4\xd3\x9f" + - "\x969\xa05\x0d\x93\x98<\xabA\x84\xd9N\xda\xd0s" + - "\x86>\xcb\x14\xad\xd9i#\x973\xf4\xd9y\xd3\xb0\x8d" + - "\xd9\xae\xfd\xf2\xb4\x9a\xd7\xf3\xadK\xdc\x1f\xdaF-\x9d" + - "\x1a\xd0\xd3K\x0c\xddV\xb3\xbafN\xefRMQ\xcd" + - "YJD\x88\x00D\x10@\xaaY\x0c\xa0\xc4\x04T\x92" + - "\x0c\xb7\x98Z\x7fA\xb3l\xac\x0d\xde\x1e\x10k\x01\xc7" + - "\x146mj\xaa\xad]\xab\xe64+\xaf\xa65kz" + - "\xb7f\x15\xc4>{D\xd8\xab\x01\x94j\x01\x95)\x0c" + - "\x1dS\xb3\xf2\x86ni\x00\x80\xb5\x01\xd7\xfe/Bw" + - "\xa9\xa6*T\xf2\xc2~;\x18G\xd4%%Q\xbb\xc9" + - "\xab`\xd9]\x88J\xad\x1fX\xa5W^#\xa0\xd2\xc7" + - "PBL\"\x19\xb3\xab\x00\x94^\x01\x95\xbb\x18J\x8c" + - "%\x91\x01H[\xd7\x02(\xb7\x0b\xa8<\xc4P\x12\x84" + - "$\x0a\x00\xd2 \x19\x1f\x10Py\x86\xa1\x93\xd3l5" + - "\xa3\xda*e\xac\x06\x18\xd6\x00:z1>\x08\x9a\x85" + - "\x93\x01\xbb\x04\xc4D@\x1b\x80dt\x0a\xd9\xcc5j" + - ">\x9f\x05Q\xef\xf1a\xd5\xc0\xf8\xc3\x9e/{8\xb1" + - "\x8cXyC\xd4-\x8dR\x12\xda\x8bU\xc5*\x98\xc1" + - "\xca\xaf\xbf6\xe8\x82\xc5\xf5\x8fe\x15\xa6f\xe45}" + - "\x85\xd1\x13\x1c\x80n\xad\xd1*T\\\x8a~\xa3.)" + - "\x8a\xaa\x0a\x82w{\xc1\xe9\xdd\x13\x86\xfb\xeec\xf2\xe0" + - "\xa7/\xecA\x89\xf9\x0b\xbf\xb4\x19@\x99.\xa02\x87" + - "\xa1WO\xb3\xc86C@e>\xc3\x84=\x90\xd7J" + - "J \x01\x98\xc8\xabv/\xdf\xd5\xea1\xe6S\xb5m" + - "5\xdd;\x82M\xd4\x1cVp\xb8\xfc\xb61\x8e\x8ck\x01R\xcf\x90\xfdM\xb2O\x8a%q" + - "\x12\x80|\x82\xaf\xff\x0d\xb2\xff\x1eG\xd0\xaa\xb3\xb6\xa0" + - "g\xfa\xb4.\x15\x84\x90&\xb053\x97\xd5\xd5>\xa2" + - "\xd3b\xe7k\xb4\xecLV\xf7\xfb\xa0\xb61kw\xa9" + - "v/\xe0(\x99h\x18\xb9Nz\x0a\x09\xd5\xee\x1d\xf5" + - "\xb4\xcfc\x13\xc1\x0c\x89\xbc\xd0\xdc\xe6\x8a\xbc>M\xd5" + - "\x0b\xf9% \xe42\xa34j\x9f\xb1V\xed\xeb0A" + - "('Qs9U\xcft\x80h\x8e~\xf8eMb" + - "<\xea\x87\xa6\x9a>\xa1R)\xe9\xb3~\x89\x04\x8aU" + - "\x10\xd9\x0a7\xdd\x12\xf9e\x01|\xb5\xfe\xf2{\xc98" + - "\xf4W\x91\x0f*\x17{~\x1f\x1d\xf7\xfc6\xde\x1c\xfb" + - "\xddxbr\xbd\xbf jV\xa9Rl\x08\x94\xa2T" + - "N*\x86\x0f\xd5DU\"o/\x09\xa2@Z\x84+" + - "\x96\xaeh\xa2\xb7\x92f\xd1\x07\x93.\xa6\x0fA\x9a\xd6" + - "\x0c\x80\x11\xa9\xbe\x09@\xcc\xe6\xd3\xa2\xae\xd9b>\x9b" + - "I\x14,\xcd\x14\x0b\xb65\xa6\xdc\x97\xe9\xe1\xa1\xb1\xa3" + - "\xfc\x1c\xeb\x8f\xb1a)\xe7\xe9\xbb\\kq\xb6\xb5\x89" + - "3/r\xf5]?\xfd;/\xa0\xf2\x1d\xe6\xd2\xc8\x12" + - "#\xc3\xb7/\x02\x0c#\x80m\x96\x9d1\x0a\xb6\x977" + - "\xfa\xa9\x99\xa6\x9fF;\x9b\xd32\xdf,\xd8!j\x9a" + - "X{\xa0\xfa\x11F\xcd\xa5\xebC5\x96.\x82!a" + - "ve3\x18\x03\x86\xb1q\xce0E\x09\xc3w\xd5\x0f" + - "\xb6\x99Jkc\xf1\x0a\xc0K\xe8\xd6U\xc5+\x80\xef" + - "\x87\xee\x05\xb6\x9b\x00\xca\xdd\x02*\xbb\x19b\xf1Z`" + - "\xe7.\x00e\xb7\x80\xca>j@\x02o@\xd2^J" + - "\xf2C\x02*\x8f\x8d,L\xcbH\xdf\xa2\xd9%l\xcf" + - "[\xbafY\xd0\x985\xf4\xe5!\xb0m\xe4;\xd6\xd9" + - "\x1a\x9a)\xe2\xfeN\x03\xd7U4\x07M\x80\xd1\xf8q" + - "\xb7\xb1\xc2\xe3\xee\x8b\xe2\x09\xb0\xda\xd8\x08\xc6\x9f\x0a\xc6" + - "Ame.#\xba\xd4\x84Y\xd1m\x9c?%\x8c\xe3" + - "M=\xfdn^\xber \x8fn\xa1\xf3j\x8a\x9e\x04" + - "\xf0\x8b\x9b\x99\xdd\x05\x9d\x0e\xd7r\xdd\xd6\xccu\\\x01" + - "\x8fk\xcc\x0e\x9d\xa7\x19\xbe\xde\x8asA\x14#\x01\x92" + - "D\x9f5d\x09\x17\x03\xa4\xaa\xc9<\x05\x03\xe2\x90\xeb" + - "\xb1\x09 UK\xf6\x0b\xb9\xdeb\xae\xde\x9a\x8a\xad\x00" + - "\xa9$\xd9/\xc2\xa0\xdc\xe5i\xdc\xfd\x85d\x9f\xc1\xf5" + - "V\xc4\xd5[\x17s| \xf3\xaa\xa2\xae\xde\xba\x94\xeb" + - "$_\xe6Ib\x95\xab\xb7\xe6r}\x16\xe8\xbc\x98\xe8" + - "\xea\xad\xab\xb8\xff\x05d_\xca\xf5V\xcc\xd5[\x1d\\" + - "W\xb5\x93}\x052t\xf2\xa6\x91\xd6,k9\xa0O" + - "\x13\x9e\x9c\xf6N\x95h\xab=\xde\xf76\xcaj\xd6\x0e" + - "i\xb1l_f\xa9j\x03j>\xc4V\xcd\x1e-\x80" + - "\x98\x05\xcb\xa6T\x83\x18\xf2\xe9\xa4U\xb3\xc7\xb8^3" + - "!a\x8d2\xaf4\xb5\x90\xbf\x09\x8b\xa0\xb2\x83\x9e\xd7" + - ".\xcbsZyJ+\xf6\x88\xedT\xf0w\x15\xe9K" + - "hw9-L_\x9e\xa8\x96\x0e\x90\xf1\x11\x01\x95'" + - "Gr\x1a\x15\xadQ\xb0S hi\xefJcKQ" + - "\x08\x96J\xc02\xc2v\xc2\x19)\x95,\x15\xeb$\xff" + - "Va\x1cd2\xfaf\xbf[\xb3\x12\x95_l\xfa\xb7" + - "\x13\xe3\x88]r\x05\xe4\xb9\xedB\xfco\x00\x00\x00\xff" + - "\xff\x0d\x13v\xb5" +const schema_ffaaf7385bc4adad = "x\xda\xacY\x7fpT\xd5\xf5?\xe7\xde\xdd\xbc\x0d\xbf" + + "\x96\x97\xb7D\xbe\x19\xf9\xc62\xe0@\x10A\x82\x15\x19" + + "\x98$@\x86B\xd1\xe6eA; \x0e/\xbb\x97d" + + "1\xbboy\xef\xad\x10,\xc5\xa8L\x05[[(\x8c" + + "\xc6\x91\x19\xa8\xe8\x08\x95jk\xb1B\xad\x9d\xfact" + + "PZ\xc3\xb4\xb4:\xb5\xad\xa5Tp\xfcQF\x99\xa2" + + "\xe3\xf4u\xce{\xfb~d\xb3\xa3\x9b\xa4\x7fe\xf7\xbc" + + "\xcf\x9es\xef\xb9\xe7~\xce\xe7\xbc\xcc\x9e\x18k\x8e\\" + + "3\xf6\xd7c\x80\xa9{\xa2U\xb6\xf9N\x8f\xf1\xd8\xbe" + + "\xa5w\x83<\x03\x01\xa2(\x014\xb6VMf\x80\xca" + + "\xda\xaa&@\xfb\xe2\xc1W\x17>\xb0\xeb\xa3\x9da\xc0" + + "V\x17\xb0\xd7\x01\xfcy^\xc3\xfa\xfd|\xc5}a\xc0" + + "\xb1\xaa:\x02\xf4;\x80o\xaf\x8b\xef\xf9a\xed\x1a\x07" + + "`_h\\\xffv\xdf\xb9\xeb~\x01\xd1*\x02^\xa8" + + "\xaac\x8a,\xd1\xc7\xb1\xd2\xcd\x08h\xbf\xf6\xff[n" + + "\x8e?\xf6\x9d\x07K\xd0\x8e\xdb}\xb1\xc9L\xf9UL" + + "\x02P\x8e\xc5\xc8\xf5\xf6\xf37>\xb3\xea\xee\x8f\xf6\x87" + + "c\x7f\x10\x9bC\xb1\xa3\xd5\x04\xe8[s\xee\xb6\xd6e" + + "\xf1\x1f\x0d\xf4\x16!\xdc\xf4\xea\xf7Pi\xad\x96\x80\xdb" + + "\xb5\x9f\x9d9x:9\xef\x10\xa83pP\xd0\xaf\x10" + + "na5\x05\xbd\xbez\x13\xa0=\xe5\xc9\x17\xfbw." + + "\x98u8\x1ctou\x0d\x05=\xe2\x04\xdd\xb4\xee\xd5" + + "'\xb7\xa8g\x9f(\x13\xf4d\xf5)T\xce;A\x95" + + "%\xb3\x8f\x9dnl8R6\xe8\xcb\x84{\xc7\x09\xfa" + + "\xb6\x13t\xee\x81\xa7\x9f\xb9\xff\xc3\xcd?!4+E" + + "/\x1cu\x18\x95U\xa3.\x03P\xd6\x8ez\x12\xd0\xbe" + + "\xa3\xff\xbd\xc7\xef\xbf\xaf\xe5h\xa9oF\xe8\x8b\xa3\x18" + + "S\xe4\xd1\xe4{\xec\xe8w!\xf4\\\x9e\xc2\xed#G" + + "^Z3\xef\xdf\x87m\x00l<;z56~:" + + "\xfa2\xa4\x18c_Ae\xeb8\x09\xc0>\xf1\xcc\xa1" + + "\xf9\x9f\x9d\xd9t\xbc\xd4;-\xb7Q\x8c\xaba\xcav" + + "\xc25\xf6\x8e\xd3\x19\xa0=~\xcd\xef\x16\xbe\x7f\xeb?" + + "_\x1ePA\xb2S {e\xca\xd7\xbb\xda/Y\xeb" + + "\xc9\xeeW\x06T\x90\xbc\x9c\x00\x7fp\x01\xff\xf8\xcf\x86" + + "\xce\xfc\xac\xd7\xc3\x80\x8b\xf2)\x04T\xaak\x08pi" + + "\xc2\xf3\x0f\xd4-8\xfe\xdb0`z\x8d\x13\xa2\xc5\x01" + + "\xd4\xb5\xf4\xcf\x8d\xe7\x96\xbeQRUN>D\xcd\xdf" + + "Q\xe9\xad\xa1|l\xad\xa1\\?\xf2\xf1\xe3\xeb\x8e\xee" + + "J\x9c\x06yF(\xd5\x80\x8do\xd7\x1cF\xe5\xa2\x83" + + "\xbcP\xb3\x0d\xd0\xfe|\xfb\x82;'M:\xfdf\xd9" + + "\x9f\x01)\xd7\xe9\xc3\xc6\x00s" + + "\x1ev~\xd1\xc3\x0e\xcd\x14m\x9a\xd5E1\xc96\x06" + + "\xb0>\xaf\xa7\x97\xa5\xbdo#\xcc\xae\x99\xd7\xa5\x9c)" + + "(\xbd\xa1s]]\xac\xa8i\xac\xfc\x0e\xc7\x07\x1d\xb5" + + "\xb8\xc3\xa1\xac\xc2\x10z^\xe4V\xe8\x9d\xc1ej\x17" + + "\xf5f\xa1\xe2\xb2\xf6\x9b~I\x81UU\x10\xbc\xdd\x0b" + + "N{\x8f\xeb\xee\xde\x87\xe4\xc1O_\xd8\x83\x1a\xf3\x17" + + ">\xbd\x01@\x9d\xc2Q\x9d\xcd\xd0\xab\xcd\x99d\x9b\xc6" + + "Q\x9d\xcb0n\xf5\xe4EI\x91\xc4\x01\xe3y\xcd\xea" + + "\x1a\xd6\xa9j\x96\xa5\xa5\xba\x060\x93\x96\xc5\x0a.\xaa" + + "\xdf\x82\x86\x91\xc7\x16'h\xbb{.8\xe4$\xde$" + + "\x0c3\xa3\xe7\x9c\xebm\xa2U\x92\xc1Ee2H\xe5" + + "p\x15Gu\x1e\xc3m\xb7\x0b\xa3C7\x05\"0\xa4" + + "\x8eQ\xeefz\xab\x89V\xb0\x9a\x15z\xe7\x12#\x9e" + + "\xb9]\x18j\x04\xc3\x8d\x10\x1b\xe2+{\xf2B\x1d\xe3" + + "\xaf\xad\x95N\xb2\x99\xa3\xba\"X\xdb2\xb2-\xe1\xa8" + + "\xb6\x11\xf3\xa0\xcb<7\xd0&\xbe\xc6Q]\x19\x1c\xb9" + + "\xef\xb8\xcc\x91o\xcbj\x9b\x93\x99-\x02\xab\x81a\xf5" + + "\x10K )\xac\x9b3\xb9\xb4\xbe\x89<\xb8I\xb5\x80" + + "\x92:\xde_\xb8V\x07\xa0\xde\xc2Q\xed\x0a\x16.\x88" + + "\xde\xd6qT\xbbC\x0b\xcf\xcc\x07P\xd3\x1c\xd5 !\xf7\xc7D\xf4\xe6\x1fy/\xfd" + + "\xee{\x12F|\xad\x8d\xde\x00+\xf7>\x04L\xde*" + + "a\xd4\x9f\x86\xd0\x93\xf4\xf2\xc6\xe3\xc0\xe4\xac\x84U\xfe" + + "\xac\x8d\xdeT.k;\x81\xc9k%\xa2<*\x8cf" + + "\xb4S\xc5\xd3\xc6\xe2\xb9A3\xda\x9e\x93\x8e\x17LaH\x05\xcb\x1cR\xee" + + "\xcb\xf4\xf0\xd0\xd8\x11\xd2w\xcb\x03-\xe7eb\x80\x94" + + "\xf3\xf4]\x96\x8c]\x1cU\x8b8\xf3\x0aW\xdfm\xa4" + + "_\xe79\xaa\xdfb.\x8d,\xd6\xd3\xce\xf1E\x80a" + + "\x04\xb0\xc9\xb4\xd2z\xc1\xf2\xf2F_\x85a\xf8i\xb4" + + "2Y\x91\xfeF\xc1\x0aQ\xd3\xc8\xda\x03\xd5\x0f\x1f4" + + "\x97n\x08\xd5X\xaa\x08\x86\xb8\xd1\x96Ic\x0c\x18\xc6" + + "\x869\xc3\x14%\x8cs\xaa~\xb0\xadTZ\x9b9\xaa" + + "\xf7\x84J\xab\x97&\xe3;9\xaa\xdf\x0d\xbdc\xd8a" + + "\x00\xa8\xf7rT\xf70\xc4\xe2+\x86]\xbb\x01\xd4=" + + "\x1c\xd5\xfd\xd4\x80\xb8\xfb\x8aa\x1f%\xf9a\x8e\xea\xe3" + + "\x03\x0b\xd3\xd4S\xb7\x09\xab\x84\xed\x9d\x96.L\x13\xea" + + "3z.4\xef\x9b\x96\x9eoYo\x094\x92\xc4\xfd" + + "\xad:\xae\xafh\x0e\x1a\x01\xa39\xd7\xdd\xc2\x0a\xaf\xbb" + + "/\x8aG\xc0jC#\x18\x7f*\x18\x06\xb5\x95y\x19" + + "\xd1\xa6\xc5\x8d\x8a\xde\xec\xf9S\xc20v\xea\xe9w\xe3" + + "\xea\x95=yt\x0b\xdd\xa9\xa6\xe8)\x00\xbf\xb8\x99\xd1" + + "^\xc8\xd1\xe5Z\x96\xb3\x84\xb1\xdeQ\xc0\xc3\x1a\xb3C" + + "\xf7i\x9a\xaf\xb7\xaa\x1dA\x14#\x01\x92@\x9f5\x14" + + "\x19\x17\x01$\xc7\x90y\"\x06\xc4\xa1L\xc0\xc9\x00\xc9" + + "\xf1d\xbf\xdc\xd1[\xcc\xd5[\xff\x87\xf3\x01\x92\x09\xb2" + + "_\x81A\xb9+\x93\x1c\xf7\x97\x93}\x9a\xa3\xb7\"\xae" + + "\xde\x9a\xea\xe0\x03\x99W\x15u\xf5\xd6tG'\xf92" + + "O\x96\xaa\\\xbdu\x8d\xa3\xcf\x02\x9d\x17\x93\\\xbdu" + + "\xbd\xe3\x7f\x1e\xd9\x978z+\xe6\xea\xad\x16GW5" + + "\x93}\x052\xb4\xf3\x86\x9e\x12\xa6\xb9\x0c\xd0\xa7\x09O" + + "N{\xb7J\xb2\xb4N\xefs\x13e5c\x85\xb4X" + + "\xa6;\xbdD\xb3\x00\x85\x0f\xb14\xa3S\x04\x10\xa3`" + + "Z\x94j\x90B>\xed\x94ft\xea7\x09\x03\xe2\xe6" + + " \xf3JC\x84\xfc\x8dX\x04\x95\x1d\xf4\xbcvY\x9e" + + "\xd3\xcaSZ\xb1G\xec\xa0\x82\xbf\xa7H_\xbc\xd9\xe5" + + "\xb40}y\xa2Z~\x94\x8c\x079\xaaO\x0d\xe44" + + "*Z\xbd`%\x81\x8b\x94\xf7Jc[Q\x08\x96J" + + "\xc02\xc2v\xc4\x19)\x95,\x15\xeb$\xff\xad\xc20" + + "\xc8d\xf0\x7f\x09\xda\x85\x19\xaf\xfc\xc5\xa6\xffvb\x18" + + "\xb1K^\x01yn\xdb\x10\xff\x1b\x00\x00\xff\xffn\x1e" + + "\x85n" func init() { schemas.Register(schema_ffaaf7385bc4adad, diff --git a/pkg/client/client.go b/pkg/client/client.go index 98f4c21a5c..4406614652 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1026,6 +1026,13 @@ type CreateaNamespacesConfig struct { // IDMappings are the user and group ID mappings when unsharing the user // namespace. IDMappings *idtools.IDMappings + + // BasePath is the root path for storing the namespaces. + // Defaults to "/var/run" if unset. + BasePath string + + // PodID is the unique identifier of the pod. + PodID string } // CreateaNamespacesResponse is the response of the CreateNamespaces method. @@ -1042,7 +1049,16 @@ type NamespacesResponse struct { Path string } -// CreateNamespaces can be used to create a new set of namespaces. +// CreateNamespaces can be used to create a new set of unshared namespaces by +// bind mounting it to the local filesystem. +// +// If a namespace is not selected by the CreateaNamespacesConfig, then the +// server will fallback to the host namespace and still create the bind mount +// to it. All namespaces are mounted to /var/run/[ipc,pid,net,user,uts]ns/$POD_ID, +// whereas the POD_ID is being used from the CreateaNamespacesConfig as well. +// +// UID and GID mappings are required if unsharing of the user namespace is +// requested. func (c *ConmonClient) CreateNamespaces( ctx context.Context, cfg *CreateaNamespacesConfig, ) (*CreateaNamespacesResponse, error) { @@ -1127,6 +1143,18 @@ func (c *ConmonClient) CreateNamespaces( return fmt.Errorf("set namespaces: %w", err) } + if cfg.BasePath == "" { + cfg.BasePath = "/var/run" + } + + if err := req.SetBasePath(cfg.BasePath); err != nil { + return fmt.Errorf("set base path: %w", err) + } + + if err := req.SetPodId(cfg.PodID); err != nil { + return fmt.Errorf("set base path: %w", err) + } + return nil }) defer free() diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 428249948f..bdec3ee10e 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -18,6 +18,7 @@ import ( "github.com/containers/conmon-rs/pkg/client" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/unshare" + "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/opencontainers/runtime-tools/generate" @@ -526,22 +527,42 @@ var _ = Describe("ConmonClient", func() { tr.createRuntimeConfig(false) sut = tr.configGivenEnv() + podID := uuid.New().String() + response, err := sut.CreateNamespaces( context.Background(), - &client.CreateaNamespacesConfig{}, + &client.CreateaNamespacesConfig{ + PodID: podID, + }, ) Expect(err).To(BeNil()) Expect(response).NotTo(BeNil()) }) + It("should fail without pod ID", func() { + tr = newTestRunner() + tr.createRuntimeConfig(false) + sut = tr.configGivenEnv() + + response, err := sut.CreateNamespaces( + context.Background(), + &client.CreateaNamespacesConfig{}, + ) + Expect(err).NotTo(BeNil()) + Expect(response).To(BeNil()) + }) + It("should succeed without user namespace", func() { tr = newTestRunner() tr.createRuntimeConfig(false) sut = tr.configGivenEnv() + podID := uuid.New().String() + response, err := sut.CreateNamespaces( context.Background(), &client.CreateaNamespacesConfig{ + PodID: podID, Namespaces: []client.Namespace{ client.NamespaceIPC, client.NamespaceNet, @@ -560,20 +581,41 @@ var _ = Describe("ConmonClient", func() { Expect(response.Namespaces[3].Type).To(Equal(client.NamespaceUser)) Expect(response.Namespaces[4].Type).To(Equal(client.NamespaceUTS)) - for _, ns := range response.Namespaces { + for i, ns := range response.Namespaces { stat, err := os.Lstat(ns.Path) Expect(err).To(BeNil()) Expect(stat.IsDir()).To(BeFalse()) Expect(stat.Size()).To(BeZero()) Expect(stat.Mode()).To(Equal(fs.FileMode(0o444))) + + Expect(filepath.Base(ns.Path)).To(Equal(podID)) + Expect(err).To(BeNil()) + + const basePath = "/var/run/" + switch i { + case 0: + Expect(ns.Path).To(ContainSubstring(basePath + "ipcns/")) + case 1: + Expect(ns.Path).To(ContainSubstring(basePath + "pidns/")) + case 2: + Expect(ns.Path).To(ContainSubstring(basePath + "netns/")) + case 3: + Expect(ns.Path).To(ContainSubstring(basePath + "userns/")) + case 4: + Expect(ns.Path).To(ContainSubstring(basePath + "utsns/")) + } } }) - It("should succeed with user namespace", func() { + It("should succeed with user namespace and custom base path", func() { tr = newTestRunner() tr.createRuntimeConfig(false) sut = tr.configGivenEnv() + basePath := MustTempDir("ns-test-") + defer os.RemoveAll(basePath) + podID := uuid.New().String() + uids := []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}} gids := []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}} @@ -588,6 +630,8 @@ var _ = Describe("ConmonClient", func() { client.NamespaceUser, }, IDMappings: idtools.NewIDMappingsFromMaps(uids, gids), + BasePath: basePath, + PodID: podID, }, ) Expect(err).To(BeNil())