Parcourir la source

AMBARI-16787. 'Configure Ambari Identity' fails when enabling Kerberos on non-root Ambari server (rlevas)

Robert Levas il y a 9 ans
Parent
commit
476f943e93

+ 18 - 19
ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java

@@ -52,11 +52,13 @@ import org.apache.ambari.server.controller.utilities.KerberosChecker;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO;
+import org.apache.ambari.server.orm.entities.KerberosPrincipalEntity;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
 import org.apache.ambari.server.security.SecurePasswordHelper;
 import org.apache.ambari.server.security.credential.Credential;
 import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.serveraction.ActionLog;
 import org.apache.ambari.server.serveraction.ServerAction;
 import org.apache.ambari.server.serveraction.kerberos.CleanupServerAction;
 import org.apache.ambari.server.serveraction.kerberos.ConfigureAmbariIndetityServerAction;
@@ -732,7 +734,7 @@ public class KerberosHelperImpl implements KerberosHelper {
         KerberosIdentityDescriptor ambariServerIdentity = kerberosDescriptor.getIdentity(KerberosHelper.AMBARI_IDENTITY_NAME);
         if (ambariServerIdentity != null) {
           createUserIdentity(ambariServerIdentity, kerberosConfiguration, kerberosOperationHandler, configurations);
-          configureAmbariIdentity(ambariServerIdentity, kerberosOperationHandler, configurations);
+          installAmbariIdentity(ambariServerIdentity, configurations);
           try {
             KerberosChecker.checkJaasConfiguration();
           } catch (AmbariException e) {
@@ -754,34 +756,31 @@ public class KerberosHelperImpl implements KerberosHelper {
     return true;
   }
 
-  private boolean configureAmbariIdentity(KerberosIdentityDescriptor ambariServerIdentity,
-                                          KerberosOperationHandler kerberosOperationHandler,
-                                          Map<String, Map<String, String>> configurations) throws AmbariException {
-    boolean created = false;
-
-
+  /**
+   * Performs tasks needed to install the Kerberos identities created for the Ambari server.
+   *
+   * @param ambariServerIdentity the ambari server's {@link KerberosIdentityDescriptor}
+   * @param configurations       a map of compiled configrations used for variable replacment
+   * @throws AmbariException
+   * @see ConfigureAmbariIndetityServerAction#installAmbariServerIdentity(String, String, String, ActionLog)
+   */
+  private void installAmbariIdentity(KerberosIdentityDescriptor ambariServerIdentity,
+                                     Map<String, Map<String, String>> configurations) throws AmbariException {
     KerberosPrincipalDescriptor principalDescriptor = ambariServerIdentity.getPrincipalDescriptor();
     if (principalDescriptor != null) {
       String principal = variableReplacementHelper.replaceVariables(principalDescriptor.getValue(), configurations);
+      KerberosPrincipalEntity ambariServerPrincipalEntity = kerberosPrincipalDAO.find(principal);
 
-      // If this principal is already in the Ambari database, then don't try to recreate it or it's
-      // keytab file.
-      if (kerberosPrincipalDAO.exists(principal)) {
-
+      if(ambariServerPrincipalEntity != null) {
         KerberosKeytabDescriptor keytabDescriptor = ambariServerIdentity.getKeytabDescriptor();
-        String keytabFilePath = variableReplacementHelper.replaceVariables(keytabDescriptor.getFile(), configurations);
+        if(keytabDescriptor != null) {
+          String keytabFilePath = variableReplacementHelper.replaceVariables(keytabDescriptor.getFile(), configurations);
 
-        if (keytabDescriptor != null) {
           injector.getInstance(ConfigureAmbariIndetityServerAction.class)
-              .createAndConfigureAmbariKeytab(principal, kerberosOperationHandler, keytabFilePath,
-                  keytabDescriptor.getOwnerName(), keytabDescriptor.getOwnerAccess(), null);
-          // throw new AmbariException("Failed to create the keytab for " + principal);
+              .installAmbariServerIdentity(principal, ambariServerPrincipalEntity.getCachedKeytabPath(), keytabFilePath, null);
         }
-
       }
     }
-
-    return created;
   }
 
   @Override

+ 41 - 102
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIndetityServerAction.java

@@ -28,16 +28,13 @@ import org.apache.ambari.server.actionmanager.HostRoleStatus;
 import org.apache.ambari.server.agent.CommandReport;
 import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.controller.utilities.KerberosChecker;
-import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO;
-import org.apache.ambari.server.orm.entities.KerberosPrincipalEntity;
 import org.apache.ambari.server.serveraction.ActionLog;
+import org.apache.ambari.server.utils.ShellCommandUtil;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
-import org.apache.directory.server.kerberos.shared.keytab.Keytab;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.inject.Inject;
-
 /**
  * ConfigureAmbariIndetityServerAction is a ServerAction implementation that creates keytab files as
  * instructed.
@@ -56,13 +53,6 @@ public class ConfigureAmbariIndetityServerAction extends KerberosServerAction {
 
   private final static Logger LOG = LoggerFactory.getLogger(ConfigureAmbariIndetityServerAction.class);
 
-  /**
-   * KerberosPrincipalDAO used to set and get Kerberos principal details
-   */
-  @Inject
-  private KerberosPrincipalDAO kerberosPrincipalDAO;
-
-
   /**
    * Called to execute this action.  Upon invocation, calls
    * {@link KerberosServerAction#processIdentities(Map)} )}
@@ -125,10 +115,13 @@ public class ConfigureAmbariIndetityServerAction extends KerberosServerAction {
 
         String hostName = identityRecord.get(KerberosIdentityDataFileReader.HOSTNAME);
         if (hostName != null && hostName.equalsIgnoreCase(KerberosHelper.AMBARI_SERVER_HOST_NAME)) {
-          String keytabFilePath = identityRecord.get(KerberosIdentityDataFileReader.KEYTAB_FILE_PATH);
-          String keytabOwner = identityRecord.get(KerberosIdentityDataFileReader.KEYTAB_FILE_OWNER_NAME);
-          String keytabAccess = identityRecord.get(KerberosIdentityDataFileReader.KEYTAB_FILE_OWNER_ACCESS);
-          createAndConfigureAmbariKeytab(evaluatedPrincipal, operationHandler, keytabFilePath, keytabOwner, keytabAccess, actionLog);
+          String destKeytabFilePath = identityRecord.get(KerberosIdentityDataFileReader.KEYTAB_FILE_PATH);
+          File hostDirectory = new File(dataDirectory, hostName);
+          File srcKeytabFile = new File(hostDirectory, DigestUtils.sha1Hex(destKeytabFilePath));
+
+          if(srcKeytabFile.exists()) {
+            installAmbariServerIdentity(evaluatedPrincipal, srcKeytabFile.getAbsolutePath(), destKeytabFilePath, actionLog);
+          }
         }
       }
     }
@@ -136,64 +129,46 @@ public class ConfigureAmbariIndetityServerAction extends KerberosServerAction {
     return commandReport;
   }
 
-  public boolean createAndConfigureAmbariKeytab(String principal, KerberosOperationHandler operationHandler,
-                                    String keytabFilePath, String keytabOwner, String keytabAccess, ActionLog
-                                      actionLog) throws AmbariException {
-
-    KerberosPrincipalEntity principalEntity = kerberosPrincipalDAO.find(principal);
-    String cachedKeytabPath = (principalEntity == null) ? null : principalEntity.getCachedKeytabPath();
-
-    if (cachedKeytabPath == null) {
-      return false;
-    }
+  /**
+   * Installs the Ambari Server Kerberos identity by copying its keytab file to the specified location
+   * and then creating the Ambari Server JAAS File.
+   *
+   * @param principal          the ambari server principal name
+   * @param srcKeytabFilePath  the source location of the ambari server keytab file
+   * @param destKeytabFilePath the destination location of the ambari server keytab file
+   * @param actionLog          the logger
+   * @return true if success; false otherwise
+   * @throws AmbariException
+   */
+  public boolean installAmbariServerIdentity(String principal,
+                                             String srcKeytabFilePath,
+                                             String destKeytabFilePath,
+                                             ActionLog actionLog) throws AmbariException {
 
-    Keytab keytab = null;
+    // Use sudo to copy the file into place....
     try {
-      keytab = Keytab.read(new File(cachedKeytabPath));
-    } catch (IOException e) {
-      String message = String.format("Failed to read the cached keytab for %s, recreating if possible - %b",
-        principal, e.getMessage());
-      if (LOG.isDebugEnabled()) {
-        LOG.warn(message, e);
-      } else {
-        LOG.warn(message, e);
-      }
-    }
-
-    if (keytab == null) {
-      return false;
-    }
+      ShellCommandUtil.Result result;
 
-    File keytabFile = new File(keytabFilePath);
-    ensureKeytabFolderExists(keytabFilePath);
-    try {
-      boolean created = operationHandler.createKeytabFile(keytab, keytabFile);
-      String message = String.format("Keytab successfully created: %s for principal %s", created, principal);
-      if (actionLog != null) {
-        actionLog.writeStdOut(message);
+      // Ensure the parent directory exists...
+      File destKeytabFile = new File(destKeytabFilePath);
+      result = ShellCommandUtil.mkdir(destKeytabFile.getParent(), true);
+      if (!result.isSuccessful()) {
+        throw new AmbariException(result.getStderr());
       }
-      if (created) {
-        ensureAmbariOnlyAccess(keytabFile);
-        configureJAAS(principal, keytabFilePath, actionLog);
-      }
-      return created;
-    } catch (KerberosOperationException e) {
-      String message = String.format("Failed to create keytab file for %s - %s", principal, e.getMessage());
-      if (actionLog != null) {
-        actionLog.writeStdErr(message);
+
+      // Copy the keytab file into place...
+      result = ShellCommandUtil.copyFile(srcKeytabFilePath, destKeytabFilePath, true, true);
+      if (!result.isSuccessful()) {
+        throw new AmbariException(result.getStderr());
       }
-      LOG.error(message, e);
+    } catch (InterruptedException | IOException e) {
+      throw new AmbariException(e.getLocalizedMessage(), e);
     }
 
-    return false;
-  }
+    // Create/update the JAASFile...
+    configureJAAS(principal, destKeytabFilePath, actionLog);
 
-  private void ensureKeytabFolderExists(String keytabFilePath) {
-    String keytabFolderPath = keytabFilePath.substring(0, keytabFilePath.lastIndexOf("/"));
-    File keytabFolder = new File(keytabFolderPath);
-    if (!keytabFolder.exists() || !keytabFolder.isDirectory()) {
-      keytabFolder.mkdir();
-    }
+    return true;
   }
 
   private void configureJAAS(String evaluatedPrincipal, String keytabFilePath, ActionLog actionLog) {
@@ -230,40 +205,4 @@ public class ConfigureAmbariIndetityServerAction extends KerberosServerAction {
     }
   }
 
-  /**
-   * Ensures that the owner of the Ambari server process is the only local user account able to
-   * read and write to the specified file or read, write to, and execute the specified directory.
-   *
-   * @param file the file or directory for which to modify access
-   */
-  protected void ensureAmbariOnlyAccess(File file) throws AmbariException {
-    if (file.exists()) {
-      if (!file.setReadable(false, false) || !file.setReadable(true, true)) {
-        String message = String.format("Failed to set %s readable only by Ambari", file.getAbsolutePath());
-        LOG.warn(message);
-        throw new AmbariException(message);
-      }
-
-      if (!file.setWritable(false, false) || !file.setWritable(true, true)) {
-        String message = String.format("Failed to set %s writable only by Ambari", file.getAbsolutePath());
-        LOG.warn(message);
-        throw new AmbariException(message);
-      }
-
-      if (file.isDirectory()) {
-        if (!file.setExecutable(false, false) || !file.setExecutable(true, true)) {
-          String message = String.format("Failed to set %s executable by Ambari", file.getAbsolutePath());
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } else {
-        if (!file.setExecutable(false, false)) {
-          String message = String.format("Failed to set %s not executable", file.getAbsolutePath());
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      }
-    }
-  }
-
 }

+ 115 - 4
ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java

@@ -24,6 +24,7 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Map;
 
 /**
@@ -34,6 +35,8 @@ public class ShellCommandUtil {
   private static final Object WindowsProcessLaunchLock = new Object();
   private static final String PASS_TOKEN = "pass:";
   private static final String KEY_TOKEN = "-key ";
+  private static final String AMBARI_SUDO = "ambari-sudo.sh";
+
   /*
   public static String LogAndReturnOpenSslExitCode(String command, int exitCode) {
     logOpenSslExitCode(command, exitCode);
@@ -61,7 +64,7 @@ public class ShellCommandUtil {
     CharSequence cs = command.subSequence(start, command.indexOf(" ", start));
     return command.replace(cs, "****");
   }
-  
+
   public static String getOpenSslCommandResult(String command, int exitCode) {
     return new StringBuilder().append("Command ").append(hideOpenSslPassword(command)).append(" was finished with exit code: ")
             .append(exitCode).append(" - ").append(getOpenSslExitCodeDescription(exitCode)).toString();
@@ -165,17 +168,111 @@ public class ShellCommandUtil {
     }
   }
 
+  /**
+   * Test if a file or directory exists
+   *
+   * @param path the path to test
+   * @param sudo true to execute the command using sudo (ambari-sudo); otherwise false
+   * @return the shell command result, success indicates the file or directory exists
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  public static Result pathExists(String path, boolean sudo) throws IOException, InterruptedException {
+    String[] command = {
+        (WINDOWS) ? "dir" : "/bin/ls",
+        path
+    };
+
+    return runCommand(command, null, sudo);
+  }
+
+  /**
+   * Creates the specified directory and any directories in the path
+   *
+   * @param directoryPath the directory to create
+   * @param sudo          true to execute the command using sudo (ambari-sudo); otherwise false
+   * @return the shell command result
+   */
+  public static Result mkdir(String directoryPath, boolean sudo) throws IOException, InterruptedException {
+
+    // If this directory already exists, do not try to create it
+    if (pathExists(directoryPath, sudo).isSuccessful()) {
+      return new Result(0, "The directory already exists, skipping.", ""); // Success!
+    }
+    else {
+      ArrayList<String> command = new ArrayList<String>();
+
+      command.add("/bin/mkdir");
+
+      if (!WINDOWS) {
+        command.add("-p"); // create parent directories
+      }
+
+      command.add(directoryPath);
+
+    return runCommand(command.toArray(new String[command.size()]), null, sudo);
+    }
+  }
+
+
+  /**
+   * Copies a source file to the specified destination.
+   *
+   * @param srcFile  the path to the source file
+   * @param destFile the path to the destination file
+   * @param force    true to force copy even if the file exists
+   * @param sudo     true to execute the command using sudo (ambari-sudo); otherwise false
+   * @return the shell command result
+   */
+  public static Result copyFile(String srcFile, String destFile, boolean force, boolean sudo) throws IOException, InterruptedException {
+    ArrayList<String> command = new ArrayList<String>();
+
+    if (WINDOWS) {
+      command.add("copy");
+
+      if (force) {
+        command.add("/Y"); // force overwrite
+      }
+    } else {
+      command.add("cp");
+      command.add("-p"); // preserve mode, ownership, timestamps
+
+      if (force) {
+        command.add("-f"); // force overwrite
+      }
+    }
+
+    command.add(srcFile);
+    command.add(destFile);
+
+    return runCommand(command.toArray(new String[command.size()]), null, sudo);
+  }
+
   /**
    * Runs a command with a given set of environment variables
+   *
    * @param args a String[] of the command and its arguments
    * @param vars a Map of String,String setting an environment variable to run the command with
+   * @param sudo true to execute the command using sudo (ambari-sudo); otherwise false
    * @return Result
    * @throws IOException
    * @throws InterruptedException
    */
-  public static Result runCommand(String [] args, Map<String, String> vars) throws IOException,
-          InterruptedException {
-    ProcessBuilder builder = new ProcessBuilder(args);
+  public static Result runCommand(String [] args, Map<String, String> vars, boolean sudo)
+      throws IOException, InterruptedException {
+
+    String[] processArgs;
+
+    if(sudo) {
+      processArgs = new String[args.length + 1];
+      processArgs[0] = AMBARI_SUDO;
+      System.arraycopy(args, 0, processArgs, 1, args.length);
+    }
+    else {
+      processArgs = args;
+    }
+
+    ProcessBuilder builder = new ProcessBuilder(processArgs);
 
     if (vars != null) {
       Map<String, String> env = builder.environment();
@@ -204,6 +301,20 @@ public class ShellCommandUtil {
     return new Result(exitCode, stdout, stderr);
   }
 
+  /**
+   * Runs a command with a given set of environment variables
+   *
+   * @param args a String[] of the command and its arguments
+   * @param vars a Map of String,String setting an environment variable to run the command with
+   * @return Result
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  public static Result runCommand(String[] args, Map<String, String> vars)
+      throws IOException, InterruptedException {
+    return runCommand(args, vars, false);
+  }
+
   /**
    * Run a command
    * @param args A String[] of the command and its arguments

+ 1 - 5
ambari-server/src/main/resources/stacks/HDP/2.0.6/kerberos.json

@@ -52,11 +52,7 @@
         "configuration": "cluster-env/ambari_principal_name"
       },
       "keytab": {
-        "file": "${keytab_dir}/ambari.server.keytab",
-        "owner": {
-          "name": "root",
-          "access": "r"
-        }
+        "file": "${keytab_dir}/ambari.server.keytab"
       }
     }
   ]

+ 64 - 42
ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java

@@ -18,16 +18,7 @@
 
 package org.apache.ambari.server.utils;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import junit.framework.TestCase;
-import org.apache.ambari.server.configuration.Configuration;
-import org.apache.ambari.server.security.CertGenerationTest;
-import org.apache.ambari.server.security.CertificateManager;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.RandomStringUtils;
+import junit.framework.Assert;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -35,28 +26,15 @@ import org.junit.rules.TemporaryFolder;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
-import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.security.ProtectionDomain;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.Random;
 
-public class TestShellCommandUtil extends TestCase {
+public class TestShellCommandUtil {
 
   public TemporaryFolder temp = new TemporaryFolder();
 
-  protected Constructor<Configuration> getConfigurationConstructor() {
-    try {
-      return Configuration.class.getConstructor(Properties.class);
-    } catch (NoSuchMethodException e) {
-      throw new RuntimeException(
-          "Expected constructor not found in Configuration.java", e);
-    }
-  }
-
   @Before
   public void setUp() throws IOException {
     try {
@@ -74,9 +52,9 @@ public class TestShellCommandUtil extends TestCase {
   @Test
   public void testOSDetection() throws Exception {
     // At least check, that only one OS is selected
-    assertTrue(ShellCommandUtil.LINUX ^ ShellCommandUtil.WINDOWS
+    Assert.assertTrue(ShellCommandUtil.LINUX ^ ShellCommandUtil.WINDOWS
             ^ ShellCommandUtil.MAC);
-    assertTrue(ShellCommandUtil.LINUX || ShellCommandUtil.MAC ==
+    Assert.assertTrue(ShellCommandUtil.LINUX || ShellCommandUtil.MAC ==
             ShellCommandUtil.UNIX_LIKE);
   }
 
@@ -89,19 +67,19 @@ public class TestShellCommandUtil extends TestCase {
               dummyFile.getAbsolutePath());
       String p = ShellCommandUtil.getUnixFilePermissions(
               dummyFile.getAbsolutePath());
-      assertEquals("600", p);
+      Assert.assertEquals("600", p);
 
       ShellCommandUtil.setUnixFilePermissions("444",
               dummyFile.getAbsolutePath());
       p = ShellCommandUtil.getUnixFilePermissions(
               dummyFile.getAbsolutePath());
-      assertEquals("444", p);
+      Assert.assertEquals("444", p);
 
       ShellCommandUtil.setUnixFilePermissions("777",
               dummyFile.getAbsolutePath());
       p = ShellCommandUtil.getUnixFilePermissions(
               dummyFile.getAbsolutePath());
-      assertEquals("777", p);
+      Assert.assertEquals("777", p);
 
     } else {
       // Next command is silently ignored, it's OK
@@ -110,26 +88,26 @@ public class TestShellCommandUtil extends TestCase {
       // On Windows/Mac, output is always MASK_EVERYBODY_RWX
       String p = ShellCommandUtil.getUnixFilePermissions(
               dummyFile.getAbsolutePath());
-      assertEquals(p, ShellCommandUtil.MASK_EVERYBODY_RWX);
+      Assert.assertEquals(p, ShellCommandUtil.MASK_EVERYBODY_RWX);
     }
   }
 
 
   @Test
   public void testRunCommand() throws Exception {
-    ShellCommandUtil.Result result = null;
+    ShellCommandUtil.Result result;
     if (ShellCommandUtil.LINUX) {
       result = ShellCommandUtil.
               runCommand(new String [] {"echo", "dummy"});
-      assertEquals(0, result.getExitCode());
-      assertEquals("dummy\n", result.getStdout());
-      assertEquals("", result.getStderr());
-      assertTrue(result.isSuccessful());
+      Assert.assertEquals(0, result.getExitCode());
+      Assert.assertEquals("dummy\n", result.getStdout());
+      Assert.assertEquals("", result.getStderr());
+      Assert.assertTrue(result.isSuccessful());
 
       result = ShellCommandUtil.
               runCommand(new String [] {"false"});
-      assertEquals(1, result.getExitCode());
-      assertFalse(result.isSuccessful());
+      Assert.assertEquals(1, result.getExitCode());
+      Assert.assertFalse(result.isSuccessful());
     } else {
       // Skipping this test under Windows/Mac
     }
@@ -143,17 +121,61 @@ public class TestShellCommandUtil extends TestCase {
         "-key 1234 -selfsign -extensions jdk7_ca " +
         "-config /var/lib/ambari-server/keys/ca.config -batch " +
         "-infiles /var/lib/ambari-server/keys/ca.csr";
-    assertFalse(ShellCommandUtil.hideOpenSslPassword(command_pass).contains("1234"));
-    assertFalse(ShellCommandUtil.hideOpenSslPassword(command_key).contains("1234"));
+    Assert.assertFalse(ShellCommandUtil.hideOpenSslPassword(command_pass).contains("1234"));
+    Assert.assertFalse(ShellCommandUtil.hideOpenSslPassword(command_key).contains("1234"));
   }
 
+  @Test
   public void testResultsClassIsPublic() throws Exception {
     Class resultClass = ShellCommandUtil.Result.class;
 
-    assertEquals(Modifier.PUBLIC, resultClass.getModifiers() & Modifier.PUBLIC);
+    Assert.assertEquals(Modifier.PUBLIC, resultClass.getModifiers() & Modifier.PUBLIC);
 
     for(Method method : resultClass.getMethods()) {
-      assertEquals(method.getName(), Modifier.PUBLIC, (method.getModifiers() & Modifier.PUBLIC));
+      Assert.assertEquals(method.getName(), Modifier.PUBLIC, (method.getModifiers() & Modifier.PUBLIC));
     }
   }
+
+  @Test
+  public void testPathExists() throws Exception {
+    File nonExisting = new File(temp.getRoot(), "i_do_not_exist");
+    File existing = temp.newFolder();
+
+    ShellCommandUtil.Result result;
+
+    result = ShellCommandUtil.pathExists(existing.getAbsolutePath(), false);
+    Assert.assertTrue(existing.exists());
+    Assert.assertTrue(result.isSuccessful());
+
+    result = ShellCommandUtil.pathExists(nonExisting.getAbsolutePath(), false);
+    Assert.assertFalse(nonExisting.exists());
+    Assert.assertFalse(result.isSuccessful());
+  }
+
+  @Test
+  public void testMkdir() throws Exception {
+    File directory = new File(temp.getRoot(), "newdir");
+
+    ShellCommandUtil.Result result = ShellCommandUtil.mkdir(directory.getAbsolutePath(), false);
+
+    Assert.assertTrue(result.isSuccessful());
+    Assert.assertTrue(directory.exists());
+  }
+
+  @Test
+  public void testCopy() throws Exception {
+    File srcFile = temp.newFile();
+    File destFile = new File(srcFile.getParentFile(), "copied_file");
+
+    FileWriter writer = new FileWriter(srcFile);
+    writer.write("Hello World!!!!!");
+    writer.close();
+
+    ShellCommandUtil.Result result = ShellCommandUtil.copyFile(srcFile.getAbsolutePath(), destFile.getAbsolutePath(), false, false);
+
+    Assert.assertTrue(result.isSuccessful());
+    Assert.assertTrue(destFile.exists());
+    Assert.assertTrue(destFile.length() > 0);
+    Assert.assertEquals(destFile.length(), srcFile.length());
+  }
 }