Clarify gid used by docker image process and bind-mount method (#49632)
Fix reference about the uid:gid that Elasticsearch runs as inside the Docker container and add a packaging test to ensure that bind mounting a data dir with a random uid and gid:0 works as expected. Backport of #49529 Closes #47929
This commit is contained in:
parent
502873b144
commit
4b6915ea41
|
@ -196,7 +196,7 @@ sudo sysctl -w vm.max_map_count=262144
|
|||
===== Configuration files must be readable by the `elasticsearch` user
|
||||
|
||||
By default, {es} runs inside the container as user `elasticsearch` using
|
||||
uid:gid `1000:1000`.
|
||||
uid:gid `1000:0`.
|
||||
|
||||
IMPORTANT: One exception is https://docs.openshift.com/container-platform/3.6/creating_images/guidelines.html#openshift-specific-guidelines[Openshift],
|
||||
which runs containers using an arbitrarily assigned user ID.
|
||||
|
@ -204,7 +204,7 @@ Openshift presents persistent volumes with the gid set to `0`, which works witho
|
|||
|
||||
If you are bind-mounting a local directory or file, it must be readable by the `elasticsearch` user.
|
||||
In addition, this user must have write access to the <<path-settings,data and log dirs>>.
|
||||
A good strategy is to grant group access to gid `1000` or `0` for the local directory.
|
||||
A good strategy is to grant group access to gid `0` for the local directory.
|
||||
|
||||
For example, to prepare a local directory for storing data through a bind-mount:
|
||||
|
||||
|
@ -212,7 +212,7 @@ For example, to prepare a local directory for storing data through a bind-mount:
|
|||
--------------------------------------------
|
||||
mkdir esdatadir
|
||||
chmod g+rwx esdatadir
|
||||
chgrp 1000 esdatadir
|
||||
chgrp 0 esdatadir
|
||||
--------------------------------------------
|
||||
|
||||
As a last resort, you can force the container to mutate the ownership of
|
||||
|
@ -351,7 +351,7 @@ For example, to bind-mount `custom_elasticsearch.yml` with `docker run`, specify
|
|||
--------------------------------------------
|
||||
|
||||
IMPORTANT: The container **runs {es} as user `elasticsearch` using
|
||||
**uid:gid `1000:1000`**. Bind mounted host directories and files must be accessible by this user,
|
||||
uid:gid `1000:0`**. Bind mounted host directories and files must be accessible by this user,
|
||||
and the data and log directories must be writable by this user.
|
||||
|
||||
[[_c_customized_image]]
|
||||
|
|
|
@ -43,7 +43,9 @@ import static org.elasticsearch.packaging.util.Docker.assertPermissionsAndOwners
|
|||
import static org.elasticsearch.packaging.util.Docker.copyFromContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.ensureImageIsLoaded;
|
||||
import static org.elasticsearch.packaging.util.Docker.existsInContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.mkDirWithPrivilegeEscalation;
|
||||
import static org.elasticsearch.packaging.util.Docker.removeContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.rmDirWithPrivilegeEscalation;
|
||||
import static org.elasticsearch.packaging.util.Docker.runContainer;
|
||||
import static org.elasticsearch.packaging.util.Docker.runContainerExpectingFailure;
|
||||
import static org.elasticsearch.packaging.util.Docker.verifyContainerInstallation;
|
||||
|
@ -181,6 +183,27 @@ public class DockerTests extends PackagingTestCase {
|
|||
assertThat(nodesResponse, containsString("\"using_compressed_ordinary_object_pointers\":\"false\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the default config can be overridden using a bind mount, and that env vars are respected
|
||||
*/
|
||||
public void test71BindMountCustomPathWithDifferentUID() throws Exception {
|
||||
final Path tempEsDataDir = tempDir.resolve("esDataDir");
|
||||
// Make the local directory and contents accessible when bind-mounted
|
||||
mkDirWithPrivilegeEscalation(tempEsDataDir, 1500, 0);
|
||||
|
||||
// Restart the container
|
||||
final Map<Path, Path> volumes = singletonMap(tempEsDataDir.toAbsolutePath(), Paths.get("/usr/share/elasticsearch/data"));
|
||||
|
||||
runContainer(distribution(), volumes, null);
|
||||
|
||||
waitForElasticsearch(installation);
|
||||
|
||||
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
|
||||
|
||||
assertThat(nodesResponse, containsString("\"_nodes\":{\"total\":1,\"successful\":1,\"failed\":0}"));
|
||||
rmDirWithPrivilegeEscalation(tempEsDataDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that environment variables can be populated by setting variables with the suffix "_FILE",
|
||||
* which point to files that hold the required values.
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.elasticsearch.common.CheckedRunnable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -35,6 +37,7 @@ import static java.nio.file.attribute.PosixFilePermissions.fromString;
|
|||
import static org.elasticsearch.packaging.util.FileMatcher.p644;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p660;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p755;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p770;
|
||||
import static org.elasticsearch.packaging.util.FileMatcher.p775;
|
||||
import static org.elasticsearch.packaging.util.FileUtils.getCurrentVersion;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
@ -281,6 +284,76 @@ public class Docker {
|
|||
return result.isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run privilege escalated shell command on the local file system via a bind mount inside a Docker container.
|
||||
* @param shellCmd The shell command to execute on the localPath e.g. `mkdir /containerPath/dir`.
|
||||
* @param localPath The local path where shellCmd will be executed on (inside a container).
|
||||
* @param containerPath The path to mount localPath inside the container.
|
||||
*/
|
||||
private static void executePrivilegeEscalatedShellCmd(String shellCmd, Path localPath, Path containerPath) {
|
||||
final List<String> args = new ArrayList<>();
|
||||
|
||||
args.add("docker run");
|
||||
|
||||
// Don't leave orphaned containers
|
||||
args.add("--rm");
|
||||
|
||||
// Mount localPath to a known location inside the container, so that we can execute shell commands on it later
|
||||
args.add("--volume \"" + localPath.getParent() + ":" + containerPath.getParent() + "\"");
|
||||
|
||||
// Use a lightweight musl libc based small image
|
||||
args.add("alpine");
|
||||
|
||||
// And run inline commands via the POSIX shell
|
||||
args.add("/bin/sh -c \"" + shellCmd + "\"");
|
||||
|
||||
final String command = String.join(" ", args);
|
||||
logger.info("Running command: " + command);
|
||||
sh.run(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory with specified uid/gid using Docker backed privilege escalation.
|
||||
* @param localPath The path to the directory to create.
|
||||
* @param uid The numeric id for localPath
|
||||
* @param gid The numeric id for localPath
|
||||
*/
|
||||
public static void mkDirWithPrivilegeEscalation(Path localPath, int uid, int gid) {
|
||||
final Path containerBasePath = Paths.get("/mount");
|
||||
final Path containerPath = containerBasePath.resolve(Paths.get("/").relativize(localPath));
|
||||
final List<String> args = new ArrayList<>();
|
||||
|
||||
args.add("mkdir " + containerPath.toAbsolutePath());
|
||||
args.add("&&");
|
||||
args.add("chown " + uid + ":" + gid + " " + containerPath.toAbsolutePath());
|
||||
args.add("&&");
|
||||
args.add("chmod 0770 " + containerPath.toAbsolutePath());
|
||||
final String command = String.join(" ", args);
|
||||
executePrivilegeEscalatedShellCmd(command, localPath, containerPath);
|
||||
|
||||
final PosixFileAttributes dirAttributes = FileUtils.getPosixFileAttributes(localPath);
|
||||
final Map<String, Integer> numericPathOwnership = FileUtils.getNumericUnixPathOwnership(localPath);
|
||||
assertEquals(localPath + " has wrong uid", numericPathOwnership.get("uid").intValue(), uid);
|
||||
assertEquals(localPath + " has wrong gid", numericPathOwnership.get("gid").intValue(), gid);
|
||||
assertEquals(localPath + " has wrong permissions", dirAttributes.permissions(), p770);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a directory using Docker backed privilege escalation.
|
||||
* @param localPath The path to the directory to delete.
|
||||
*/
|
||||
public static void rmDirWithPrivilegeEscalation(Path localPath) {
|
||||
final Path containerBasePath = Paths.get("/mount");
|
||||
final Path containerPath = containerBasePath.resolve(Paths.get("/").relativize(localPath));
|
||||
final List<String> args = new ArrayList<>();
|
||||
|
||||
args.add("cd " + containerBasePath.toAbsolutePath());
|
||||
args.add("&&");
|
||||
args.add("rm -rf " + localPath.getFileName());
|
||||
final String command = String.join(" ", args);
|
||||
executePrivilegeEscalatedShellCmd(command, localPath, containerPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified path's permissions and ownership match those specified.
|
||||
*/
|
||||
|
|
|
@ -46,6 +46,7 @@ public class FileMatcher extends TypeSafeMatcher<Path> {
|
|||
public enum Fileness { File, Directory }
|
||||
|
||||
public static final Set<PosixFilePermission> p775 = fromString("rwxrwxr-x");
|
||||
public static final Set<PosixFilePermission> p770 = fromString("rwxrwx---");
|
||||
public static final Set<PosixFilePermission> p755 = fromString("rwxr-xr-x");
|
||||
public static final Set<PosixFilePermission> p750 = fromString("rwxr-x---");
|
||||
public static final Set<PosixFilePermission> p660 = fromString("rw-rw----");
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.nio.channels.FileChannel;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
@ -42,7 +43,9 @@ import java.nio.file.attribute.PosixFileAttributes;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -240,6 +243,23 @@ public class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets numeric ownership attributes that are supported by Unix filesystems
|
||||
* @return a Map of the uid/gid integer values
|
||||
*/
|
||||
public static Map<String, Integer> getNumericUnixPathOwnership(Path path) {
|
||||
Map<String, Integer> numericPathOwnership = new HashMap<>();
|
||||
|
||||
try {
|
||||
numericPathOwnership.put("uid", (int) Files.getAttribute(path, "unix:uid", LinkOption.NOFOLLOW_LINKS));
|
||||
numericPathOwnership.put("gid", (int) Files.getAttribute(path, "unix:gid", LinkOption.NOFOLLOW_LINKS));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return numericPathOwnership;
|
||||
}
|
||||
|
||||
|
||||
// vagrant creates /tmp for us in windows so we use that to avoid long paths
|
||||
public static Path getTempDir() {
|
||||
return Paths.get("/tmp");
|
||||
|
|
Loading…
Reference in New Issue