Buscar este blog

sábado, 25 de junio de 2016

Apache CXF - Logging interceptor in separate log files

If you have multiple services, each one with its LoggingInInterceptor and LoggingOutInterceptor, probably you will get all messages logged in the same log file.

<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"/>    
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingIOutInterceptor"/>


<jaxws:endpoint id="service1" implementor="#springService1" address="/service1">
 <jaxws:inInterceptors>          
  <ref bean="logginInInterceptor"/>             
 </jaxws:inInterceptors>
 
 <jaxws:outInterceptors>          
  <ref bean="loggingOutInterceptor"/>             
 </jaxws:inInterceptors>
</jaxws:endpoint>


<jaxws:endpoint id="service2" implementor="#springService2" address="/service2">
 <jaxws:inInterceptors>          
  <ref bean="logginInInterceptor"/>             
 </jaxws:inInterceptors>
 
 <jaxws:outInterceptors>          
  <ref bean="loggingOutInterceptor"/>             
 </jaxws:inInterceptors>
</jaxws:endpoint>

The trick is that CXF prints each message in a specify log category, based in the service name, port name and portTypeName. These parameters are part of the own service configuration, so they will be unique for each web service.

You can see their values in the logging message (the minimum log level required is INFO). For example:
20:54:19,421 INFO  [stdout] (http-localhost/127.0.0.1:8080-2) [2016-06-25 20:54:19,420] (AbstractLoggingInterceptor.java:249) INFO http-localhost/127.0.0.1:8080-2 org.apache.cxf.services.service1.MyService1WebServiceImplPort.MyService1WebService Inbound Message ...

You can handle these categories by using your log configuration, for example, with log4j:
<logger name="org.apache.cxf.services.service1">
    <level value="INFO" />
  <appender-ref ref="FILE1" />
</logger>

<logger name="org.apache.cxf.services.service2">
    <level value="INFO" />
  <appender-ref ref="FILE2" />
</logger>

sábado, 4 de junio de 2016

Apache CXF - Logging interceptor mask sensitive information

Apache CXF has two built-in logging interceptors:

They are used when you define your service client or service provider:
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor">
 <property name="prettyLogging" value="false" />
</bean>

<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor">
 <property name="prettyLogging" value="false" />
</bean>

<jaxws:client id="myWSClient" 
 name="xxxxx"     
 serviceClass="xxxxx"
 address="xxxxx"> 
   
 <jaxws:outInterceptors>   
  <ref bean="loggingOutInterceptor" />
 </jaxws:outInterceptors>

 <jaxws:inInterceptors>
  <ref bean="loggingInInterceptor" />  
 </jaxws:inInterceptors>   
</jaxws:client>

In this way, you get a Log of the outbound and inbound messages in your application.

The problem is that you are also logging sensitive information, like user passwords, in case they are sent in plain text (often happens). So, I want to transform specific xml tags content (SOAP messages are xml based) in '*'.  For example:
Outbound Message
---------------------------
ID: 4
Address: https://desarr.local/accesosweb/servizos/entrada
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], Connection=[Keep-Alive], SOAPAction=["http://tempuri.org/ObtenerAplicaciones"]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ObtenerAplicaciones xmlns="http://tempuri.org/"><usuario><Uid>Sisifo</Uid><password>**********</password></usuario></ObtenerAplicaciones></soap:Body></soap:Envelope>
--------------------------------------

Both CXF logging interceptors extends from AbstractLoggingInterceptor. In this class there is a protected method called transform. As this javadoc states, it is meant to mask sensitive information:
Transform the string before display. The implementation in this class does nothing. Override this method if you wish to change the contents of the logged message before it is delivered to the output. For example, you can use this to mask out sensitive information.
So you only need to extend these interceptors and override this method. You receives the message before it is printed, so you have to extract the sensitive data and to convert then in whatever you want.

I created a class called LoggingTransform, which implements the same signature as the transform method. In the constructor, it receives the list o tags to filter. For example, if you want to mask '<password>myPass</password>' you identify it with 'password'. It does not have xpath query support.


LoggingTransform
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class LoggingTransform {
 private final List<Pattern> patterns;

 public LoggingTransform(final List<String> tagsToFilter) {
  patterns = new ArrayList<Pattern>();
  for (final String tagToFilter : tagsToFilter) {
   patterns.add(buildNewPattern(tagToFilter));
  }
 }

 private Pattern buildNewPattern(final String tagName) {
  return Pattern.compile(
    String.format("<%s>(.+?)</%s>", tagName, tagName),
    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
 }

 
 public String transform(final String originalLogString) {
  String filtered = originalLogString;

  for (final Pattern pattern : patterns) {
   final Matcher matcher = pattern.matcher(originalLogString);
   while (matcher.find()) {
    filtered = filter(filtered, matcher.start(1), matcher.end(1));
   }
  }
  return filtered;
 }

 private String filter(final String original, final int posIni, final int posFin) {
  return original.substring(0, posIni) + repeat("*", posFin - posIni) + original.substring(posFin);
 }

 private String repeat(final String str, final int times) {
  return new String(new char[times]).replace("\0", str);
 }
}

Then you create the two interceptors, wich extends from their respective CXF interceptors, and override the transform method. In this method you have to calle the LoggingTransform´s transform method.

FilteredLoggingInInterceptor
import java.util.List;

import org.apache.cxf.interceptor.LoggingInInterceptor;

public class FilteredLoggingInInterceptor extends LoggingInInterceptor {
 private final LoggingTransform loggingTransform;

 public FilteredLoggingInInterceptor(final List<String> tagsToFilter) {
  loggingTransform = new LoggingTransform(tagsToFilter);
 }

 @Override
 public String transform(final String originalLogString) {
  return loggingTransform.transform(originalLogString);
 }
}

FilteredLoggingOutInterceptor
import java.util.List;

import org.apache.cxf.interceptor.LoggingOutInterceptor;

public class FilteredLoggingOutInterceptor extends LoggingOutInterceptor {
 private final LoggingTransform loggingTransform;

 public FilteredLoggingOutInterceptor(final List<String> tagsToFilter) {
  loggingTransform = new LoggingTransform(tagsToFilter);
 }

 @Override
 public String transform(final String originalLogString) {
  return loggingTransform.transform(originalLogString);
 }
}

And finally, declare your new interceptors.
<bean id="loggingOutInterceptor" class="es.sisifo.cxf.logging.FilteredLoggingOutInterceptor">
 <constructor-arg name="tagsToFilter">
  <list>
   <value>password</value>
   <value>otherSensitiveTag</value>
  </list>
 </constructor-arg>
 <property name="prettyLogging" value="false" />
</bean>


<bean id="loggingInInterceptor" class="es.sisifo.cxf.logging.FilteredLoggingInInterceptor">
 <constructor-arg name="tagsToFilter">
  <list>
   <value>password</value>
   <value>otherSensitiveTag</value>   
  </list>
 </constructor-arg>
 <property name="prettyLogging" value="false" />
</bean>