1#[cfg(doc)]
6use std::path::Path;
7use std::{
8 fmt::Display,
9 path::PathBuf,
10 process::{Command, Output},
11};
12
13use log::debug;
14
15use crate::{
16 Error,
17 RootlessBackend,
18 RootlessOptions,
19 utils::{detect_virt, get_command},
20};
21
22const ARG_DEBUG: &str = "--debug";
24const ARG_COPY_UP: &str = "--copy-up";
26const ARG_COPY_UP_MODE: &str = "--copy-up-mode";
28const ARG_PROPAGATION: &str = "--propagation";
30const ARG_NET: &str = "--net";
32const ARG_MTU: &str = "--mtu";
34const ARG_CIDR: &str = "--cidr";
36const ARG_IFNAME: &str = "--ifname";
38const ARG_DISABLE_HOST_LOOPBACK: &str = "--disable-host-loopback";
40const ARG_IPV6: &str = "--ipv6";
42const ARG_DETACH_NETNS: &str = "--detach-netns";
44const ARG_LXC_USER_NIC_BINARY: &str = "--lxc-user-nic-binary";
46const ARG_LXC_USER_NIC_BRIDGE: &str = "--lxc-user-nic-bridge";
48const ARG_PASTA_BINARY: &str = "--pasta-binary";
50const ARG_SLIRP4NETNS_BINARY: &str = "--slirp4netns-binary";
52const ARG_SLIRP4NETNS_SANDBOX: &str = "--slirp4netns-sandbox";
54const ARG_SLIRP4NETNS_SECCOMP: &str = "--slirp4netns-seccomp";
56const ARG_VPNKIT_BINARY: &str = "--vpnkit-binary";
58const ARG_PORT_DRIVER: &str = "--port-driver";
60const ARG_PUBLISH: &str = "--publish";
62const ARG_PIDNS: &str = "--pidns";
64const ARG_CGROUPNS: &str = "--cgroupns";
66const ARG_UTSNS: &str = "--utsns";
68const ARG_IPCNS: &str = "--ipcns";
70const ARG_REAPER: &str = "--reaper";
72const ARG_EVACUATE_CGROUP2: &str = "--evacuate-cgroup2";
74const ARG_STATE_DIR: &str = "--state-dir";
76const ARG_SUBID_SOURCE: &str = "--subid-source";
78
79#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
85#[strum(serialize_all = "lowercase")]
86pub enum CopyUpMode {
87 #[default]
89 #[strum(serialize = "tmpfs+symlink")]
90 TmpfsAndSymlink,
91}
92
93#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
99#[strum(serialize_all = "lowercase")]
100pub enum Propagation {
101 #[default]
103 Rprivate,
104
105 Rslave,
107}
108
109#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
115#[strum(serialize_all = "lowercase")]
116pub enum Net {
117 #[default]
119 Host,
120
121 None,
123
124 Pasta,
126
127 Slirp4netns,
129
130 Vpnkit,
132
133 #[strum(serialize = "lxc-user-nic")]
135 LxcUserNic,
136}
137
138#[derive(Clone, Copy, Debug, strum::Display, Eq, PartialEq)]
140#[strum(serialize_all = "lowercase")]
141pub enum AutoOption {
142 True,
144
145 False,
147
148 Auto,
150}
151
152#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
156#[strum(serialize_all = "lowercase")]
157pub enum PortDriver {
158 #[default]
160 None,
161
162 Implicit,
164
165 Builtin,
167
168 Slirp4netns,
170}
171
172#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
176#[strum(serialize_all = "lowercase")]
177pub enum SubIdSource {
178 #[default]
180 Auto,
181
182 Dynamic,
186
187 Static,
189}
190
191#[derive(Clone, Debug, Default, Eq, PartialEq)]
195pub struct RootlesskitOptions {
196 pub debug: bool,
200
201 pub copy_up: Vec<String>,
205
206 pub copy_up_mode: Option<CopyUpMode>,
210
211 pub propagation: Option<Propagation>,
215
216 pub net: Option<Net>,
220
221 pub mtu: Option<usize>,
227
228 pub cidr: Option<String>,
234
235 pub ifname: Option<String>,
242
243 pub disable_host_loopback: bool,
247
248 pub ipv6: bool,
254
255 pub detach_netns: bool,
259
260 pub lxc_user_nic_binary: Option<PathBuf>,
264
265 pub lxc_user_nic_bridge: Option<String>,
269
270 pub pasta_binary: Option<PathBuf>,
274
275 pub slirp4netns_binary: Option<PathBuf>,
279
280 pub slirp4netns_sandbox: Option<AutoOption>,
284
285 pub slirp4netns_seccomp: Option<AutoOption>,
289
290 pub vpnkit_binary: Option<PathBuf>,
294
295 pub port_driver: Option<PortDriver>,
299
300 pub publish: Vec<String>,
304
305 pub pidns: bool,
309
310 pub cgroupns: bool,
314
315 pub utsns: bool,
319
320 pub ipcns: bool,
324
325 pub reaper: Option<AutoOption>,
331
332 pub evacuate_cgroup2: Option<String>,
339
340 pub state_dir: Option<PathBuf>,
344
345 pub subid_source: Option<SubIdSource>,
349}
350
351impl Display for RootlesskitOptions {
352 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353 write!(f, "{}", self.to_vec().join(" "))
354 }
355}
356
357impl RootlessOptions for RootlesskitOptions {
358 fn to_vec(&self) -> Vec<String> {
364 let mut options = Vec::new();
365 if self.debug {
366 options.push(ARG_DEBUG.to_string());
367 }
368 for option in self.copy_up.iter() {
369 options.push(ARG_COPY_UP.to_string());
370 options.push(option.to_string());
371 }
372 if let Some(option) = self.copy_up_mode {
373 options.push(ARG_COPY_UP_MODE.to_string());
374 options.push(option.to_string());
375 }
376 if let Some(option) = self.propagation {
377 options.push(ARG_PROPAGATION.to_string());
378 options.push(option.to_string());
379 }
380 if let Some(option) = self.net {
381 options.push(ARG_NET.to_string());
382 options.push(option.to_string());
383 }
384 if let Some(option) = self.mtu {
385 options.push(ARG_MTU.to_string());
386 options.push(option.to_string());
387 }
388 if let Some(option) = self.cidr.as_ref() {
389 options.push(ARG_CIDR.to_string());
390 options.push(option.to_string());
391 }
392 if let Some(option) = self.ifname.as_ref() {
393 options.push(ARG_IFNAME.to_string());
394 options.push(option.to_string());
395 }
396 if self.disable_host_loopback {
397 options.push(ARG_DISABLE_HOST_LOOPBACK.to_string());
398 }
399 if self.ipv6 {
400 options.push(ARG_IPV6.to_string());
401 }
402 if self.detach_netns {
403 options.push(ARG_DETACH_NETNS.to_string());
404 }
405 if let Some(option) = self.lxc_user_nic_binary.as_ref() {
406 options.push(ARG_LXC_USER_NIC_BINARY.to_string());
407 options.push(option.to_string_lossy().to_string());
408 }
409 if let Some(option) = self.lxc_user_nic_bridge.as_ref() {
410 options.push(ARG_LXC_USER_NIC_BRIDGE.to_string());
411 options.push(option.to_string());
412 }
413 if let Some(option) = self.pasta_binary.as_ref() {
414 options.push(ARG_PASTA_BINARY.to_string());
415 options.push(option.to_string_lossy().to_string());
416 }
417 if let Some(option) = self.slirp4netns_binary.as_ref() {
418 options.push(ARG_SLIRP4NETNS_BINARY.to_string());
419 options.push(option.to_string_lossy().to_string());
420 }
421 if let Some(option) = self.slirp4netns_sandbox {
422 options.push(ARG_SLIRP4NETNS_SANDBOX.to_string());
423 options.push(option.to_string());
424 }
425 if let Some(option) = self.slirp4netns_seccomp {
426 options.push(ARG_SLIRP4NETNS_SECCOMP.to_string());
427 options.push(option.to_string());
428 }
429 if let Some(option) = self.vpnkit_binary.as_ref() {
430 options.push(ARG_VPNKIT_BINARY.to_string());
431 options.push(option.to_string_lossy().to_string());
432 }
433 if let Some(option) = self.port_driver {
434 options.push(ARG_PORT_DRIVER.to_string());
435 options.push(option.to_string());
436 }
437 for option in self.publish.iter() {
438 options.push(ARG_PUBLISH.to_string());
439 options.push(option.to_string());
440 }
441 if self.pidns {
442 options.push(ARG_PIDNS.to_string());
443 }
444 if self.cgroupns {
445 options.push(ARG_CGROUPNS.to_string());
446 }
447 if self.utsns {
448 options.push(ARG_UTSNS.to_string());
449 }
450 if self.ipcns {
451 options.push(ARG_IPCNS.to_string());
452 }
453 if let Some(option) = self.reaper {
454 options.push(ARG_REAPER.to_string());
455 options.push(option.to_string());
456 }
457 if let Some(option) = self.evacuate_cgroup2.as_ref() {
458 options.push(ARG_EVACUATE_CGROUP2.to_string());
459 options.push(option.to_string());
460 }
461 if let Some(option) = self.state_dir.as_ref() {
462 options.push(ARG_STATE_DIR.to_string());
463 options.push(option.to_string_lossy().to_string());
464 }
465 if let Some(option) = self.subid_source {
466 options.push(ARG_SUBID_SOURCE.to_string());
467 options.push(option.to_string());
468 }
469
470 options
471 }
472}
473
474#[derive(Clone, Debug)]
478pub struct RootlesskitBackend(RootlesskitOptions);
479
480impl RootlessBackend<RootlesskitOptions> for RootlesskitBackend {
481 type Err = Error;
482
483 fn new(options: RootlesskitOptions) -> Self {
485 debug!("Create a new rootlesskit backend with options \"{options}\"");
486 Self(options)
487 }
488
489 fn options(&self) -> &RootlesskitOptions {
491 &self.0
492 }
493
494 fn run(&self, cmd: &[&str]) -> Result<Output, Self::Err> {
505 {
506 let virt = detect_virt()?;
507 if virt.uses_namespaces() {
508 return Err(Error::NamespacesInContainer { runtime: virt });
509 }
510 }
511
512 let command_name = get_command("rootlesskit")?;
513 let mut command = Command::new(command_name);
514
515 if self.0.debug {
517 command.arg(ARG_DEBUG);
518 }
519 for option in self.0.copy_up.iter() {
520 command.arg(ARG_COPY_UP);
521 command.arg(option);
522 }
523 if let Some(option) = self.0.copy_up_mode {
524 command.arg(ARG_COPY_UP_MODE);
525 command.arg(option.to_string());
526 }
527 if let Some(option) = self.0.propagation {
528 command.arg(ARG_PROPAGATION);
529 command.arg(option.to_string());
530 }
531 if let Some(option) = self.0.net {
532 command.arg(ARG_NET);
533 command.arg(option.to_string());
534 }
535 if let Some(option) = self.0.mtu {
536 command.arg(ARG_MTU);
537 command.arg(option.to_string());
538 }
539 if let Some(option) = self.0.cidr.as_ref() {
540 command.arg(ARG_CIDR);
541 command.arg(option);
542 }
543 if let Some(option) = self.0.ifname.as_ref() {
544 command.arg(ARG_IFNAME);
545 command.arg(option);
546 }
547 if self.0.disable_host_loopback {
548 command.arg(ARG_DISABLE_HOST_LOOPBACK);
549 }
550 if self.0.ipv6 {
551 command.arg(ARG_IPV6);
552 }
553 if self.0.detach_netns {
554 command.arg(ARG_DETACH_NETNS);
555 }
556 if let Some(option) = self.0.lxc_user_nic_binary.as_ref() {
557 command.arg(ARG_LXC_USER_NIC_BINARY);
558 command.arg(option);
559 }
560 if let Some(option) = self.0.lxc_user_nic_bridge.as_ref() {
561 command.arg(ARG_LXC_USER_NIC_BRIDGE);
562 command.arg(option);
563 }
564 if let Some(option) = self.0.pasta_binary.as_ref() {
565 command.arg(ARG_PASTA_BINARY);
566 command.arg(option);
567 }
568 if let Some(option) = self.0.slirp4netns_binary.as_ref() {
569 command.arg(ARG_SLIRP4NETNS_BINARY);
570 command.arg(option);
571 }
572 if let Some(option) = self.0.slirp4netns_sandbox {
573 command.arg(ARG_SLIRP4NETNS_SANDBOX);
574 command.arg(option.to_string());
575 }
576 if let Some(option) = self.0.slirp4netns_seccomp {
577 command.arg(ARG_SLIRP4NETNS_SECCOMP);
578 command.arg(option.to_string());
579 }
580 if let Some(option) = self.0.vpnkit_binary.as_ref() {
581 command.arg(ARG_VPNKIT_BINARY);
582 command.arg(option);
583 }
584 if let Some(option) = self.0.port_driver {
585 command.arg(ARG_PORT_DRIVER);
586 command.arg(option.to_string());
587 }
588 for option in self.0.publish.iter() {
589 command.arg(ARG_PUBLISH);
590 command.arg(option);
591 }
592 if self.0.pidns {
593 command.arg(ARG_PIDNS);
594 }
595 if self.0.cgroupns {
596 command.arg(ARG_CGROUPNS);
597 }
598 if self.0.utsns {
599 command.arg(ARG_UTSNS);
600 }
601 if self.0.ipcns {
602 command.arg(ARG_IPCNS);
603 }
604 if let Some(option) = self.0.reaper {
605 command.arg(ARG_REAPER);
606 command.arg(option.to_string());
607 }
608 if let Some(option) = self.0.evacuate_cgroup2.as_ref() {
609 command.arg(ARG_EVACUATE_CGROUP2);
610 command.arg(option);
611 }
612 if let Some(option) = self.0.state_dir.as_ref() {
613 command.arg(ARG_STATE_DIR);
614 command.arg(option);
615 }
616 if let Some(option) = self.0.subid_source {
617 command.arg(ARG_SUBID_SOURCE);
618 command.arg(option.to_string());
619 }
620
621 for command_component in cmd.iter() {
623 command.arg(command_component);
624 }
625
626 debug!("Run rootless command: {command:?}");
627
628 command
629 .output()
630 .map_err(|source| crate::Error::CommandExec {
631 command: format!("{command:?}"),
632 source,
633 })
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use rstest::rstest;
640
641 use super::*;
642
643 #[rstest]
645 #[case::all_options(
646 RootlesskitOptions{
647 debug: true,
648 copy_up: vec!["/etc".to_string(), "/usr".to_string()],
649 copy_up_mode: Some(CopyUpMode::default()),
650 propagation: Some(Propagation::default()),
651 net: Some(Net::Pasta),
652 mtu: Some(1500),
653 cidr: Some("10.0.1.0/24".to_string()),
654 ifname: Some("tap1".to_string()),
655 disable_host_loopback: true,
656 ipv6: true,
657 detach_netns: true,
658 lxc_user_nic_binary: Some(PathBuf::from("/usr/local/bin/lxc-user-nic")),
659 lxc_user_nic_bridge: Some("lxcbr1".to_string()),
660 pasta_binary: Some(PathBuf::from("/usr/local/bin/pasta")),
661 slirp4netns_binary: Some(PathBuf::from("/usr/local/bin/slirp4netns")),
662 slirp4netns_sandbox: Some(AutoOption::Auto),
663 slirp4netns_seccomp: Some(AutoOption::Auto),
664 vpnkit_binary: Some(PathBuf::from("/usr/local/bin/vpnkit")),
665 port_driver: Some(PortDriver::Implicit),
666 publish: vec![
667 "127.0.0.1:8080:80/tcp".to_string(),
668 "127.0.0.1:8443:443/tcp".to_string(),
669 ],
670 pidns: true,
671 cgroupns: true,
672 utsns: true,
673 ipcns: true,
674 reaper: Some(AutoOption::Auto),
675 evacuate_cgroup2: Some("testgroup".to_string()),
676 state_dir: Some(PathBuf::from("/var/foo")),
677 subid_source: Some(SubIdSource::Dynamic),
678 },
679 vec![
680 ARG_DEBUG.to_string(),
681 ARG_COPY_UP.to_string(),
682 "/etc".to_string(),
683 ARG_COPY_UP.to_string(),
684 "/usr".to_string(),
685 ARG_COPY_UP_MODE.to_string(),
686 "tmpfs+symlink".to_string(),
687 ARG_PROPAGATION.to_string(),
688 "rprivate".to_string(),
689 ARG_NET.to_string(),
690 "pasta".to_string(),
691 ARG_MTU.to_string(),
692 "1500".to_string(),
693 ARG_CIDR.to_string(),
694 "10.0.1.0/24".to_string(),
695 ARG_IFNAME.to_string(),
696 "tap1".to_string(),
697 ARG_DISABLE_HOST_LOOPBACK.to_string(),
698 ARG_IPV6.to_string(),
699 ARG_DETACH_NETNS.to_string(),
700 ARG_LXC_USER_NIC_BINARY.to_string(),
701 "/usr/local/bin/lxc-user-nic".to_string(),
702 ARG_LXC_USER_NIC_BRIDGE.to_string(),
703 "lxcbr1".to_string(),
704 ARG_PASTA_BINARY.to_string(),
705 "/usr/local/bin/pasta".to_string(),
706 ARG_SLIRP4NETNS_BINARY.to_string(),
707 "/usr/local/bin/slirp4netns".to_string(),
708 ARG_SLIRP4NETNS_SANDBOX.to_string(),
709 "auto".to_string(),
710 ARG_SLIRP4NETNS_SECCOMP.to_string(),
711 "auto".to_string(),
712 ARG_VPNKIT_BINARY.to_string(),
713 "/usr/local/bin/vpnkit".to_string(),
714 ARG_PORT_DRIVER.to_string(),
715 "implicit".to_string(),
716 ARG_PUBLISH.to_string(),
717 "127.0.0.1:8080:80/tcp".to_string(),
718 ARG_PUBLISH.to_string(),
719 "127.0.0.1:8443:443/tcp".to_string(),
720 ARG_PIDNS.to_string(),
721 ARG_CGROUPNS.to_string(),
722 ARG_UTSNS.to_string(),
723 ARG_IPCNS.to_string(),
724 ARG_REAPER.to_string(),
725 "auto".to_string(),
726 ARG_EVACUATE_CGROUP2.to_string(),
727 "testgroup".to_string(),
728 ARG_STATE_DIR.to_string(),
729 "/var/foo".to_string(),
730 ARG_SUBID_SOURCE.to_string(),
731 "dynamic".to_string(),
732 ]
733 )]
734 #[case::default_options(RootlesskitOptions::default(), Vec::new())]
735 fn rootlesskit_options_to_vec(
736 #[case] options: RootlesskitOptions,
737 #[case] to_vec: Vec<String>,
738 ) {
739 assert_eq!(options.to_vec(), to_vec);
740 }
741
742 #[test]
744 fn rootlesskit_backend_options() {
745 let backend = RootlesskitBackend::new(RootlesskitOptions::default());
746 assert_eq!(backend.options(), &RootlesskitOptions::default());
747 }
748}