/*
* Copyright (C) 2007-2014 Christian Bockermann <chris@jwall.org>
*
* This file is part of the web-audit library.
*
* web-audit library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The web-audit library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.jwall;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jwall.web.audit.AuditEventDispatcher;
import org.jwall.web.audit.io.AuditEventReader;
import org.jwall.web.audit.io.ConcurrentAuditReader;
import org.jwall.web.audit.io.ModSecurity2AuditReader;
import org.jwall.web.audit.net.AuditEventConsoleSender;
/**
*
* This is a small collector-tool that listens for events from
* the ModSecurity audit-engine and sends them back to a running
* ModSecurity Console.
*
* The tool is given a property-file on startup. This property-file
* needs to be setup with the host, port and auth-data of the
* console that you want to feed events to.
*
* A typical property-file for reading events from a concurrent
* audit-source looks like this:
*
* <pre>
* org.modsecurity.console.host=webserver.example.com
* org.modsecurity.console.port=8886
* org.modsecurity.console.user=test
* org.modsecurity.console.pass=sensor
* org.modsecurity.collector.concurrent-log=/var/log/apache2/audit
* org.modsecurity.collector.concurrent-index=/var/log/apache2/audit/index
* </pre>
*
* This will create a collector that expects all events to be written
* to unique-files below the directory <code>/var/log/apache2/audit</code>
* and an index written to <code>/var/log/apache2/audit/index</code>.
*
* The events that are read from this place will be sent to a console
* running on <code>webserver.example.com</code> at port <code>8886</code>.
* It will authenticate to the console using the login <code>test</code>
* and password <code>sensor</code>.
*
* These properties can also be given to the collector tool on the
* commandline:
*
* <pre>
* java -Dorg.modsecurity.console.host=webserver.example.com
* -Dorg.modsecurity.console.port=8886
* -Dorg.modsecurity.console.user=test
* ...
* </pre>
*
* @author Christian Bockermann <chris@jwall.org>
*
*/
public class Collector
{
private static Logger log = LoggerFactory.getLogger( "Collector" );
public static final String COLLECTOR_SERIAL_LOG = "org.modsecurity.collector.serial-log";
public static final String COLLECTOR_CONCURRENT_LOG = "org.modsecurity.collector.concurrent-log";
public static final String COLLECTOR_CONCURRENT_INDEX = "org.modsecurity.collector.concurrent-index";
public static final String COLLECTOR_SEND_COMPLETE_LOG = "org.modsecurity.collector.send-complete-log";
public static final String COLLECTOR_CONSOLE_URL = "org.modsecurity.collector.url";
public static final String COLLECTOR_CONSOLE_USER = "org.modsecurity.collector.user";
public static final String COLLECTOR_CONSOLE_PASSWORD = "org.modsecurity.collector.password";
public final static String VERSION = "0.2.17";
public static Properties p = new Properties();
static URL url = null;
static {
try {
new URL( "https://localhost:8443/rpc/auditLogReceiver");
} catch (Exception e) {
log.info( "Internal error: " + e.getLocalizedMessage() );
e.printStackTrace();
}
}
static String user = "test";
static String pass = "sensor";
/** This field enables debug-mode by specifying <code>-Dorg.modsecurity.Collector.DEBUG=1</code> on the commandline */
public final static boolean DEBUG = System.getProperty("org.modsecurity.Collector.DEBUG") != null;
/**
*
* This method is used to create an AuditEventReader that is
* Bsed as the event-source. Based on the properties set, it
* will return an instance that implements the <code>AuditEventReader</code>-interface,
* like <code>ConcurrentAuditReader</code> or <code>ModSecurity2AuditReader</code>.
*
* @return An instance that implements the <code>AuditEventReader</code>-interface.
* @throws Exception In case an error occurs or no properties have been set.
*/
public static AuditEventReader createAuditEventReader()
throws Exception
{
boolean tail = true;
if( "true".equals( p.getProperty( Collector.COLLECTOR_SEND_COMPLETE_LOG ) ) ){
System.err.println( "Sending all audit-log entries!" );
tail = false;
}
AuditEventReader src = null;
if( "stdin".equals( p.getProperty( COLLECTOR_SERIAL_LOG ) ) ){
System.out.println( "Reading from standard input..." );
src = new ModSecurity2AuditReader( System.in, true );
return src;
}
if( p.getProperty( COLLECTOR_SERIAL_LOG ) != null ){
String input = p.getProperty( COLLECTOR_SERIAL_LOG );
if( "stdin".equalsIgnoreCase( input ) ){
System.out.println("Reading ModSecurity 2.x serial audit-log from standard input..." );
src = new ModSecurity2AuditReader( System.in );
return src;
}
File f = new File( p.getProperty( COLLECTOR_SERIAL_LOG ) );
if( ! f.canRead() )
throw new Exception("Cannot open "+f.getAbsoluteFile()+" for reading!");
src = new ModSecurity2AuditReader( f, tail );
return src;
}
if( p.getProperty( COLLECTOR_CONCURRENT_LOG ) != null && p.getProperty(COLLECTOR_CONCURRENT_INDEX) != null){
String index = p.getProperty( COLLECTOR_CONCURRENT_INDEX );
File dat = new File( p.getProperty( COLLECTOR_CONCURRENT_LOG ) );
if( ! dat.canRead() )
throw new Exception("Cannot open concurrent data-directory '"+dat.getAbsolutePath()+"' for reading!");
if( "stdin".equalsIgnoreCase( index ) ){
System.out.println( "Reading summaries from standard input..." );
src = new ConcurrentAuditReader( System.in, dat );
} else {
File idx = new File( p.getProperty( COLLECTOR_CONCURRENT_INDEX ) );
if( ! idx.canRead() )
throw new Exception("Cannot open concurrent index-file "+idx.getAbsoluteFile()+" for reading!");
src = new ConcurrentAuditReader( dat, idx, tail );
}
}
return src;
}
/**
*
* This method checks all the properties, found in the given file
* <code>pf</code>. If the file does not conform to a java-property
* file or does not contain the required properties, then
* <code>false</code> is returned.
*
* If the file is null, cannot be read or any other error occurs
* while reading the file, an exception will be thrown.
*
* The method returns <code>true</code>, iff all required properties
* are set within the file.
*
* @param pf The file to read properties from.
* @return <code>true</code> if all required properties are contained in the file.
* @throws Exception In case any error occurs while accessing the file.
*/
public static boolean checkProperties( File pf )
throws Exception
{
boolean ok = true;
p = new Properties();
File f = pf;
if( f == null )
f = new File( "sensor.properties" );
if( ! f.exists() ){
URL url = new URL( "https://localhost:8886/rpc/auditLogReceiver" );
p.setProperty( COLLECTOR_CONSOLE_USER, "test" );
p.setProperty( COLLECTOR_CONSOLE_PASSWORD, "sensor" );
p.setProperty( COLLECTOR_SERIAL_LOG, "/var/log/audit.log");
p.setProperty( COLLECTOR_CONSOLE_URL, url.toString() );
FileOutputStream fos = new FileOutputStream( f );
if( fos != null ){
p.store( fos, "Automatically created initial config. Please edit for your needs!!");
fos.close();
}
log.info("Created default configuration in "+f.getAbsoluteFile()+"!");
log.info("Please adjust config for your environment and restart.");
System.exit(0);
}
if( ! f.canRead() ){
log.info("Properties file "+f.getAbsoluteFile()+" cannot be read!");
ok = false;
} else {
log.debug("Reading properties from "+f.getAbsoluteFile());
}
try {
FileInputStream fis = new FileInputStream( f );
if( fis != null ){
p.load( fis );
fis.close();
}
} catch (Exception e) {
log.error( "Error while reading from " + f.getAbsoluteFile() + ": " + e.getMessage() );
ok = false;
}
//
// Environment variables overwrite file-properties!
//
Iterator<Object> it = System.getProperties().keySet().iterator();
while( it.hasNext() ){
String key = (String) it.next();
if( key.startsWith( "org.modsecurity" ) ) {
p.setProperty( key , System.getProperty( key ) );
log.info( "Found system-property \"" + key + "\" = " + System.getProperty( key ) );
}
}
it = p.keySet().iterator();
while( it.hasNext() ){
String key = (String)it.next();
System.err.println(" " + key + " = " + p.getProperty( key ) );
if( key.startsWith( "org.modsecurity" ) )
log.debug( "Property \"" + key + "\" = " + p.getProperty( key ) );
}
if( p.getProperty( COLLECTOR_CONSOLE_URL ) != null ){
url = new URL( p.getProperty( COLLECTOR_CONSOLE_URL ) );
log.info( "Found console url " + url );
p.setProperty( org.jwall.web.audit.net.AuditEventConsoleSender.CONSOLE_HOST, url.getHost() );
p.setProperty( org.jwall.web.audit.net.AuditEventConsoleSender.CONSOLE_PORT, url.getPort() + "" );
p.setProperty( org.jwall.web.audit.net.AuditEventConsoleSender.CONSOLE_CONNECTION_SSL, url.getProtocol().toLowerCase().startsWith( "https" ) + "" );
} else {
throw new Exception("Property \"" + COLLECTOR_CONSOLE_URL + "\" not found!" );
}
user = p.getProperty( org.jwall.web.audit.net.AuditEventConsoleSender.CONSOLE_USER );
if( user == null ){
log.warn("Warning: No user-name specified, using default ('test').");
user = "test";
}
pass = p.getProperty( org.jwall.web.audit.net.AuditEventConsoleSender.CONSOLE_PASS );
if( pass == null ){
log.warn("Warning: No password specified, using default ('sensor').");
pass = "sensor";
}
Enumeration<Object> en = p.keys();
while( en.hasMoreElements() ){
String key = (String) en.nextElement();
log.debug(key+" = "+p.getProperty(key));
}
try {
String ser = p.getProperty( COLLECTOR_SERIAL_LOG );
String con = p.getProperty( COLLECTOR_CONCURRENT_LOG );
String conIdx = p.getProperty( COLLECTOR_CONCURRENT_INDEX );
if( ser == null ){
log.debug("No serial-log specified.");
if( con == null )
log.debug("Warning: No concurrent auditlog specified!");
if( conIdx == null )
log.debug("Warning: No concurrent auditlog index file specified!");
}
if( ser == null && con == null && conIdx == null ){
log.debug("Error: No audit-event-source specified!");
throw new Exception("Missing audit-event-source!");
}
} catch (Exception e){
e.printStackTrace();
}
return ok;
}
public static String getProperty( String key ){
return p.getProperty( key );
}
/**
*
* This is the entrypoint for the Collector-tool. It expects
* <code>args[0]</code> to be the name or path of a property-file
* containing information about the remote-console and the source
* to read audit-events from.
*
* If the file does not contains the desired information, the
* Collector will exit.
*
* @param args The commandline arguments.
*
*/
public static void main(String[] args) {
try {
File pf = null;
if( args.length > 0 )
pf = new File(args[0]);
else {
InputStream in = Collector.class.getResourceAsStream( "/org/jwall/collector-usage.txt" );
if( in != null ){
BufferedReader r = new BufferedReader( new InputStreamReader( in ) );
String line = r.readLine();
while( line != null ){
System.out.println( line );
line = r.readLine();
}
r.close();
} else {
System.out.println("\nSyntax error. Please see help for instructions on starting the collector!\n" );
}
return;
}
//log.setLevel( Level.FINEST );
System.err.println("Reading settings from " + pf.getAbsolutePath() );
boolean ok = checkProperties(pf);
if( ! ok ){
log.info("Error in configuration.");
}
AuditEventReader src = createAuditEventReader();
if( src == null ){
log.info(" Error: No valid audit-event-source specified!");
log.info(" Error: Please check your config and try again.");
System.exit(-1);
}
int port = url.getPort() > 0 ? url.getPort() : 8443;
if( System.getProperty( "DEBUG" ) != null ){
log.info( "Sending events to URL " + url.toString() );
log.info( " host = " + url.getHost() );
log.info( " port = " + port );
log.info( " user = " + user );
log.info( " pass = " + pass );
}
AuditEventConsoleSender sender = new AuditEventConsoleSender( url.getHost(), port, user, pass );
AuditEventDispatcher d = new AuditEventDispatcher( src );
//
//
d.setPersistent( !"true".equals( p.getProperty( Collector.COLLECTOR_SEND_COMPLETE_LOG ) ) );
d.addAuditEventListener( sender );
d.start();
} catch (Exception e){
e.printStackTrace();
System.exit(-1);
}
}
}