Skip to content

Commit

Permalink
feat(rootless): help detection with execv check "is current builder r…
Browse files Browse the repository at this point in the history
…ootless?" (#1380)

I recently encountered #1098 and propose an iteration on the fix in #890

This patch adds a last minute check that looks up the current builder
endpoint. This may be seen as a costly operation however, as mentioned
in #889

Here's the output of `docker builder inspect` on my rootless install:
```
Name:          rootless
Driver:        docker
Last Activity: 2023-12-03 02:04:14 +0000 UTC

Nodes:
Name:      rootless
Endpoint:  rootless  # <= THIS HERE
Status:    running
Buildkit:  v0.11.7+d3e6c1360f6e
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386
Labels:
 org.mobyproject.buildkit.worker.moby.host-gateway-ip: 172.17.0.1
```
  • Loading branch information
Emilgardis authored Dec 17, 2023
2 parents bfc59f0 + edf7422 commit 5896ed1
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 44 deletions.
66 changes: 66 additions & 0 deletions src/docker/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub struct Engine {
pub arch: Option<Architecture>,
pub os: Option<ContainerOs>,
pub is_remote: bool,
pub is_rootless: bool,
}

impl Engine {
Expand Down Expand Up @@ -94,6 +95,7 @@ impl Engine {
None => Self::in_docker(msg_info)?,
};
let (kind, arch, os) = get_engine_info(&path, msg_info)?;
let is_rootless = is_rootless(kind).unwrap_or_else(|| is_docker_rootless(&path, msg_info));
let is_remote = is_remote.unwrap_or_else(Self::is_remote);
Ok(Engine {
path,
Expand All @@ -102,6 +104,7 @@ impl Engine {
arch,
os,
is_remote,
is_rootless,
})
}

Expand Down Expand Up @@ -143,6 +146,69 @@ impl Engine {
}
}

fn is_rootless(kind: EngineType) -> Option<bool> {
env::var("CROSS_ROOTLESS_CONTAINER_ENGINE")
.ok()
.and_then(|s| match s.as_ref() {
"auto" => None,
b => Some(bool_from_envvar(b)),
})
.or_else(|| (!kind.is_docker()).then_some(true))
}

#[must_use]
fn is_docker_rootless(ce: &Path, msg_info: &mut MessageInfo) -> bool {
let mut cmd = Command::new(ce);
cmd.args(["info", "-f", "{{.SecurityOptions}}"])
.run_and_get_output(msg_info)
.ok()
.and_then(|cmd| cmd.stdout().ok())
.map(|out| {
out.to_lowercase()
.replace(['[', ' ', ']'], ",")
.contains(",name=rootless,")
})
.unwrap_or_default()
}

#[test]
fn various_is_rootless_configs() {
let var = "CROSS_ROOTLESS_CONTAINER_ENGINE";
let old = env::var(var);
env::remove_var(var);

assert!(!is_rootless(EngineType::Docker).unwrap_or(false));
assert!(is_rootless(EngineType::Docker).unwrap_or(true));

assert_eq!(is_rootless(EngineType::Docker), None);
assert_eq!(is_rootless(EngineType::Podman), Some(true));
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
assert_eq!(is_rootless(EngineType::Other), Some(true));

env::set_var(var, "0");
assert_eq!(is_rootless(EngineType::Docker), Some(false));
assert_eq!(is_rootless(EngineType::Podman), Some(false));
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(false));
assert_eq!(is_rootless(EngineType::Other), Some(false));

env::set_var(var, "1");
assert_eq!(is_rootless(EngineType::Docker), Some(true));
assert_eq!(is_rootless(EngineType::Podman), Some(true));
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
assert_eq!(is_rootless(EngineType::Other), Some(true));

env::set_var(var, "auto");
assert_eq!(is_rootless(EngineType::Docker), None);
assert_eq!(is_rootless(EngineType::Podman), Some(true));
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
assert_eq!(is_rootless(EngineType::Other), Some(true));

match old {
Ok(v) => env::set_var(var, v),
Err(_) => env::remove_var(var),
}
}

// determine if the container engine is docker. this fixes issues with
// any aliases (#530), and doesn't fail if an executable suffix exists.
fn get_engine_info(
Expand Down
2 changes: 1 addition & 1 deletion src/docker/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub(crate) fn run(
docker
.add_seccomp(engine.kind, &options.target, &paths.metadata)
.wrap_err("when copying seccomp profile")?;
docker.add_user_id(engine.kind);
docker.add_user_id(engine.is_rootless);

docker
.args([
Expand Down
2 changes: 1 addition & 1 deletion src/docker/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ symlink_recurse \"${{prefix}}\"

// 6. execute our cargo command inside the container
let mut docker = engine.subcommand("exec");
docker.add_user_id(engine.kind);
docker.add_user_id(engine.is_rootless);
docker.add_envvars(&options, toolchain_dirs, msg_info)?;
docker.add_cwd(&paths)?;
docker.arg(&container_id);
Expand Down
49 changes: 7 additions & 42 deletions src/docker/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::image::PossibleImage;
use super::Image;
use super::PROVIDED_IMAGES;
use crate::cargo::CargoMetadata;
use crate::config::{bool_from_envvar, Config};
use crate::config::Config;
use crate::errors::*;
use crate::extensions::{CommandExt, SafeCommand};
use crate::file::{self, write_file, PathExt, ToUtf8};
Expand Down Expand Up @@ -947,7 +947,7 @@ pub(crate) trait DockerCommandExt {
) -> Result<()>;
fn add_cwd(&mut self, paths: &DockerPaths) -> Result<()>;
fn add_build_command(&mut self, dirs: &ToolchainDirectories, cmd: &SafeCommand) -> &mut Self;
fn add_user_id(&mut self, engine_type: EngineType);
fn add_user_id(&mut self, is_rootless: bool);
fn add_userns(&mut self);
fn add_seccomp(
&mut self,
Expand Down Expand Up @@ -1094,17 +1094,10 @@ impl DockerCommandExt for Command {
self.args(["sh", "-c", &build_command])
}

fn add_user_id(&mut self, engine_type: EngineType) {
fn add_user_id(&mut self, is_rootless: bool) {
// by default, docker runs as root so we need to specify the user
// so the resulting file permissions are for the current user.
// since we can have rootless docker, we provide an override.
let is_rootless = env::var("CROSS_ROOTLESS_CONTAINER_ENGINE")
.ok()
.and_then(|s| match s.as_ref() {
"auto" => None,
b => Some(bool_from_envvar(b)),
})
.unwrap_or_else(|| engine_type != EngineType::Docker);
if !is_rootless {
self.args(["--user", &format!("{}:{}", user_id(), group_id(),)]);
}
Expand Down Expand Up @@ -1519,45 +1512,17 @@ mod tests {

#[test]
fn test_docker_user_id() {
let var = "CROSS_ROOTLESS_CONTAINER_ENGINE";
let old = env::var(var);
env::remove_var(var);

let rootful = format!("\"engine\" \"--user\" \"{}:{}\"", id::user(), id::group());
let rootless = "\"engine\"".to_owned();

let test = |engine, expected| {
let test = |noroot, expected| {
let mut cmd = Command::new("engine");
cmd.add_user_id(engine);
cmd.add_user_id(noroot);
assert_eq!(expected, &format!("{cmd:?}"));
};
test(EngineType::Docker, &rootful);
test(EngineType::Podman, &rootless);
test(EngineType::PodmanRemote, &rootless);
test(EngineType::Other, &rootless);

env::set_var(var, "0");
test(EngineType::Docker, &rootful);
test(EngineType::Podman, &rootful);
test(EngineType::PodmanRemote, &rootful);
test(EngineType::Other, &rootful);

env::set_var(var, "1");
test(EngineType::Docker, &rootless);
test(EngineType::Podman, &rootless);
test(EngineType::PodmanRemote, &rootless);
test(EngineType::Other, &rootless);

env::set_var(var, "auto");
test(EngineType::Docker, &rootful);
test(EngineType::Podman, &rootless);
test(EngineType::PodmanRemote, &rootless);
test(EngineType::Other, &rootless);

match old {
Ok(v) => env::set_var(var, v),
Err(_) => env::remove_var(var),
}
test(false, &rootful);
test(true, &rootless);
}

#[test]
Expand Down

0 comments on commit 5896ed1

Please sign in to comment.