From 0fc492ce7ab76fee55d6d17d8caf6b111d944d42 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 15 Jul 2020 21:35:13 -0600 Subject: [PATCH] Allow running under root on Linux when unshare is available --- .github/workflows/maven.yml | 2 +- pom.xml | 32 ++++++------ .../postgres/embedded/EmbeddedPostgres.java | 52 ++++++++++++------- .../test/db/postgres/util/LinuxUtils.java | 52 +++++++++++++++++++ 4 files changed, 101 insertions(+), 37 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 90ba9f60..e19593aa 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 13] + java: [8, 11, 13, 14] steps: - name: Checkout project uses: actions/checkout@v1 diff --git a/pom.xml b/pom.xml index 16b287c4..7fef61a6 100644 --- a/pom.xml +++ b/pom.xml @@ -109,12 +109,12 @@ org.apache.commons commons-lang3 - 3.6 + 3.10 org.apache.commons commons-compress - 1.19 + 1.20 org.tukaani @@ -124,29 +124,29 @@ commons-io commons-io - 2.6 + 2.7 commons-codec commons-codec - 1.11 + 1.14 org.flywaydb flyway-core - 6.0.8 + 6.5.1 true org.liquibase liquibase-core - 3.6.3 + 4.0.0 true org.postgresql postgresql - 42.2.5 + 42.2.14 junit @@ -158,7 +158,7 @@ org.junit.jupiter junit-jupiter-api - 5.3.2 + 5.6.2 provided true @@ -166,13 +166,13 @@ org.slf4j slf4j-simple - 1.7.25 + 1.7.30 test org.mockito mockito-core - 2.13.0 + 3.4.0 test @@ -181,7 +181,7 @@ maven-pmd-plugin - 3.8 + 3.13.0 verify @@ -194,13 +194,13 @@ net.sourceforge.pmd pmd-core - 5.6.1 + 6.25.0 compile net.sourceforge.pmd pmd-java - 5.6.1 + 6.25.0 compile @@ -216,7 +216,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.2.1 attach-sources @@ -229,7 +229,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.2.0 attach-javadocs @@ -263,7 +263,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.5 + 1.6 sign-artifacts diff --git a/src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java b/src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java index fef7a9f0..6610200e 100644 --- a/src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java +++ b/src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java @@ -14,12 +14,7 @@ package io.zonky.test.db.postgres.embedded; -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -55,7 +50,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; import javax.sql.DataSource; @@ -72,6 +66,7 @@ import org.slf4j.LoggerFactory; import org.tukaani.xz.XZInputStream; +import static io.zonky.test.db.postgres.util.LinuxUtils.isUnshareUseable; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.WRITE; import static java.util.Collections.unmodifiableMap; @@ -104,6 +99,7 @@ public class EmbeddedPostgres implements Closeable private volatile FileOutputStream lockStream; private volatile FileLock lock; private final boolean cleanDataDirectory; + private static boolean useUnshare; private final ProcessBuilder.Redirect errorRedirector; private final ProcessBuilder.Redirect outputRedirector; @@ -133,6 +129,8 @@ public class EmbeddedPostgres implements Closeable this.pgStartupWait = pgStartupWait; Objects.requireNonNull(this.pgStartupWait, "Wait time cannot be null"); + useUnshare = isUnshareUseable(); + if (parentDirectory != null) { mkdirs(parentDirectory); cleanOldDataDirectories(parentDirectory); @@ -243,10 +241,10 @@ private void initdb() watch.start(); List command = new ArrayList<>(); command.addAll(Arrays.asList( - pgBin("initdb"), "-A", "trust", "-U", PG_SUPERUSER, + "-A", "trust", "-U", PG_SUPERUSER, "-D", dataDirectory.getPath(), "-E", "UTF-8")); command.addAll(createLocaleOptions()); - system(command.toArray(new String[command.size()])); + system(pgBin("initdb"), command); LOG.info("{} initdb completed in {}", instanceId, watch); } @@ -259,13 +257,11 @@ private void startPostmaster() throws IOException } final List args = new ArrayList<>(); + args.addAll(pgBin("postgres")); args.addAll(Arrays.asList( - pgBin("pg_ctl"), - "-D", dataDirectory.getPath(), - "-o", createInitOptions().stream().collect(Collectors.joining(" ")), - "-w", - "start" + "-D", dataDirectory.getPath() )); + args.addAll(createInitOptions()); final ProcessBuilder builder = new ProcessBuilder(args); @@ -275,7 +271,7 @@ private void startPostmaster() throws IOException final Process postmaster = builder.start(); if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) { - ProcessOutputLogger.logOutput(LOG, postmaster, "pg_ctl"); + ProcessOutputLogger.logOutput(LOG, postmaster, "postgres"); } LOG.info("{} postmaster started as {} on port {}. Waiting up to {} for server startup to finish.", instanceId, postmaster.toString(), port, pgStartupWait); @@ -414,7 +410,13 @@ public void close() throws IOException private void pgCtl(File dir, String action) { - system(pgBin("pg_ctl"), "-D", dir.getPath(), action, "-m", PG_STOP_MODE, "-t", PG_STOP_WAIT_S, "-w"); + final List args = new ArrayList<>(); + args.addAll(Arrays.asList( + "-D", dir.getPath(), action, + "-m", PG_STOP_MODE, "-t", + PG_STOP_WAIT_S, "-w" + )); + system(pgBin("pg_ctl"), args); } private void cleanOldDataDirectories(File parentDirectory) @@ -461,10 +463,17 @@ private void cleanOldDataDirectories(File parentDirectory) } } - private String pgBin(String binaryName) + private List pgBin(String binaryName) { + final List args = new ArrayList<>(); + if (useUnshare) { + args.addAll(Arrays.asList( + "unshare", "-U" + )); + } final String extension = SystemUtils.IS_OS_WINDOWS ? ".exe" : ""; - return new File(pgDir, "bin/" + binaryName + extension).getPath(); + args.add(new File(pgDir, "bin/" + binaryName + extension).getPath()); + return args; } private static File getWorkingDirectory() @@ -614,8 +623,11 @@ public int hashCode() { } } - private void system(String... command) + private void system(List bin, List args) { + final List command = new ArrayList<>(); + command.addAll(bin); + command.addAll(args); try { final ProcessBuilder builder = new ProcessBuilder(command); builder.redirectErrorStream(true); @@ -624,7 +636,7 @@ private void system(String... command) final Process process = builder.start(); if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) { - String processName = command[0].replaceAll("^.*[\\\\/](\\w+)(\\.exe)?$", "$1"); + String processName = bin.get(bin.size() - 1).replaceAll("^.*[\\\\/](\\w+)(\\.exe)?$", "$1"); ProcessOutputLogger.logOutput(LOG, process, processName); } if (0 != process.waitFor()) { diff --git a/src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java b/src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java index 70088693..ba005bdd 100644 --- a/src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java +++ b/src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java @@ -22,8 +22,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -34,12 +39,16 @@ public final class LinuxUtils { private static final String DISTRIBUTION_NAME = resolveDistributionName(); + private static final boolean UNSHARE_USEABLE = unshareUseable(); + private LinuxUtils() {} public static String getDistributionName() { return DISTRIBUTION_NAME; } + public static boolean isUnshareUseable() { return UNSHARE_USEABLE; } + private static String resolveDistributionName() { if (!SystemUtils.IS_OS_LINUX) { return null; @@ -85,4 +94,47 @@ private static String resolveDistributionName() { return null; } } + + private static boolean unshareUseable() { + if (SystemUtils.IS_OS_LINUX) { + int uid; + try { + Class c = Class.forName("com.sun.security.auth.module.UnixSystem"); + Object o = c.getDeclaredConstructor().newInstance(); + Method method = c.getDeclaredMethod("getUid"); + uid = ((Number) method.invoke(o)).intValue(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | + NoSuchMethodException | InvocationTargetException e) { + return false; + } + if (uid == 0) { + final List command = new ArrayList<>(); + command.addAll(Arrays.asList( + "unshare", "-U", + "id", "-u" + )); + final ProcessBuilder builder = new ProcessBuilder(command); + final Process process; + try { + process = builder.start(); + } catch (IOException e) { + return false; + } + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + try { + process.waitFor(); + } catch (InterruptedException e) { + return false; + } + try { + if (process.exitValue() == 0 && br.readLine() != "0") { + return true; + } + } catch (IOException e) { + return false; + } + } + } + return false; + } }