From 58abbd2f47df8ec5d817ee93d44e525837c29087 Mon Sep 17 00:00:00 2001 From: Mike Kienenberger Date: Fri, 5 Jun 2020 14:50:50 -0400 Subject: [PATCH] Rework sensitive logging --- .../java/net/authorize/util/HttpCallTask.java | 17 +- .../java/net/authorize/util/HttpUtility.java | 31 +-- .../util/SensitiveFilterLogWrapper.java | 178 ++++++++++++++++++ .../net/authorize/util/SensitiveLogger.java | 5 + 4 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 src/main/java/net/authorize/util/SensitiveFilterLogWrapper.java create mode 100644 src/main/java/net/authorize/util/SensitiveLogger.java diff --git a/src/main/java/net/authorize/util/HttpCallTask.java b/src/main/java/net/authorize/util/HttpCallTask.java index 59bbb24c..83f14cb1 100644 --- a/src/main/java/net/authorize/util/HttpCallTask.java +++ b/src/main/java/net/authorize/util/HttpCallTask.java @@ -30,7 +30,8 @@ * */ public class HttpCallTask implements Callable { - private static Log logger = LogFactory.getLog(HttpCallTask.class); + private static Log loggerSensitive = LogFactory.getLog(SensitiveLogger.class); + private static Log filteredLogger = new SensitiveFilterLogWrapper(LogFactory.getLog(HttpUtility.class)); Environment env = null; ANetApiRequest request = null; @@ -82,7 +83,11 @@ public ANetApiResponse call() throws Exception { } } } - LogHelper.debug(logger, "Raw Response: '%s'", buffer.toString()); + if (loggerSensitive.isDebugEnabled()) { + LogHelper.debug(loggerSensitive, "Raw Response: '%s'", buffer.toString()); + } else if (filteredLogger.isDebugEnabled()) { + LogHelper.debug(filteredLogger, "Raw Response: '%s'", buffer.toString()); + } // handle HTTP errors if (0 == buffer.length()) { response = createErrorResponse(httpResponse, null); @@ -117,7 +122,7 @@ public ANetApiResponse call() throws Exception { { response = (ANetApiResponse) localResponse; } else { - LogHelper.warn( logger, "Unknown ResponseType: '%s'", localResponse); + LogHelper.warn( filteredLogger, "Unknown ResponseType: '%s'", localResponse); } } } @@ -158,7 +163,7 @@ private void setErrorResponse(List messages, HttpResponse httpResponse) String text = "Unknown Error"; if (null != httpResponse.getStatusLine()) { - LogHelper.warn( logger, "Error deserializing response to '%s'", this.classType); + LogHelper.warn( filteredLogger, "Error deserializing response to '%s'", this.classType); code = String.format("%d", httpResponse.getStatusLine().getStatusCode()); if (null != httpResponse.getStatusLine().getReasonPhrase()) { text = httpResponse.getStatusLine().getReasonPhrase();} @@ -172,7 +177,7 @@ private void setErrorResponse(List messages, Exception exception) { messages.add(errorMessage); String code = "Error"; String text = "Unknown Error"; - LogHelper.error( logger, "Http request execute failed: '%s'", exception.getMessage()); + LogHelper.error( filteredLogger, "Http request execute failed: '%s'", exception.getMessage()); code = exception.getClass().getCanonicalName(); //code = exception.getClass().getTypeName();// requires java1.8 text = exception.getMessage(); @@ -184,6 +189,6 @@ private void setErrorResponse(List messages, Exception exception) { private void setErrorMessageValues(String code, String text) { errorMessage.setCode(code); errorMessage.setText(text); - LogHelper.warn(logger, "Adding ErrorMessage: Code: '%s', Text: '%s'", code, text); + LogHelper.warn(filteredLogger, "Adding ErrorMessage: Code: '%s', Text: '%s'", code, text); } } diff --git a/src/main/java/net/authorize/util/HttpUtility.java b/src/main/java/net/authorize/util/HttpUtility.java index cae21c51..a85b4997 100644 --- a/src/main/java/net/authorize/util/HttpUtility.java +++ b/src/main/java/net/authorize/util/HttpUtility.java @@ -34,7 +34,8 @@ */ public final class HttpUtility { - private static Log logger = LogFactory.getLog(HttpUtility.class); + private static Log loggerSensitive = LogFactory.getLog(SensitiveLogger.class); + private static Log filteredLogger = new SensitiveFilterLogWrapper(LogFactory.getLog(HttpUtility.class)); static int httpConnectionTimeout = Environment.getIntProperty(Constants.HTTP_CONNECTION_TIME_OUT); static int httpReadTimeout = Environment.getIntProperty(Constants.HTTP_READ_TIME_OUT); @@ -68,7 +69,7 @@ static HttpPost createPostRequest(Environment env, ANetApiRequest request) throw if(null != request) { postUrl = new URI(env.getXmlBaseUrl() + "/xml/v1/request.api"); - logger.debug(String.format("Posting request to Url: '%s'", postUrl)); + filteredLogger.debug(String.format("Posting request to Url: '%s'", postUrl)); httpPost = new HttpPost(postUrl); httpPost.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); @@ -80,7 +81,11 @@ static HttpPost createPostRequest(Environment env, ANetApiRequest request) throw httpPost.setHeader("Content-Type", "text/xml; charset=utf-8"); String xmlRequest = XmlUtility.getXml(request); - logger.debug(String.format("Request: '%s%s%s'", LogHelper.LineSeparator, xmlRequest, LogHelper.LineSeparator)); + if (loggerSensitive.isDebugEnabled()) { + loggerSensitive.debug(String.format("Request: '%s%s%s'", LogHelper.LineSeparator, xmlRequest, LogHelper.LineSeparator)); + } else if (filteredLogger.isDebugEnabled()) { + filteredLogger.debug(String.format("Request: '%s%s%s'", LogHelper.LineSeparator, xmlRequest, LogHelper.LineSeparator)); + } httpPost.setEntity(new StringEntity(xmlRequest, HTTP.UTF_8)); } @@ -103,11 +108,15 @@ public static ANetApiResponse postData(Environment env, ANetApiRequest reque try { response = future.get(); - logger.debug(String.format("Response: '%s'", response)); + if (loggerSensitive.isDebugEnabled()) { + loggerSensitive.debug(String.format("Response: '%s'", response)); + } else if (filteredLogger.isDebugEnabled()) { + filteredLogger.debug(String.format("Response: '%s'", response)); + } } catch (InterruptedException ie) { - logger.error(String.format("Http call interrupted Message: '%s'", ie.getMessage())); + filteredLogger.error(String.format("Http call interrupted Message: '%s'", ie.getMessage())); } catch (ExecutionException ee) { - logger.error(String.format("Execution error for http post Message: '%s'", ee.getMessage())); + filteredLogger.error(String.format("Execution error for http post Message: '%s'", ee.getMessage())); } return response; @@ -125,9 +134,9 @@ public static String convertStreamToString(InputStream is) { try { bomStripperStream = new BOMStripperInputStream(is) ; } catch (NullPointerException e) { - logger.warn(String.format("Exception creating BOMStripperInputStream: '%s'", e.getMessage())); + filteredLogger.warn(String.format("Exception creating BOMStripperInputStream: '%s'", e.getMessage())); } catch (IOException e) { - logger.warn(String.format("Exception creating BOMStripperInputStream: '%s'", e.getMessage())); + filteredLogger.warn(String.format("Exception creating BOMStripperInputStream: '%s'", e.getMessage())); } if ( null == bomStripperStream) { throw new NullPointerException("Unable to create BomStriper from the input stream"); @@ -137,7 +146,7 @@ public static String convertStreamToString(InputStream is) { try { bomStripperStream.skipBOM(); } catch (IOException e) { - logger.warn(String.format("Exception setting skip for BOMStripperInputStream: '%s'", e.getMessage())); + filteredLogger.warn(String.format("Exception setting skip for BOMStripperInputStream: '%s'", e.getMessage())); } String line = null; @@ -153,7 +162,7 @@ public static String convertStreamToString(InputStream is) { sb.append(line).append(LogHelper.LineSeparator); } } catch (IOException e) { - logger.warn(String.format("Exception reading data from Stream: '%s'", e.getMessage())); + filteredLogger.warn(String.format("Exception reading data from Stream: '%s'", e.getMessage())); } finally { tryClose( reader); @@ -171,7 +180,7 @@ private static void tryClose( T closableObject) { try { closableObject.close(); } catch (Exception e) { - logger.warn(String.format("Exception closing '%s': '%s'", closableObject.getClass(), e.getMessage())); + filteredLogger.warn(String.format("Exception closing '%s': '%s'", closableObject.getClass(), e.getMessage())); } } } diff --git a/src/main/java/net/authorize/util/SensitiveFilterLogWrapper.java b/src/main/java/net/authorize/util/SensitiveFilterLogWrapper.java new file mode 100644 index 00000000..9d32dda0 --- /dev/null +++ b/src/main/java/net/authorize/util/SensitiveFilterLogWrapper.java @@ -0,0 +1,178 @@ +package net.authorize.util; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class SensitiveFilterLogWrapper implements Log { + + private static final String SENSITIVE_CONFIG_FILE_NAME = "/AuthorizedNetSensitiveTagsConfig.json"; + + private static Pattern[] cardPatterns; + + private static Pattern[] tagPatterns; + private static String[] tagReplacements; + private static Gson gson; + private Log delegate; + + public SensitiveFilterLogWrapper(Log delegate) { + this.delegate = delegate; + + parseConfiguration(delegate); + } + + private void parseConfiguration(Log delegate) { + try { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(SensitiveDataConfigType.class, new SensitiveTagsDeserializer()); + gson = gsonBuilder.create(); + + InputStream in = getClass().getResourceAsStream(SENSITIVE_CONFIG_FILE_NAME); + Closeable closeableResource = in; + if (null != in) { + try { + InputStreamReader inputStreamReader = new InputStreamReader(in); + closeableResource = inputStreamReader; + BufferedReader reader = new BufferedReader(inputStreamReader); + closeableResource = reader; + SensitiveDataConfigType configType = gson.fromJson(reader, SensitiveDataConfigType.class); + cardPatterns = new Pattern[configType.sensitiveStringRegexes.length]; + + for (int i = 0; i < configType.sensitiveStringRegexes.length; i++) { + cardPatterns[i] = Pattern.compile(configType.sensitiveStringRegexes[i]); + } + + int noOfSensitiveTags = configType.sensitiveTags.length; + tagPatterns = new Pattern[noOfSensitiveTags]; + tagReplacements = new String[noOfSensitiveTags]; + + for (int j = 0; j < noOfSensitiveTags; j++) { + String tagName = configType.sensitiveTags[j].tagName; + String pattern = configType.sensitiveTags[j].pattern; + String replacement = configType.sensitiveTags[j].replacement; + + if (pattern != null && !pattern.isEmpty()) + tagPatterns[j] = Pattern.compile("<" + tagName + ">" + pattern + ""); + else + tagPatterns[j] = Pattern.compile("<" + tagName + ">" + ".+" + ""); + tagReplacements[j] = "<" + tagName + ">" + replacement + ""; + } + } finally { + closeableResource.close(); + } + } else { + delegate.error("Resource " + SENSITIVE_CONFIG_FILE_NAME + " not found when configuring SensitiveFilterLogWrapper"); + } + } catch (Exception e) { + delegate.error("Something went wrong configuring the SensitiveFilterLogWrapper", e); + } + } + + public static String maskCreditCards(String str) { + for (int i = 0; i < cardPatterns.length; i++) { + str = cardPatterns[i].matcher(str).replaceAll("XXXX"); + } + return str; + } + + public static String maskSensitiveXmlString(String str) { + for (int i = 0; i < tagPatterns.length; i++) { + str = tagPatterns[i].matcher(str).replaceAll(tagReplacements[i]); + } + return str; + } + + public String filterSensitiveInformation(Object messageObject) { + if (null == messageObject) { + return null; + } + + String message = messageObject.toString(); + try { + String messageWithMaskedXml = SensitiveFilterLogWrapper.maskSensitiveXmlString(message); + String messageWithMaskedCardNumber = SensitiveFilterLogWrapper.maskCreditCards(messageWithMaskedXml); + return messageWithMaskedCardNumber; + } catch (Exception e) { + return "Error masking message: " + e.getMessage(); + } + } + + public void debug(Object arg0, Throwable arg1) { + delegate.debug(filterSensitiveInformation(arg0), arg1); + } + + public void debug(Object arg0) { + delegate.debug(filterSensitiveInformation(arg0)); + } + + public void error(Object arg0, Throwable arg1) { + delegate.error(filterSensitiveInformation(arg0), arg1); + } + + public void error(Object arg0) { + delegate.error(filterSensitiveInformation(arg0)); + } + + public void fatal(Object arg0, Throwable arg1) { + delegate.fatal(filterSensitiveInformation(arg0), arg1); + } + + public void fatal(Object arg0) { + delegate.fatal(filterSensitiveInformation(arg0)); + } + + public void info(Object arg0, Throwable arg1) { + delegate.info(filterSensitiveInformation(arg0), arg1); + } + + public void info(Object arg0) { + delegate.info(filterSensitiveInformation(arg0)); + } + + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } + + public boolean isFatalEnabled() { + return delegate.isFatalEnabled(); + } + + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + public void trace(Object arg0, Throwable arg1) { + delegate.trace(filterSensitiveInformation(arg0), arg1); + } + + public void trace(Object arg0) { + delegate.trace(filterSensitiveInformation(arg0)); + } + + public void warn(Object arg0, Throwable arg1) { + delegate.warn(filterSensitiveInformation(arg0), arg1); + } + + public void warn(Object arg0) { + delegate.warn(filterSensitiveInformation(arg0)); + } +} diff --git a/src/main/java/net/authorize/util/SensitiveLogger.java b/src/main/java/net/authorize/util/SensitiveLogger.java new file mode 100644 index 00000000..38e150f7 --- /dev/null +++ b/src/main/java/net/authorize/util/SensitiveLogger.java @@ -0,0 +1,5 @@ +package net.authorize.util; + +public class SensitiveLogger { + +}