Protección antivirus en Alfresco ECM (segunda parte)

En la primera parte se vio como implementar una solución de antivirus escaneando todo el repositorio y que podía programarse por ejemplo en la “crontab” del sistema operativo.
Otra forma de realizar la detección de un virus que suba a Alfresco es mediante un escaneo “on-demand”, es decir, bajo demanda, y en este sentido cuando sea actualizado el contenido del documento. Dentro de las posibilidades que tenemos, de realizarlo de esta forma, podemos enviar un stream de datos del documento hacia el antivirus o bien ejecutar el propio antivirus pasándole exactamente el fichero a escanear. Ambas soluciones son posibles en ClamAV y en cualquiera de los dos casos el antivirus devuelve un código de error que será 0 si no hay infección y distinto si ha sido detectado, el fichero no existe, etc.
Por otra parte, Alfresco ECM disponde de las llamadas “policies” para ejecutar clases de Java o scripts de JavaScript cuando se produce algún “evento”. De todos los posibles tanto a nivel de contenido y de nodos, vamos a usar los siguientes:
Interface
Method
org.alfresco.repo.content.ContentServicePolicies
onContentUpdate
onContentRead
Usando OnContentUpdate y OnContentRead se puede lanzar la detección cuando sean leídos los documentos y/o cuando son actualizados. En el ejemplo se va a utilizar el evento OnContentUpdate para que cuando se realice una actualización de este (en el momento en el que se hace un COMMIT) se lance el antivirus y si este devuelve un código de error se añada un aspecto “infected” con dos propiedades, la fecha de detección y si ha sido “limpiado”.

Primero definimos los beans que usamos en el «behavior»: alfviral-behavior-context.xml

    
       
            true
        property>
       
           
                classpath:alfresco/extension/alfviral-behavior.properties
           
       
   
   
    <bean id="AlfViralBehavior" class="com.fegor.alfresco.behavior.OnUpdateReadScan"
        init-method=»init»>
       
           
       
       
           
       
       
           
       
       
            ${alfviral.command}
       
       
            ${dir.contentstore}
                  
    bean>

Seguidamente del fichero de propiedades: alfviral-behavior.properties

alfviral.command=/usr/bin/clamscan

Montamos el modelo de datos: alfviral-model-context.xml

       
   
       
           
                alfresco/extension/alfviralModel.xml
           
       
     

El modelo: alfviralModel.xml

     
   Alfresco Virus Alarm Model
   Fernando González Ruano (twitter://fegorara)
   1.0
  
     
     
  
  
     
  
   
       
            Infected
           
               
                    d:date
                    false
               
               
                    d:boolean
                    false
               
           
       
   

La parte para el cliente web del browser: web-client-config-custom.xml


  
     
        
        
     
  
  
     
        
     
  

El fichero de propiedades para las etiquetas: webclient.properties

ava_date=Fecha de detecciu00F3n
ava_clean=u00BFDesinfectado?

Y por último la clase java: OnUpdateReadScan.java

package com.fegor.alfresco.behavior;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.policy.Behaviour;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.log4j.Logger;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.ContentReader;

/**
 * Integrates antivirus scanning documents for alfresco
 *
 * Implements the policies of «OnContentUpdate» and «OnContentRead».
 *
 * @author Fernando González Ruano (fegor)
 */
public class OnUpdateReadScan
    implements ContentServicePolicies.OnContentUpdatePolicy,
                ContentServicePolicies.OnContentReadPolicy
{
    private Logger logger = Logger.getLogger(OnUpdateReadScan.class);

    // behaviours
    private Behaviour onContentUpdate;
    private Behaviour onContentRead;
   
    // dependencias
    private PolicyComponent policyComponent;
    private ContentService contentService;
    private NodeService nodeService;
   
    // configuration
    private List command;
    private String store;
    private final String NAMESPACE_ALFVIRAL_CONTENT_MODEL = «alfviral.model»;
    private final QName ASPECT_INFECTED = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «infected»);
    private final QName PROP_INFECTED_DATE = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «date»);
    private final QName PROP_INFECTED_CLEAN = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «clean»);  
   
    Map aspectValues = new HashMap();

    // método de inicio
    public void init ()
    {
        if (logger.isDebugEnabled()) logger.debug(«Start OnUpdateReadScan.»);
       
        // crear behaviours
        this.onContentUpdate = new JavaBehaviour(this,
                «onContentUpdate»,
                NotificationFrequency.TRANSACTION_COMMIT);
       
        this.onContentRead = new JavaBehaviour(this,
                «onContentRead»,
                NotificationFrequency.TRANSACTION_COMMIT);
       
        // binding «policies»
        this.policyComponent.bindClassBehaviour(QName.createQName
                (NamespaceService.ALFRESCO_URI,
                «onContentUpdate»),
                «cm:content», this.onContentUpdate);
       
        this.policyComponent.bindClassBehaviour(QName.createQName
                (NamespaceService.ALFRESCO_URI,
                «onContentRead»),
                «cm:content», this.onContentRead);
    }
   
    @Override
    public void onContentUpdate (NodeRef nodeRef, boolean flag)
    {
        ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
       
        // full path of file
        String contentUrl = contentReader.getContentUrl();
        String contentPath = contentUrl.replaceFirst(«store:/», this.store);
       
        if (logger.isDebugEnabled())
        {
            logger.debug(«(Update) «+this.command+» «+contentPath);
        }
        else if (logger.isInfoEnabled())
        {
            logger.info(«(Update) Llamando al script de escaneo de virus para «+nodeRef.getId());
        }
       
        try
        {
            // execute command «antivir contentPath»
            this.command.add(contentPath);
            ProcessBuilder pb = new ProcessBuilder(this.command);
            Process process = pb.start();
           
            int intResult = process.waitFor();

            logger.debug(«(Update) Resultado del escaneo: «+intResult);
           
            // if result is not 0, file is infected
            if (intResult != 0)
            {
                logger.info(«ALERTA ** El fichero: «+contentReader.getContentUrl()+» está infectado. **»);
               
                // add aspect Infected is not assigned              
                if (!nodeService.hasAspect(nodeRef, this.ASPECT_INFECTED))
                {  
                    this.aspectValues.put(this.PROP_INFECTED_DATE, new Date());
                    this.aspectValues.put(this.PROP_INFECTED_CLEAN, false);               
                    nodeService.addAspect(nodeRef, this.ASPECT_INFECTED, this.aspectValues);
                   
                    // TODO Other actions… quarantine, delete, send email, etc.
                }
                else
                {
                    logger.debug(«Este fichero se detectó como infectado anteriormente.»);                   
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
   
    @Override
    public void onContentRead (NodeRef nodeRef)
    {
        // TODO Actions for read content
    }
   
    // Setters…
    public void setPolicyComponent(PolicyComponent policyComponent)
    {
        this.policyComponent = policyComponent;
    }
   
    public void setContentService(ContentService contentService)
    {
        this.contentService = contentService;
    }
   
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
   
    public void setStore(String store)
    {
        this.store = store;
    }
   
    public void setCommand(List command)
    {
        this.command = command;
    }   
}

A partir de aquí…

Esto es solo un ejemplo de la multitud de acciones y configuraciones que pueden realizarse en este sentido, por ejemplo, falta el código para el evento OnContentRead, falta alguna acción de mover los documentos infectados a algún espacio de cuarentena, avisar al administrador y al usuario de la detección, internacionalizar, etc. pero creo que es un buen punto de partida para que cada cual adapte esta solución a su manera. No os lo voy a dar todo hecho ¿verdad?… 😉

Finalmente dos capturas de pantalla:


Un extracto del log (poniendo en el log4j.properties el valor log4j.logger.com.fegor=debug):

 

10:44:16,141 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) [/usr/local/bin/clamscan, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/8/3/30a7961f-cdef-4410-9ba0-ca94a8542d03.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin] /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin
10:44:20,125 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) Resultado del escaneo: 1
10:44:20,126 User:admin INFO  [alfresco.behavior.OnUpdateReadScan] ALERTA ** El fichero: store://2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin está infectado. **

Y algo de bibliografía que podéis consultar:

Libro: Alfresco Developer Guide
Autor: Jeff Potts
ISBN: 978-1-847193-11-7

Web de Jeff Potts: http://ecmarchitect.com/
Wiki de Alfresco: http://wiki.alfresco.com/wiki/Policy_Component
Web de ClamAV: http://www.clamav.net/lang/en/

2 respuestas en “Protección antivirus en Alfresco ECM (segunda parte)”

  1. Hola Nathalie,

    Veo que hablas de la primera parte del artículo. En esta uso un WebScript para realizar la tarea de detección y en caso de infección renombrar el fichero. Debería funcionar tanto en el Explorer como en Share ya que es a nivel de repositorio. No obstante lo miraré y corrijo si veo algo.

    Te recomiendo que uses mejor la segunda parte del artículo y por tanto el AlfVirAl como módulo que lo ha hace es usar el comportamiento o «policies» para escanear y asignar un aspecto en caso de infección.

    Tengo pendiente crear una nueva versión que también tenga un módulo para Share y poder ver también en este los metadatos del aspecto así como alguna funcionalidad más.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *