NIFI-2656: replace -k [password] with -K [passwordfile].

This approaches a proper solution on how to hand over the key from
RunNiFi to NiFi. Insofar the password file is pruned as part of the
startup, NiFi processors can't read it.

See also: NIFI-3045.

This closes #1302.

Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
Anders Breindahl 2016-12-06 14:00:38 +01:00 committed by Andy LoPresto
parent c0f0462e8b
commit 995c7ce2fa
No known key found for this signature in database
GPG Key ID: 6EC293152D90B61D
3 changed files with 114 additions and 50 deletions

View File

@ -17,6 +17,7 @@
package org.apache.nifi.bootstrap; package org.apache.nifi.bootstrap;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -33,7 +34,12 @@ import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.FileAlreadyExistsException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -1027,19 +1033,42 @@ public class RunNiFi {
cmd.add("-Dorg.apache.nifi.bootstrap.config.log.dir=" + nifiLogDir); cmd.add("-Dorg.apache.nifi.bootstrap.config.log.dir=" + nifiLogDir);
cmd.add("org.apache.nifi.NiFi"); cmd.add("org.apache.nifi.NiFi");
if (props.containsKey(NIFI_BOOTSTRAP_SENSITIVE_KEY) && !StringUtils.isBlank(props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY))) { if (props.containsKey(NIFI_BOOTSTRAP_SENSITIVE_KEY) && !StringUtils.isBlank(props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY))) {
cmd.add("-k " + props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY)); Path sensitiveKeyFile = Paths.get(confDir+"/sensitive.key");
try {
// Initially create file with the empty permission set (so nobody can get a file descriptor on it):
Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
sensitiveKeyFile = Files.createFile(sensitiveKeyFile, attr);
// Then, once created, add owner-only rights:
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.OWNER_READ);
attr = PosixFilePermissions.asFileAttribute(perms);
Files.setPosixFilePermissions(sensitiveKeyFile, perms);
} catch (final FileAlreadyExistsException faee) {
cmdLogger.error("The sensitive.key file {} already exists. That shouldn't have been. Aborting.", sensitiveKeyFile);
System.exit(1);
} catch (final Exception e) {
cmdLogger.error("Other failure relating to setting permissions on {}. "
+ "(so that only the owner can read it). "
+ "This is fatal to the bootstrap process for security reasons. Exception was: {}", sensitiveKeyFile, e);
System.exit(1);
}
BufferedWriter sensitiveKeyWriter = Files.newBufferedWriter(sensitiveKeyFile, StandardCharsets.UTF_8);
sensitiveKeyWriter.write(props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY));
sensitiveKeyWriter.close();
cmd.add("-K " + sensitiveKeyFile.toFile().getAbsolutePath());
} }
builder.command(cmd); builder.command(cmd);
final StringBuilder cmdBuilder = new StringBuilder(); final StringBuilder cmdBuilder = new StringBuilder();
for (final String s : cmd) { for (final String s : cmd) {
// Mask the key cmdBuilder.append(s).append(" ");
if (s.startsWith("-k ")) {
cmdBuilder.append("-k ****");
} else {
cmdBuilder.append(s).append(" ");
}
} }
cmdLogger.info("Starting Apache NiFi..."); cmdLogger.info("Starting Apache NiFi...");

View File

@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.bridge.SLF4JBridgeHandler;
import java.io.File; import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -36,12 +37,14 @@ import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.Random;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -55,7 +58,7 @@ import java.util.concurrent.atomic.AtomicLong;
public class NiFi { public class NiFi {
private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class); private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class);
private static final String KEY_FLAG = "-k"; private static final String KEY_FILE_FLAG = "-K";
private final NiFiServer nifiServer; private final NiFiServer nifiServer;
private final BootstrapListener bootstrapListener; private final BootstrapListener bootstrapListener;
@ -307,39 +310,73 @@ public class NiFi {
String key = null; String key = null;
List<String> parsedArgs = parseArgs(args); List<String> parsedArgs = parseArgs(args);
// Check if args contain protection key // Check if args contain protection key
if (parsedArgs.contains(KEY_FLAG)) { if (parsedArgs.contains(KEY_FILE_FLAG)) {
key = getKeyFromArgs(parsedArgs); key = getKeyFromKeyFileAndPrune(parsedArgs);
// Format the key (check hex validity and remove spaces) // Format the key (check hex validity and remove spaces)
key = formatHexKey(key); key = formatHexKey(key);
if (!isHexKeyValid(key)) {
throw new IllegalArgumentException("The key was not provided in valid hex format and of the correct length");
}
return key; }
} else {
// throw new IllegalStateException("No key provided from bootstrap"); if (null == key) {
return ""; return "";
} else if (!isHexKeyValid(key)) {
throw new IllegalArgumentException("The key was not provided in valid hex format and of the correct length");
} else {
return key;
} }
} }
private static String getKeyFromArgs(List<String> parsedArgs) { private static String getKeyFromKeyFileAndPrune(List<String> parsedArgs) {
String key; String key = null;
LOGGER.debug("The bootstrap process provided the " + KEY_FLAG + " flag"); LOGGER.debug("The bootstrap process provided the " + KEY_FILE_FLAG + " flag");
int i = parsedArgs.indexOf(KEY_FLAG); int i = parsedArgs.indexOf(KEY_FILE_FLAG);
if (parsedArgs.size() <= i + 1) { if (parsedArgs.size() <= i + 1) {
LOGGER.error("The bootstrap process passed the {} flag without a key", KEY_FLAG); LOGGER.error("The bootstrap process passed the {} flag without a filename", KEY_FILE_FLAG);
throw new IllegalArgumentException("The bootstrap process provided the " + KEY_FLAG + " flag but no key"); throw new IllegalArgumentException("The bootstrap process provided the " + KEY_FILE_FLAG + " flag but no key");
} }
key = parsedArgs.get(i + 1); try {
LOGGER.info("Read property protection key from bootstrap process"); String passwordfile_path = parsedArgs.get(i + 1);
// Slurp in the contents of the file:
byte[] encoded = Files.readAllBytes(Paths.get(passwordfile_path));
key = new String(encoded,StandardCharsets.UTF_8);
if (0 == key.length())
throw new IllegalArgumentException("Key in keyfile " + passwordfile_path + " yielded an empty key");
LOGGER.info("Now overwriting file in "+passwordfile_path);
// Overwrite the contents of the file (to avoid littering file system
// unlinked with key material):
File password_file = new File(passwordfile_path);
FileWriter overwriter = new FileWriter(password_file,false);
// Construe a random pad:
Random r = new Random();
StringBuffer sb = new StringBuffer();
// Note on correctness: this pad is longer, but equally sufficient.
while(sb.length() < encoded.length){
sb.append(Integer.toHexString(r.nextInt()));
}
String pad = sb.toString();
LOGGER.info("Overwriting key material with pad: "+pad);
overwriter.write(pad);
overwriter.close();
LOGGER.info("Removing/unlinking file: "+passwordfile_path);
password_file.delete();
} catch (IOException e) {
LOGGER.error("Caught IOException while retrieving the "+KEY_FILE_FLAG+"-passed keyfile; aborting: "+e.toString());
System.exit(1);
}
LOGGER.info("Read property protection key from key file provided by bootstrap process");
return key; return key;
} }
private static List<String> parseArgs(String[] args) { private static List<String> parseArgs(String[] args) {
List<String> parsedArgs = new ArrayList<>(Arrays.asList(args)); List<String> parsedArgs = new ArrayList<>(Arrays.asList(args));
for (int i = 0; i < parsedArgs.size(); i++) { for (int i = 0; i < parsedArgs.size(); i++) {
if (parsedArgs.get(i).startsWith(KEY_FLAG + " ")) { if (parsedArgs.get(i).startsWith(KEY_FILE_FLAG + " ")) {
String[] split = parsedArgs.get(i).split(" ", 2); String[] split = parsedArgs.get(i).split(" ", 2);
parsedArgs.set(i, split[0]); parsedArgs.set(i, split[0]);
parsedArgs.add(i + 1, split[1]); parsedArgs.add(i + 1, split[1]);

View File

@ -16,27 +16,7 @@
*/ */
package org.apache.nifi.audit; package org.apache.nifi.audit;
import org.apache.nifi.action.Action; import static org.apache.nifi.web.api.dto.DtoFactory.SENSITIVE_VALUE_MASK;
import org.apache.nifi.action.Component;
import org.apache.nifi.action.FlowChangeAction;
import org.apache.nifi.action.Operation;
import org.apache.nifi.action.component.details.FlowChangeRemoteProcessGroupDetails;
import org.apache.nifi.action.details.ActionDetails;
import org.apache.nifi.action.details.ConfigureDetails;
import org.apache.nifi.action.details.FlowChangeConfigureDetails;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.remote.RemoteGroupPort;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -48,8 +28,27 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import org.apache.nifi.action.Action;
import static org.apache.nifi.web.api.dto.DtoFactory.SENSITIVE_VALUE_MASK; import org.apache.nifi.action.Component;
import org.apache.nifi.action.FlowChangeAction;
import org.apache.nifi.action.Operation;
import org.apache.nifi.action.component.details.FlowChangeRemoteProcessGroupDetails;
import org.apache.nifi.action.details.ActionDetails;
import org.apache.nifi.action.details.ConfigureDetails;
import org.apache.nifi.action.details.FlowChangeConfigureDetails;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.remote.RemoteGroupPort;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Audits remote process group creation/removal and configuration changes. * Audits remote process group creation/removal and configuration changes.
@ -355,7 +354,6 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor {
private RemoteGroupPort auditUpdateProcessGroupPortConfiguration(ProceedingJoinPoint proceedingJoinPoint, RemoteProcessGroupPortDTO remoteProcessGroupPortDto, private RemoteGroupPort auditUpdateProcessGroupPortConfiguration(ProceedingJoinPoint proceedingJoinPoint, RemoteProcessGroupPortDTO remoteProcessGroupPortDto,
RemoteProcessGroup remoteProcessGroup, RemoteGroupPort remoteProcessGroupPort) throws Throwable { RemoteProcessGroup remoteProcessGroup, RemoteGroupPort remoteProcessGroupPort) throws Throwable {
final Map<String, Object> previousValues = ConfigurationRecorder.capturePreviousValues(PORT_CONFIG_RECORDERS, remoteProcessGroupPort); final Map<String, Object> previousValues = ConfigurationRecorder.capturePreviousValues(PORT_CONFIG_RECORDERS, remoteProcessGroupPort);
// perform the underlying operation // perform the underlying operation