REPO-4305: jmx dump password redaction inconsistent (#499)

Methods and tests have been added to handle removal of passwords from the InputCommands section of the JMX dump as this is not accessible by our repository-jmx-repository.xml.
Added a static string[]  that can be altered to provide control over arguments that are redacted.

New method added to dynamically create the regex pattern, based on provided  argument.

(cherry picked from commit 2ce690a473)
This commit is contained in:
David Edwards
2019-06-27 13:18:30 +01:00
parent 9cc33ee305
commit cc74b2932b
2 changed files with 233 additions and 16 deletions

View File

@@ -30,13 +30,17 @@ import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
@@ -71,6 +75,10 @@ public class JmxDumpUtil
private static final String OS_NAME = "os.name";
private static final String INPUT_ARGUMENTS = "InputArguments";
private static final String[] REDACTED_INPUTS = {"password","token","pwd"};
/**
* Dumps a local or remote MBeanServer's entire object tree for support purposes. Nested arrays and CompositeData
* objects in MBean attribute values are handled.
@@ -164,9 +172,129 @@ public class JmxDumpUtil
attributes.put(OS_NAME, updateOSNameAttributeForLinux(osName));
}
}
if (objectName.getCanonicalName().equals("java.lang:type=Runtime"))
{
String[] commandInputs = (String[]) attributes.get(INPUT_ARGUMENTS);
if(commandInputs != null)
{
try
{
attributes.put(INPUT_ARGUMENTS, cleanPasswordsFromInputArguments(commandInputs,REDACTED_INPUTS));
} catch (IllegalArgumentException e)
{
attributes.put(INPUT_ARGUMENTS, commandInputs);
}
}
}
tabulate(JmxDumpUtil.NAME_HEADER, JmxDumpUtil.VALUE_HEADER, attributes, out, 0);
}
/**
* Replaces strings with JmxDumpUtil.PROTECTED_VALUE,
* if any of the string that contains a string from redactedInputs.
*
* @see #cleanPasswordFromInputArgument
* @param commandInputs one or more strings of input arguments
* @param redactedInputs one or more strings, that end input arguments that are to be redacted
* @return commandInputs with any arguments ending in redactedInputs with redacted values
*/
static String[] cleanPasswordsFromInputArguments(String[] commandInputs, String[] redactedInputs)
{
Pattern passwordRedactPattern = Pattern.compile(createPasswordFindRegexString(redactedInputs));
List<String> cleanInputs = new ArrayList<String>();
for (String input : commandInputs)
{
input = cleanPasswordFromInputArgument(input, passwordRedactPattern);
cleanInputs.add(input);
}
return cleanInputs.toArray(new String[commandInputs.length]);
}
/**
* Removes any characters the word/s provided in redactedInputs
* and replaces them with JmxDumpUtil.PROTECTED_VALUE
* <p>
* Example:
* <p>
* Input: -Ddb.password=alfresco
* <p>
* Output: -Ddb.password=********
* </p>
*
* @param input String
* @param redactedInputs String[]
* @return password redacted string if input matches a string in redactedInputs, an un-altered string will be returned if it does not match.
*/
static String cleanPasswordFromInputArgument(String input, Pattern redactedInputPattern)
{
//Replace the whole string with just capture group 1 to remove the desired value and concat the protected value.
String output = redactedInputPattern.matcher(input).replaceAll("$1"+PROTECTED_VALUE);
return output;
}
/**
* Creates a regular expression that will select a string that contains one of the values provided in argEndings, proceeding an "=" and defines two capture groups:
* <ul>
* <li>Group 1: An argEnding that is followed by an "=", including the "=" and all character prior to the argEnding.
* <li>Group 2: The characters that follow group 1, to the end of the string or new line.
* </ul>
* <p>
* The argEnding can be the whole Input argument or the common characters proceeding the = sign.
* Example argEndings:
* <ul>
* <li> -Ddb.password This will select the values passed as -Ddb.password
* <li> password This will select any potential values that end in the word password
* </ul>
* <p>
* Example usage:
* <p>
* argEndings={"password", "pwd"}
* <p>
* This will create a regex that will match a string that contains either argEndings.
* The following will be matched by the resulting regex:
* <p>
* "-Ddb.password=my_password"
* <p>
* For this example: group 1="-Ddb.password=" group 2="my_password"
*
*
* @param argEndings Strings that will end the input argument you wish to select
* @return Regex pattern for selecting the characters following the strings passed as argEndings
*/
static String createPasswordFindRegexString(String[] argEndings) throws IllegalArgumentException
{
if(argEndings.length<1)
{
IllegalArgumentException e = new IllegalArgumentException("Arguments are required");
throw e;
}
StringJoiner argJoiner = new StringJoiner("|");
for (String argEnding : argEndings)
{
argJoiner.add(escapeRegexMetaChars(argEnding)+"=");
}
String regex = String.format("%s%s%s%s%s",
"(?i)(.*(", argJoiner.toString(),"))((?<=",argJoiner.toString(), ").*+)");
return regex;
}
/**
* Places an escape character in front of any regex meta charater: | ? * + .
*
* @param input
* @return
*/
static String escapeRegexMetaChars (String input)
{
String pattern = "(\\||\\?|\\*|\\+|\\.)";
String output = input.replaceAll(pattern, "\\\\$1");
return output;
}
/**
* Adds a Linux version
*

View File

@@ -23,23 +23,112 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.management;
package org.alfresco.repo.management;
import static org.junit.Assert.assertArrayEquals;
import java.util.regex.Pattern;
import org.alfresco.util.ApplicationContextHelper;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import junit.framework.TestCase;
public class JmxDumpUtilTest extends TestCase
{
public void testUpdateOSNameAttribute() throws Exception
import junit.framework.TestCase;
public class JmxDumpUtilTest extends TestCase
{
public void testUpdateOSNameAttribute() throws Exception
{
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
String osName = System.getProperty("os.name");
if (osName.toLowerCase().startsWith("linux"))
{
String attr = JmxDumpUtil.updateOSNameAttributeForLinux(osName);
assertTrue(attr.toLowerCase().startsWith("linux ("));
}
}
}
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
String osName = System.getProperty("os.name");
if (osName.toLowerCase().startsWith("linux"))
{
String attr = JmxDumpUtil.updateOSNameAttributeForLinux(osName);
assertTrue(attr.toLowerCase().startsWith("linux ("));
}
}
@Test
public void testCleanPasswordsFromInputArgument() throws Exception
{
Pattern pattern = Pattern.compile("(?i)(.*(password=|pwd=|token=))((?<=password=|pwd=|token=).*+)");
String passwordArg = "-Ddb.password=I should be stars \"£$%^&*()@";
String expected = "-Ddb.password="+JmxDumpUtil.PROTECTED_VALUE;
String actual = JmxDumpUtil.cleanPasswordFromInputArgument(passwordArg,pattern);
assertEquals("Expectected output: "+ expected +" Actual output: "+actual, expected, actual);
passwordArg = "-Ddb.paSsword=@";
expected = "-Ddb.paSsword="+JmxDumpUtil.PROTECTED_VALUE;
actual = JmxDumpUtil.cleanPasswordFromInputArgument(passwordArg, pattern);
assertEquals("Expectected output: "+ expected +" Actual output: "+actual, expected, actual);
passwordArg = "somePrefix.token=\"If i'm not replaced, something has gone very wrong\"";
expected = "somePrefix.token="+JmxDumpUtil.PROTECTED_VALUE;
actual = JmxDumpUtil.cleanPasswordFromInputArgument(passwordArg, pattern);
assertEquals("Expectected output: "+ expected +" Actual output: "+actual, expected, actual);
passwordArg = "yetanotherpwd=";
expected = "yetanotherpwd="+JmxDumpUtil.PROTECTED_VALUE;
actual = JmxDumpUtil.cleanPasswordFromInputArgument(passwordArg, pattern);
assertEquals("Expectected output: "+ expected +" Actual output: "+actual, expected, actual);
passwordArg = "AnyOtherArgument=\"I should still be here\"";
expected = "AnyOtherArgument=\"I should still be here\"";
actual = JmxDumpUtil.cleanPasswordFromInputArgument(passwordArg, pattern);
assertEquals("Expectected output :"+ expected +" Actual output :"+actual, expected, actual);
}
@Test
public void testCleanPasswordsFromInputArguments() throws Exception
{
String[] argEndingsTypical = {"password", "token","pwd"};
String[] args = {"-Ddb.password=alfresco", "-Ddb.user=alfresco", "-DtestToken=asdoij3ifiej22244ojpgkmkfpsi3j55643pojpdjoismvi4563625mposvsd"};
String[] expectedArray = {"-Ddb.password="+JmxDumpUtil.PROTECTED_VALUE, "-Ddb.user=alfresco", "-DtestToken="+JmxDumpUtil.PROTECTED_VALUE};
String[] actualArray = JmxDumpUtil.cleanPasswordsFromInputArguments(args, argEndingsTypical);
assertArrayEquals("Expectected output:"+expectedArray+" Actual output:"+actualArray,expectedArray,actualArray);
args = new String[]{"-Ddb.port=1234", "-Ddb.user=alfresco", "-DtestArg=Test1234password"};
expectedArray = new String[]{"-Ddb.port=1234", "-Ddb.user=alfresco", "-DtestArg=Test1234password"};
actualArray = JmxDumpUtil.cleanPasswordsFromInputArguments(args, argEndingsTypical);
assertArrayEquals("Expectected output:"+expectedArray+" Actual output:"+actualArray, expectedArray, actualArray);
}
@Test
public void testCreatePasswordFindRegexString() throws Exception
{
String[] argEndings = {"password", "any old ending :D", "token"};
String expected = "(?i)(.*(password=|any old ending :D=|token=))((?<=password=|any old ending :D=|token=).*+)";
String actual = JmxDumpUtil.createPasswordFindRegexString(argEndings);
assertEquals("Expectected output :"+expected+" Actual output :"+actual,expected, actual);
String[] argEndings2 = {"?", "\"£$%^&*"};
expected = "(?i)(.*(\\?=|\"£$%^&\\*=))((?<=\\?=|\"£$%^&\\*=).*+)";
actual = JmxDumpUtil.createPasswordFindRegexString(argEndings2);
assertEquals("Expectected output :"+expected+" Actual output :"+actual,expected, actual);
String[] emptyArgs = {};
try
{
JmxDumpUtil.createPasswordFindRegexString(emptyArgs);
fail("expected exception was not occured.");
} catch(IllegalArgumentException e)
{
}
}
@Test
public void testEscapeRegexMetaChars()
{
String input = "|?*+.";
String expected = "\\|\\?\\*\\+\\.";
String actual = JmxDumpUtil.escapeRegexMetaChars(input);
assertEquals("Expectected output :" + expected + " Actual output :" + actual, expected, actual);
input = "Let's.Add++,*complexity?!\"";
expected = "Let's\\.Add\\+\\+,\\*complexity\\?!\"";
actual = JmxDumpUtil.escapeRegexMetaChars(input);
assertEquals("Expectected output :" + expected + " Actual output :" + actual, expected, actual);
}
}