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>

lunes, 16 de mayo de 2016

PostgreSQL - ERROR: text search configuration "public.default_spanish" does not exist

This error appears when you try to make a search and there is no search engine configured. With text search, postgreSQL is able to parse string fields and split their content in text elements.

The first step is to configure the text search (http://www.postgresql.org/docs/9.5/static/sql-createtsconfig.html). Just execute the following sentence in the query interpreter:

CREATE TEXT SEARCH DICTIONARY default_spanish (
    TEMPLATE = pg_catalog.ispell,
    dictfile = 'es_es', afffile = 'es_es', stopwords = 'es_es' );


Now, when you repeat the text query you will get the following error:
ERROR:  no se pudo abrir el archivo de diccionario «C:/Program Files (x86)/PostgreSQL/9.4/share/tsearch_data/es_es.dict»: No such file or directory


So, you need more things:
  • A dictionary. This file contains a list of words used in this language, but wich are not stop words.
  • A list of stop words. Stop words are used to eliminate very frequent words that contain no or little information to help discriminate the text they occur in.
  • A list prefix/sufix (affix). This file contains common variations of words in this language.

The dictionary and the affix file can be found here: http://fmg-www.cs.ucla.edu/geoff/ispell-dictionaries.html

The stop words can be found here: http://snowball.tartarus.org/

Just put this files in the directory shown in the previous error string.

viernes, 29 de abril de 2016

Maven - Log4j2 with slf4j

How to use log4j2 logging over the simple log facade (slf4j).

pom.xml
<dependencies>

  <dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-slf4j-impl</artifactId>
   <version>2.5</version>
  </dependency>



  <dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.5</version>
  </dependency>

  <dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.5</version>
  </dependency>
  
 </dependencies>


You need to configure de log4j2 impl, for example, by using a log4j2.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
 <Appenders>
  <Console name="CONSOLE" target="SYSTEM_OUT">
   <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
  </Console>

  <RollingFile name="FILE" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz" ignoreExceptions="false">
   <PatternLayout>
    <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
   </PatternLayout>
   <TimeBasedTriggeringPolicy />
  </RollingFile>
 </Appenders>
 
 
 <Loggers>
  
  <Logger name="com.foo.Bar" level="trace" additivity="false">
      <AppenderRef ref="CONSOLE" />
   <AppenderRef ref="FILE" />
    </Logger>
 
  <Root level="INFO">
   <AppenderRef ref="CONSOLE" />
   <AppenderRef ref="FILE" />
  </Root>
 </Loggers>
</Configuration>

domingo, 24 de abril de 2016

@Firma - Firma trifase con Autofirma 1.4.2

Según se indica en su página oficial, @firma es una plataforma de validación y firma electrónica multi-PKI desarrollada por el MINHAP, y que se pone a disposición de las Administraciones Públicas, proporcionando servicios para implementar la autenticación y firma electrónica avanzada de una forma rápida y efectiva.

Originariamente @firma sólo ofrecía un applet de firma para hacer firma en cliente. De este modo las aplicaciones incluían una serie de librerías y javascript en sus páginas para ejecutar el este applet, que era el que se encargaba de acceder a los certificados del usuario.

Con la progresiva caída en desuso de los applets se ha potenciado el uso de un componente de usuario denominado Autofirma. Se trata de una aplicación independiente que debe instalar manualmente el usuario y que es quien en última instancia realiza la firma.

El caso más básico de funcionamiento consiste en que desde la página web se invoque el componente de autofirma pasándole el fichero que se desea firmar (en base 64), se firme en local, y luego se envíe de vuelta a la página. De ahí, volvería finalmente al servidor.

Este modelo de integración es válido cuando los ficheros son relativamente pequeños, pero para documentos mayores implica un tráfico de red excesivo. Además, en algunos dispositivos móviles no se permite la descarga de documentos, como por ejemplo en iOS.

Para solventar estos problemas se dispone de la opción de firma trifase. En estos casos, el documento nunca llega a salir del servidor remoto y la firma se realiza de forma conjunta entre autofirma y una nueva aplicación web denominada servidor trifase.

Cuando desde página web se invoca a autofirma (a través de invocación por protocolo), se le pasa como parámetro la URL del servidor trifase y un identificador que hace referencia al fichero remoto que se quiere firmar. Autofirma establece una comunicación con el servidor trifase, indicándole este identificador y se intercambian mensajes para componer una firma e incrustrarla en en el documento resultante.

En la siguiente imagen se muestra el diagrama general de funcionamiento.


El componente autofirma y el server trifase se pueden descargar de la página de la forma del CTT, http://forja-ctt.administracionelectronica.gob.es/web/clienteafirma. Desde ahí se puede ir a la página de la forja.

Además, el código fuente del server trifase (como el del resto del proyecto) se puede consultar en su github https://github.com/ctt-gob-es/clienteafirma.

Para hacer pruebas lo mejor es descargar el server trifase y modificar su config.properties para que emplee acceda a una ruta de disco, que servirá de entrada y salida de los ficheros firmados. El fichero de configuración está en WEB-INF/classes/config.properties:
# Origenes permitidos
Access-Control-Allow-Origin=*

# Clase DocumentManager
#document.manager=es.gob.afirma.triphase.server.document.SelfishDocumentManager
document.manager=es.gob.afirma.triphase.server.document.FileSystemDocumentManager

# Instalar provedor de XMLdSig alternativo
alternative.xmldsig=false

# Configuracion de la clase FileSystemDocumentManager
indir=D:/tmp/afirma/entrada
outdir=D:/tmp/afirma/salida
overwrite=true

Luego basta con desplegarlo tal cual en un servidor de aplicaciones, por ejemplo tomcat.

Al descargar el componente de autofirma también se incluye una página para probar todas las opciones disponibles de firma. He cogido esa página y la he recortado para dejar únicamente la parte de firma trifase.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <!-- Ejemplo basico de lanzador de la aplicacion -->
  <head>
 <title>Ejemplo de despliegue del MiniApplet @firma</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
 <script type="text/javascript" src="miniapplet.js"></script>
 <script type="text/javascript">

  function doSign() {
   try {
    MiniApplet.sign(
     document.getElementById("idFicheroServidor").value,
     "SHA512withRSA",
     "PAdEStri",
     document.getElementById("params").value,
     showResultCallback,
     showErrorCallback);
    
   } catch(e) {
    try {
     showLog("Type: " + MiniApplet.getErrorType() + "\nMessage: " + MiniApplet.getErrorMessage());
    } catch(ex) {
     showLog("Error: " + e);
    }
   }
  }
  
  function showResultCallback(signatureB64, certificateB64) {
   showLog("Firma OK");
   document.getElementById('result').value = signatureB64;
   document.getElementById('certificate').value = certificateB64;
  }
 
  function showErrorCallback(errorType, errorMessage) {
   showLog("Type: " + errorType + "\nMessage: " + errorMessage);
  }
    
  function showAppletLog() {
   try {
    showLog(MiniApplet.getCurrentLog());
   } catch(e) {
    showLog("Type: " + MiniApplet.getErrorType() + "\nMessage: " + MiniApplet.getErrorMessage());
   }
  }
  
  function cleanDataField(dataField, textDiv) {
   textDiv.innerHTML = "";
   dataField.value = null;
  }
  
  function addExtraParam(extraParam) {
   var paramsList = document.getElementById("params");
   paramsList.value = paramsList.value + extraParam + "\n";
   document.getElementById('newParam').value = "";
  }
  
  function cleanExtraParams() {
   document.getElementById("params").value = "";
   document.getElementById('newParam').value = "";
  }
  
  function showLog(newLog) {
   document.getElementById('console').value = document.getElementById('console').value + "\n" + newLog;
  }
 </script>
  </head>
 <body>
  <script type="text/javascript">
   MiniApplet.setForceWSMode(false);
   MiniApplet.cargarAppAfirma();
  </script>

  
  <fieldset><legend>Entrada de datos</legend>
  <div>
    <span>Identificador de fichero en servidor:</span> <input id="idFicheroServidor" type="text" value="prueba.pdf">      
  </div>
  </fieldset>
  <br/>
  
  <fieldset><legend>Configuraci&oacute;n de la firma</legend>  
   <div>
     <label for="newParam">ExtraParams</label>
     <input id="newParam" type="text"><input type="button" value="Agregar" onclick="addExtraParam(document.getElementById('newParam').value);">&nbsp;
     <input type="button" value="Limpiar" onclick="cleanExtraParams();">&nbsp;
     <span>(Insertar las propiedades de una en una)</span>
     <br>
    <textarea id="params" cols="50" rows="5" readonly>serverUrl=http://localhost:8080/afirma-server-triphase-signer/SignatureService
    </textarea>
   </div>
  </fieldset>
  <br/>

  <input type="button" value="Firmar" onclick="doSign();">&nbsp;  
  <input type="button" value="Mostrar Log" onclick="showAppletLog();">
  <br/>
  
  <div>
   <span>Consola</span>
   <br>
   <textarea id="console" cols="150" rows="10">
   </textarea>
  </div>
  
  <div>
   <span>Resultado</span>
   <br>
   <textarea id="result" cols="150" rows="10">
   </textarea>
  </div>
  
  <div>
   <span>Certificado</span>
   <br>
   <textarea id="certificate" cols="150" rows="10">
   </textarea>
  </div>
 </body>
</html>

Esta página invoca la firma trifase de tipo PAdES (firma de PDF).


El único parámetro que hay que cubrir es el identificador del fichero, que será el nombre de un fichero que se encuentre en el directorio de entrada configurado dentro del server trifase.

Para ver los mensajes intercambiados entre autofirma y el server trifase se puede utilizar un filtro de log de tomcat como el describía en una entrada anterior http://trabajosdesisifo.blogspot.com.es/2016/03/tomcat-custom-request-dump-filter-log.html