From 140a6ffbc4333598adb2676291292756dcab69f9 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 22 Feb 2023 13:21:17 +0200 Subject: [PATCH 1/2] Simplify checks for rootless container runtime --- .../NativeImageBuildLocalContainerRunner.java | 71 +------------------ .../runtime/util/ContainerRuntimeUtil.java | 40 +++++++++++ 2 files changed, 42 insertions(+), 69 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java index 60d9243131ad5..48e38e68d061b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -2,27 +2,16 @@ import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; import org.apache.commons.lang3.SystemUtils; import org.jboss.logging.Logger; -import io.quarkus.deployment.OutputFilter; import io.quarkus.deployment.pkg.NativeConfig; -import io.quarkus.deployment.util.ExecUtil; import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.util.ContainerRuntimeUtil; @@ -34,7 +23,8 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp super(nativeConfig, outputDir); if (SystemUtils.IS_OS_LINUX) { ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs)); - if (isDockerRootless(containerRuntime)) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.DOCKER + && containerRuntime.isRootless()) { Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0)); } else { String uid = getLinuxID("-ur"); @@ -51,63 +41,6 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp } } - private static boolean isDockerRootless(ContainerRuntimeUtil.ContainerRuntime containerRuntime) { - if (containerRuntime != ContainerRuntimeUtil.ContainerRuntime.DOCKER) { - return false; - } - String dockerEndpoint = fetchDockerEndpoint(); - // docker socket? - String socketUriPrefix = "unix://"; - if (dockerEndpoint == null || !dockerEndpoint.startsWith(socketUriPrefix)) { - return false; - } - String dockerSocket = dockerEndpoint.substring(socketUriPrefix.length()); - String currentUid = getLinuxID("-ur"); - if (currentUid == null || currentUid.isEmpty() || currentUid.equals(String.valueOf(0))) { - return false; - } - - int socketOwnerUid; - try { - socketOwnerUid = (int) Files.getAttribute(Path.of(dockerSocket), "unix:uid", LinkOption.NOFOLLOW_LINKS); - } catch (IOException e) { - LOGGER.infof("Owner UID lookup on '%s' failed with '%s'", dockerSocket, e.getMessage()); - return false; - } - return currentUid.equals(String.valueOf(socketOwnerUid)); - } - - private static String fetchDockerEndpoint() { - // DOCKER_HOST environment variable overrides the active context - String dockerHost = System.getenv("DOCKER_HOST"); - if (dockerHost != null) { - return dockerHost; - } - - OutputFilter outputFilter = new OutputFilter(); - if (!ExecUtil.execWithTimeout(new File("."), outputFilter, Duration.ofMillis(3000), - "docker", "context", "ls", "--format", - "{{- if .Current -}} {{- .DockerEndpoint -}} {{- end -}}")) { - LOGGER.debug("Docker context lookup didn't succeed in time"); - return null; - } - - Set endpoints = outputFilter.getOutput().lines() - .filter(Objects::nonNull) - .filter(Predicate.not(String::isBlank)) - .collect(Collectors.toSet()); - if (endpoints.size() == 1) { - return endpoints.stream().findFirst().orElse(null); - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Found too many active Docker endpoints: [%s]", - endpoints.stream() - .map(endpoint -> String.format("'%s'", endpoint)) - .collect(Collectors.joining(","))); - } - return null; - } - @Override protected List getContainerRuntimeBuildArgs() { List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index b886e12ff980e..512f7fe7c90dc 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -1,7 +1,10 @@ package io.quarkus.runtime.util; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.function.Predicate; import org.jboss.logging.Logger; @@ -59,6 +62,33 @@ private static String getVersionOutputFor(ContainerRuntime containerRuntime) { } } + private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { + Process rootlessProcess = null; + try { + ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "info") + .redirectErrorStream(true); + rootlessProcess = pb.start(); + rootlessProcess.waitFor(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rootlessProcess.getInputStream())); + Predicate stringPredicate; + // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " + if (containerRuntime == ContainerRuntime.DOCKER) { + stringPredicate = line -> line.trim().equals("rootless"); + } else { + stringPredicate = line -> line.trim().equals("rootless: true"); + } + return bufferedReader.lines().anyMatch(stringPredicate); + } catch (IOException | InterruptedException e) { + // If an exception is thrown in the process, assume we are not running rootless (default docker installation) + log.debugf(e, "Failure to read info output from %s", containerRuntime.getExecutableName()); + return false; + } finally { + if (rootlessProcess != null) { + rootlessProcess.destroy(); + } + } + } + /** * Supported Container runtimes */ @@ -66,8 +96,18 @@ public enum ContainerRuntime { DOCKER, PODMAN; + private final boolean rootless; + + ContainerRuntime() { + this.rootless = getRootlessStateFor(this); + } + public String getExecutableName() { return this.name().toLowerCase(); } + + public boolean isRootless() { + return rootless; + } } } From 2bace220d8875cc68f0559a24c16539d9cb5a9bb Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 22 Feb 2023 13:22:09 +0200 Subject: [PATCH 2/2] Pass `--userns=keep-id` to podman only when in rootless mode Fixes: #31334 --- .../NativeImageBuildLocalContainerRunner.java | 3 ++- .../pkg/steps/UpxCompressionBuildStep.java | 3 ++- .../runtime/util/ContainerRuntimeUtil.java | 27 ++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java index 48e38e68d061b..a7a8aa27725e3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -31,7 +31,8 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp String gid = getLinuxID("-gr"); if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); - if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN + && containerRuntime.isRootless()) { // Needed to avoid AccessDeniedExceptions containerRuntimeArgs.add("--userns=keep-id"); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java index 8039cd91b5da9..ebaa5e4586c5d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java @@ -124,7 +124,8 @@ private boolean runUpxInContainer(NativeImageBuildItem nativeImage, NativeConfig String gid = getLinuxID("-gr"); if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { Collections.addAll(commandLine, "--user", uid + ":" + gid); - if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN + && containerRuntime.isRootless()) { // Needed to avoid AccessDeniedExceptions commandLine.add("--userns=keep-id"); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index 512f7fe7c90dc..94cee990fc25b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -2,6 +2,7 @@ import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.function.Predicate; @@ -68,16 +69,24 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "info") .redirectErrorStream(true); rootlessProcess = pb.start(); - rootlessProcess.waitFor(); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rootlessProcess.getInputStream())); - Predicate stringPredicate; - // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " - if (containerRuntime == ContainerRuntime.DOCKER) { - stringPredicate = line -> line.trim().equals("rootless"); - } else { - stringPredicate = line -> line.trim().equals("rootless: true"); + int exitCode = rootlessProcess.waitFor(); + if (exitCode != 0) { + log.warnf("Command \"%s\" exited with error code %d. " + + "Rootless container runtime detection might not be reliable.", + containerRuntime.getExecutableName(), exitCode); + } + try (InputStream inputStream = rootlessProcess.getInputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + Predicate stringPredicate; + // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " + if (containerRuntime == ContainerRuntime.DOCKER) { + stringPredicate = line -> line.trim().equals("rootless"); + } else { + stringPredicate = line -> line.trim().equals("rootless: true"); + } + return bufferedReader.lines().anyMatch(stringPredicate); } - return bufferedReader.lines().anyMatch(stringPredicate); } catch (IOException | InterruptedException e) { // If an exception is thrown in the process, assume we are not running rootless (default docker installation) log.debugf(e, "Failure to read info output from %s", containerRuntime.getExecutableName());