Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass --userns=keep-id to podman only when in rootless mode #31336

Merged
merged 2 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,14 +23,16 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp
super(nativeConfig, outputDir);
if (SystemUtils.IS_OS_LINUX) {
ArrayList<String> 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");
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");
}
Expand All @@ -51,63 +42,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<String> 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<String> getContainerRuntimeBuildArgs() {
List<String> containerRuntimeArgs = super.getContainerRuntimeBuildArgs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.quarkus.runtime.util;

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;

import org.jboss.logging.Logger;

Expand Down Expand Up @@ -59,15 +63,60 @@ 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();
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<String> stringPredicate;
// Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: <boolean>"
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
*/
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;
}
}
}