From a401d3ebc0b6aa209362d2e80de7aace3c3b8ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9da=20Housni=20Alaoui?= Date: Fri, 22 Jul 2022 22:15:40 +0200 Subject: [PATCH] Quarkus NativeImageBuildStep fails with perm denied with docker rootless --- .../quarkus/deployment/IsDockerWorking.java | 27 ------- .../io/quarkus/deployment/OutputFilter.java | 31 +++++++ .../NativeImageBuildLocalContainerRunner.java | 80 +++++++++++++++++-- 3 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java index 861e77cb98b0f4..61121cc4054740 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java @@ -1,11 +1,8 @@ package io.quarkus.deployment; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.Socket; @@ -15,7 +12,6 @@ import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; -import java.util.function.Function; import java.util.function.Supplier; import org.eclipse.microprofile.config.ConfigProvider; @@ -176,29 +172,6 @@ public Result get() { } } - public static class OutputFilter implements Function { - private final StringBuilder builder = new StringBuilder(); - - @Override - public Runnable apply(InputStream is) { - return () -> { - - try (InputStreamReader isr = new InputStreamReader(is); - BufferedReader reader = new BufferedReader(isr)) { - - for (String line = reader.readLine(); line != null; line = reader.readLine()) { - builder.append(line); - } - } catch (IOException e) { - throw new RuntimeException("Error reading stream.", e); - } - }; - } - - public String getOutput() { - return builder.toString(); - } - } } private enum Result { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java b/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java new file mode 100644 index 00000000000000..7f8f892062651f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java @@ -0,0 +1,31 @@ +package io.quarkus.deployment; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Function; + +public class OutputFilter implements Function { + private final StringBuilder builder = new StringBuilder(); + + @Override + public Runnable apply(InputStream is) { + return () -> { + + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + builder.append(line); + } + } catch (IOException e) { + throw new RuntimeException("Error reading stream.", e); + } + }; + } + + public String getOutput() { + return builder.toString(); + } +} 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 b63f2cb92886f8..63e3c734c94c37 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,37 +2,102 @@ 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.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; public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { + private static final Logger LOGGER = Logger.getLogger(NativeImageBuildLocalContainerRunner.class.getName()); + public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) { super(nativeConfig, outputDir); if (SystemUtils.IS_OS_LINUX) { ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs)); - String uid = getLinuxID("-ur"); - String gid = getLinuxID("-gr"); - if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { - Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); - if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { - // Needed to avoid AccessDeniedExceptions - containerRuntimeArgs.add("--userns=keep-id"); + if (isDockerRootless(containerRuntime)) { + Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0)); + } else { + String uid = getLinuxID("-ur"); + String gid = getLinuxID("-gr"); + if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { + Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { + // Needed to avoid AccessDeniedExceptions + containerRuntimeArgs.add("--userns=keep-id"); + } } } baseContainerRuntimeArgs = containerRuntimeArgs.toArray(baseContainerRuntimeArgs); } } + 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 -}}'")) { + return null; + } + + Set endpoints = outputFilter.getOutput().lines() + .filter(Predicate.not(String::isBlank)) + .collect(Collectors.toSet()); + if (endpoints.size() != 1) { + return null; + } + return endpoints.stream().findFirst().orElse(null); + } + @Override protected List getContainerRuntimeBuildArgs() { List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); @@ -45,4 +110,5 @@ protected List getContainerRuntimeBuildArgs() { volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); return containerRuntimeArgs; } + }