|
@@ -26,11 +26,18 @@ import java.util.ArrayList;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Random;
|
|
|
+
|
|
|
+import javax.crypto.SecretKey;
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
import org.apache.hadoop.filecache.DistributedCache;
|
|
|
+import org.apache.hadoop.fs.FSDataOutputStream;
|
|
|
+import org.apache.hadoop.fs.FileSystem;
|
|
|
import org.apache.hadoop.fs.FileUtil;
|
|
|
+import org.apache.hadoop.fs.Path;
|
|
|
+import org.apache.hadoop.fs.permission.FsPermission;
|
|
|
import org.apache.hadoop.io.FloatWritable;
|
|
|
import org.apache.hadoop.io.NullWritable;
|
|
|
import org.apache.hadoop.io.Writable;
|
|
@@ -41,6 +48,11 @@ import org.apache.hadoop.mapred.RecordReader;
|
|
|
import org.apache.hadoop.mapred.Reporter;
|
|
|
import org.apache.hadoop.mapred.TaskAttemptID;
|
|
|
import org.apache.hadoop.mapred.TaskLog;
|
|
|
+import org.apache.hadoop.mapreduce.security.SecureShuffleUtils;
|
|
|
+import org.apache.hadoop.mapreduce.security.TokenCache;
|
|
|
+import org.apache.hadoop.mapreduce.security.token.JobTokenIdentifier;
|
|
|
+import org.apache.hadoop.mapreduce.security.token.JobTokenSecretManager;
|
|
|
+import org.apache.hadoop.security.token.Token;
|
|
|
import org.apache.hadoop.util.ReflectionUtils;
|
|
|
import org.apache.hadoop.util.StringUtils;
|
|
|
|
|
@@ -82,6 +94,18 @@ class Application<K1 extends WritableComparable, V1 extends Writable,
|
|
|
env.put("TMPDIR", System.getProperty("java.io.tmpdir"));
|
|
|
env.put("hadoop.pipes.command.port",
|
|
|
Integer.toString(serverSocket.getLocalPort()));
|
|
|
+
|
|
|
+ //Add token to the environment if security is enabled
|
|
|
+ Token<JobTokenIdentifier> jobToken = TokenCache.getJobToken(conf
|
|
|
+ .getCredentials());
|
|
|
+ // This password is used as shared secret key between this application and
|
|
|
+ // child pipes process
|
|
|
+ byte[] password = jobToken.getPassword();
|
|
|
+ String localPasswordFile = conf.getJobLocalDir() + Path.SEPARATOR
|
|
|
+ + "jobTokenPassword";
|
|
|
+ writePasswordToLocalFile(localPasswordFile, password, conf);
|
|
|
+ env.put("hadoop.pipes.shared.secret.location", localPasswordFile);
|
|
|
+
|
|
|
List<String> cmd = new ArrayList<String>();
|
|
|
String interpretor = conf.get("hadoop.pipes.executable.interpretor");
|
|
|
if (interpretor != null) {
|
|
@@ -107,17 +131,52 @@ class Application<K1 extends WritableComparable, V1 extends Writable,
|
|
|
|
|
|
process = runClient(cmd, env);
|
|
|
clientSocket = serverSocket.accept();
|
|
|
- handler = new OutputHandler<K2, V2>(output, reporter, recordReader);
|
|
|
+
|
|
|
+ String challenge = getSecurityChallenge();
|
|
|
+ String digestToSend = createDigest(password, challenge);
|
|
|
+ String digestExpected = createDigest(password, digestToSend);
|
|
|
+
|
|
|
+ handler = new OutputHandler<K2, V2>(output, reporter, recordReader,
|
|
|
+ digestExpected);
|
|
|
K2 outputKey = (K2)
|
|
|
ReflectionUtils.newInstance(outputKeyClass, conf);
|
|
|
V2 outputValue = (V2)
|
|
|
ReflectionUtils.newInstance(outputValueClass, conf);
|
|
|
downlink = new BinaryProtocol<K1, V1, K2, V2>(clientSocket, handler,
|
|
|
outputKey, outputValue, conf);
|
|
|
+
|
|
|
+ downlink.authenticate(digestToSend, challenge);
|
|
|
+ waitForAuthentication();
|
|
|
+ LOG.debug("Authentication succeeded");
|
|
|
downlink.start();
|
|
|
downlink.setJobConf(conf);
|
|
|
}
|
|
|
|
|
|
+ private String getSecurityChallenge() {
|
|
|
+ Random rand = new Random(System.currentTimeMillis());
|
|
|
+ //Use 4 random integers so as to have 16 random bytes.
|
|
|
+ StringBuilder strBuilder = new StringBuilder();
|
|
|
+ strBuilder.append(rand.nextInt(0x7fffffff));
|
|
|
+ strBuilder.append(rand.nextInt(0x7fffffff));
|
|
|
+ strBuilder.append(rand.nextInt(0x7fffffff));
|
|
|
+ strBuilder.append(rand.nextInt(0x7fffffff));
|
|
|
+ return strBuilder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void writePasswordToLocalFile(String localPasswordFile,
|
|
|
+ byte[] password, JobConf conf) throws IOException {
|
|
|
+ FileSystem localFs = FileSystem.getLocal(conf);
|
|
|
+ Path localPath = new Path(localPasswordFile);
|
|
|
+ if (localFs.isFile(localPath)) {
|
|
|
+ LOG.debug("Password file is already created by previous path");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ FSDataOutputStream out = FileSystem.create(localFs, localPath,
|
|
|
+ new FsPermission("400"));
|
|
|
+ out.write(password);
|
|
|
+ out.close();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Get the downward protocol object that can send commands down to the
|
|
|
* application.
|
|
@@ -126,7 +185,19 @@ class Application<K1 extends WritableComparable, V1 extends Writable,
|
|
|
DownwardProtocol<K1, V1> getDownlink() {
|
|
|
return downlink;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Wait for authentication response.
|
|
|
+ * @throws IOException
|
|
|
+ * @throws InterruptedException
|
|
|
+ */
|
|
|
+ void waitForAuthentication() throws IOException,
|
|
|
+ InterruptedException {
|
|
|
+ downlink.flush();
|
|
|
+ LOG.debug("Waiting for authentication response");
|
|
|
+ handler.waitForAuthentication();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Wait for the application to finish
|
|
|
* @return did the application finish correctly?
|
|
@@ -190,5 +261,11 @@ class Application<K1 extends WritableComparable, V1 extends Writable,
|
|
|
Process result = builder.start();
|
|
|
return result;
|
|
|
}
|
|
|
+
|
|
|
+ public static String createDigest(byte[] password, String data)
|
|
|
+ throws IOException {
|
|
|
+ SecretKey key = JobTokenSecretManager.createSecretKey(password);
|
|
|
+ return SecureShuffleUtils.hashFromString(data, key);
|
|
|
+ }
|
|
|
|
|
|
}
|