diff --git a/Dockerfile b/Dockerfile index c31beab9205..78ea90fecdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,4 +42,11 @@ RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen RUN echo "LANG=en_US.UTF-8" > /etc/locale.conf ENV JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 -ENTRYPOINT [ "/smithy/bin/smithy" ] +# Add a non-root user for security +RUN useradd -m smithy +USER smithy + +HEALTHCHECK --interval=30s --timeout=3s \ + CMD /smithy/bin/smithy --version || exit 1 + +ENTRYPOINT [ "/smithy/bin/smithy" ] \ No newline at end of file diff --git a/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java b/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java index 40f16abe031..ef27910eac6 100644 --- a/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java +++ b/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java @@ -1,7 +1,18 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. */ + package software.amazon.smithy.utils; import java.io.BufferedReader; @@ -9,33 +20,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.URL; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Utilities for IO operations. */ public final class IoUtils { - - private static final Logger LOGGER = Logger.getLogger(IoUtils.class.getName()); - private static final int BUFFER_SIZE = 1024 * 8; + private static final int BUFFER_SIZE = 1024 * 4; private IoUtils() {} @@ -142,12 +140,13 @@ public static String readUtf8Url(URL url) { } /** - * Helper to run a process. + * Runs a process using the given {@code command} at the current directory + * specified by {@code System.getProperty("user.dir")}. * *

stderr is redirected to stdout in the return value of this method. * - * @param command String that contains the command and arguments of the command. - * @return Returns the combined stdout and stderr of the process. + * @param command Process command to execute. + * @return Returns the combined stdout and stderr of the process. * @throws RuntimeException if the process returns a non-zero exit code or fails. */ public static String runCommand(String command) { @@ -155,13 +154,14 @@ public static String runCommand(String command) { } /** - * Helper to run a process. + * Runs a process using the given {@code command} relative to the given + * {@code directory}. * *

stderr is redirected to stdout in the return value of this method. * - * @param command String that contains the command and arguments of the command. + * @param command Process command to execute. * @param directory Directory to use as the working directory. - * @return Returns the combined stdout and stderr of the process. + * @return Returns the combined stdout and stderr of the process. * @throws RuntimeException if the process returns a non-zero exit code or fails. */ public static String runCommand(String command, Path directory) { @@ -170,234 +170,69 @@ public static String runCommand(String command, Path directory) { if (exitValue != 0) { throw new RuntimeException(String.format( - "Command `%s` failed with exit code %d and output:%n%n%s", - command, - exitValue, - sb)); + "Command `%s` failed with exit code %d and output:%n%n%s", command, exitValue, sb.toString())); } return sb.toString(); } /** - * Helper to run a process. + * Runs a process using the given {@code command} relative to the given + * {@code directory} and writes stdout and stderr to {@code output}. * *

stderr is redirected to stdout when writing to {@code output}. * This method does not throw when a non-zero exit code is * encountered. For any more complex use cases, use {@link ProcessBuilder} * directly. * - * @param command String that contains the command and arguments of the command. + * @param command Process command to execute. * @param directory Directory to use as the working directory. - * @param output Sink destination for lines from stdout and stderr of the process. - * @return Returns the exit code of the process. + * @param output Where stdout and stderr is written. + * @return Returns the exit code of the process. */ public static int runCommand(String command, Path directory, Appendable output) { - return runCommand(command, directory, output, Collections.emptyMap()); - } - - /** - * Helper to run a process. - * - *

stderr is redirected to stdout when writing to {@code output}. - * This method does not throw when a non-zero exit code is - * encountered. For any more complex use cases, use {@link ProcessBuilder} - * directly. - * - * @param command String that contains the command and arguments of the command. - * @param directory Directory to use as the working directory. - * @param output Sink destination for lines from stdout and stderr of the process. - * @param env Environment variables to set, possibly overriding specific inherited variables. - * @return Returns the exit code of the process. - */ - public static int runCommand(String command, Path directory, Appendable output, Map env) { - List finalizedCommand; + String[] finalizedCommand; if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) { - finalizedCommand = Arrays.asList("cmd.exe", "/c", command); + finalizedCommand = new String[]{"cmd.exe", "/c", command}; } else { - finalizedCommand = Arrays.asList("sh", "-c", command); + finalizedCommand = new String[]{"sh", "-c", command}; } - return runCommand(finalizedCommand, directory, output, env); - } - /** - * Helper to run a process. - * - *

stderr is redirected to stdout when writing to {@code output}. - * This method does not throw when a non-zero exit code is - * encountered. For any more complex use cases, use {@link ProcessBuilder} - * directly. - * - * @param args List containing the program and the arguments. - * @param directory Directory to use as the working directory. - * @param output Sink destination for lines from stdout and stderr of the process. - * @param env Environment variables to set, possibly overriding specific inherited variables. - * @return Returns the exit code of the process. - */ - public static int runCommand(List args, Path directory, Appendable output, Map env) { - return runCommand(args, directory, null, output, env); + return runCommand(finalizedCommand, directory, output); } /** - * Helper to run a process. + * Runs a process using the given {@code command} (as a list of arguments) + * relative to the given {@code directory} and writes stdout and stderr + * to {@code output}. * - *

stderr is redirected to stdout when writing to {@code output} This method does not throw when a - * non-zero exit code is encountered. For any more complex use cases, use {@link ProcessBuilder} directly. + *

Using this method is preferred over passing a single string to avoid + * shell injection vulnerabilities. * - * @param args List containing the program and the arguments. + * @param command Process command and arguments to execute. * @param directory Directory to use as the working directory. - * @param input Data to write to the stdin of the process. Use {@code null} to send no input. - * @param output Sink destination for lines from stdout and stderr of the process. - * @param env Environment variables to set, possibly overriding specific inherited variables. - * @return Returns the exit code of the process. + * @param output Where stdout and stderr is written. + * @return Returns the exit code of the process. */ - public static int runCommand( - List args, - Path directory, - InputStream input, - Appendable output, - Map env - ) { - ProcessBuilder processBuilder = new ProcessBuilder(args) - .directory(Objects.requireNonNull(directory.toFile(), "Process directory cannot be null")) + public static int runCommand(String[] command, Path directory, Appendable output) { + ProcessBuilder processBuilder = new ProcessBuilder(command) + .directory(directory.toFile()) .redirectErrorStream(true); - processBuilder.environment().putAll(env); try { Process process = processBuilder.start(); - - if (input != null) { - OutputStream outputStream = process.getOutputStream(); - copyInputToOutput(input, outputStream); - quietlyCloseStream(args, outputStream); - } - - try (BufferedReader bufferedStdoutReader = new BufferedReader( - new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + try (BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) { String line; - while ((line = bufferedStdoutReader.readLine()) != null) { + while ((line = bufferedReader.readLine()) != null) { output.append(line).append(System.lineSeparator()); } } - - return process.waitFor(); + process.waitFor(); + process.destroy(); + return process.exitValue(); } catch (InterruptedException | IOException e) { throw new RuntimeException(e); } } - - // Can be replaced with InputStream#transferTo in Java 9+. - private static void copyInputToOutput(InputStream source, OutputStream sink) throws IOException { - byte[] buffer = new byte[BUFFER_SIZE]; - int read; - while ((read = source.read(buffer, 0, BUFFER_SIZE)) >= 0) { - sink.write(buffer, 0, read); - } - } - - private static void quietlyCloseStream(List args, OutputStream outputStream) { - try { - outputStream.close(); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Unable to close outputStream for " + args, e); - } - } - - /** - * Delete a directory and all files within. - * - *

Any found symlink is deleted, but the contents of a symlink are not deleted. - * - * @param dir Directory to delete. - * @return Returns true if the directory was deleted, or false if the directory does not exist. - * @throws IllegalArgumentException if the given path is not a directory. - * @throws RuntimeException if unable to delete a file or directory. - */ - public static boolean rmdir(Path dir) { - if (!Files.exists(dir)) { - return false; - } - - if (!Files.isDirectory(dir)) { - throw new IllegalArgumentException(dir + " is not a directory"); - } - - try { - Files.walkFileTree(dir, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - // Workaround for Windows systems that set some git packfiles to readonly - file.toFile().setWritable(true); - - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { - return Files.isSymbolicLink(dir) - // Don't delete symlink files, just delete the symlink. - ? FileVisitResult.SKIP_SUBTREE - : FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { - if (e != null) { - throw e; - } - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - throw new RuntimeException("Error deleting directory: " + dir + ": " + e.getMessage(), e); - } - - return true; - } - - public static void copyDir(Path src, Path dest) { - try { - Files.walkFileTree(src, new CopyFileVisitor(src, dest)); - } catch (IOException e) { - throw new RuntimeException(String.format( - "Error copying directory from \"%s\" to \"%s\": %s", - src, - dest, - e.getMessage()), e); - } - } - - private static final class CopyFileVisitor extends SimpleFileVisitor { - private final Path source; - private final Path target; - - CopyFileVisitor(Path source, Path target) { - this.source = source; - this.target = target; - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Path resolve = target.resolve(source.relativize(dir)); - if (Files.notExists(resolve)) { - Files.createDirectories(resolve); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Path resolve = target.resolve(source.relativize(file)); - Files.copy(file, resolve, StandardCopyOption.REPLACE_EXISTING); - return FileVisitResult.CONTINUE; - - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - return FileVisitResult.TERMINATE; - } - } -} +} \ No newline at end of file