Archive for the ‘Scripting’ Category

Advanced Intrusion Detection Environment

Monday, March 2nd, 2009

Advanced Intrusion Detection Environment (AIDE) is a file integrity checker for UNIX operating systems. Its purpose to provide reporting on the integrity of data on supported file systems. It is essentially the open source version of Tripwire. While you can use a tool such as Splunk to perform file integrity monitoring it requires more overhead on the server (running splunkd all the time) whereas AIDE is run only when it executed. This post will show you how you can install and setup a quick script to run AIDE on a target host from a trusted host (using ssh keys). By running AIDE multiple times on the target host you can determine what files are changing. Also, by using a trusted host you keep any data off of the target host free from being compromised (in theory).

The Environment

1. Target host
2. Trusted host (authorized to ssh as root using a public key to the target host)

The Concept

Here is a simple diagram of what we are going to setup.

aidedesign

Install AIDE using yum

You can install aide using yum or you can download it from SourceForge


#yum install aide

or

#wget http://superb-west.dl.sourceforge.net/sourceforge/aide/aide-0.13.1.tar.gz
#tar -xvzf aide-0.13.1.tar.gz
....
#cd aide-0.13.1
#./configure
...
#make
...
#make install
...

Install the runaide.ksh script

First, make the directories you need.


# mkdir -p /app/aide
# for x in store conf report bin; do mkdir /app/aide/$x; done

On your trusted host copy the following into a file called runaide.ksh


#!/bin/ksh -x
#
# Author: jameslabocki@gmail.com
# Date: 05.12.08
#
# A simple script that performs the following
# 1. Secure copies an aide configuration to each host
# 2. Executes an aide init via ssh on each host
# 3. Secure copies the aide database to the store directory
# 4. Performs a compare of the two databases and records the results
# 5. Emails the results to a specified email address
#
WHOTOMAIL=linux.admins@domain.com,infosec@domain.com
#
#Important variables :)
TS=`date '+%m%d%y'`
YESTERDAY=`date -d "1 day ago" '+%m%d%y'`
TWODAYS=`date -d "2 days ago" '+%m%d%y'`
ROOTDIR=/app/aide
REPODIR=${ROOTDIR}/store
CONFDIR=${ROOTDIR}/conf
REPORTDIR=${ROOTDIR}/report
BINDIR=${ROOTDIR}/bin
HOSTLIST=${CONFDIR}/hostlist
SSH=/usr/bin/ssh
#
#Almost as important variables :)
AIDE=/usr/sbin/aide
HOSTCONFDIR=/root
HOSTCONFFILE=${HOSTCONFDIR}/aide.cfg
HOSTDBFILE=/var/lib/aide/aide.db.new.gz
#
#Loop through the hosts and create database, copyi
for host in `cat $HOSTLIST`; do
scp ${CONFDIR}/aide.cfg ${host}:${HOSTCONFFILE}
ssh ${host} "${AIDE} --init -c ${HOSTCONFFILE}"
scp ${host}:${HOSTDBFILE} ${REPODIR}/${host}.${TS}.gz
#
if [ ! -f ${REPODIR}/${host}.${YESTERDAY}.gz ]; then
echo "${host}.${YESTERDAY}.gz does not exist yet" |mail -s "Aide Report for ${host}" ${WHOTOMAIL}
else
#Shortcoming in aide having to use config files for everything
cp ${CONFDIR}/aidecompare.cfg ${REPODIR}/${host}.${TS}.cfg
cat ${REPODIR}/${host}.${TS}.cfg |awk '{sub(/todaysdatabase/,'"\"${host}.${TS}.gz\""');print}' |awk '{sub(/yesterdaysdatabase/,'"\"${host}.${YESTERDAY}.gz\""');print}' > ${REPODIR}/${host}.${TS}.cfg.tmp
${AIDE} --compare -c ${REPODIR}/${host}.${TS}.cfg.tmp > ${REPORTDIR}/${host}.${TS}.log
cat ${REPORTDIR}/${host}.${TS}.log |mail -s "Aide Report for ${host}" ${WHOTOMAIL}
fi
#
#remove the db from two days ago
rm ${REPODIR}/${host}.${TWODAYS}.gz
#
done

The script looks for a file named hostlist in /app/aide/conf and then runs through the list of hosts copying over an aide.cfg file and running the aide executable on each host. This means that you can customize the aide.cfg in one place for your environment and not worry about maintaining the configuration file across machines. Once you have a hostlist and a general configuration file you can execute the script.

Further Development
I also setup the reports directory to be served over http through apache and authenticated against Active Directory using mod_authz_ldap. This is nice because you can allow a third party to review the reports on a daily basis.

Here are a few helpful crontab entries after you have the script running.


#Remove old aide reports files
30 1 * * * /usr/bin/find /app/aide/store/ -mtime +10 -exec rm {} \;
30 1 * * * /usr/bin/find /app/aide/report/ -mtime +30 -exec rm {} \;
#Aide job that audits file changes on systems at 3AM each day
0 3 * * * /app/admin/servicedelivery/linux/admin/bin/runaide.ksh

Splunk to Jira for PCI

Wednesday, February 25th, 2009

PCI DSS 10.6 requires that logs are reviewed on a daily basis. If the proper alerting mechanisms are in place you can bypass the need to look through thousands of lines of logs, but you still need a trail of what actions are taken after an alert is received.

PCI DSS 10.6

10.6
Are logs for all system components reviewed at least daily? Log reviews must include those servers that perform security functions like intrusion detection system (IDS) and authentication, authorization, and accounting protocol (AAA) servers (for example, RADIUS). Note: Log harvesting, parsing, and alerting tools may be used to achieve compliance with Requirement 10.6.

I am using XMLRPC and a perl script to automatically create issues in Jira when a Splunk alert is triggered. Although you can use Splunk to perform log review by tagging events I thought it would be more beneficial to use our currently installed work flow system so that we don’t have to allow everyone to log into Splunk and we could use some of the other features inside of Jira as well. So what do you need to do to make this work?

1. Install necessary CPAN modules (assuming you already have perl) on your splunk server.


#cpan -e
cpan> install XMLRPC::Lite
...
cpan> install Data::Dumper
...

1.b Enable XMLRPC in Jira under administration -> Plugins -> RPC Plugin.

splunkjirapci1

2. Copy the following script in $SPLUNK_HOME/bin/scripts/openJiraTicket.pl


#!/usr/bin/perl
#
# * Your search can trigger a shell script.
# * Specify the name of the shell script to run.
# * Place the script in $SPLUNK_HOME/bin/scripts.
# * Command line arguments passed to the script are:
# * $0 = script name.
# * $1 = number of events returned.
# * $2 = search terms.
# * $3 = fully qualified query string.
# * $4 = name of saved splunk.
# * $5 = trigger reason (i.e. "The number of events was greater than 1").
# * $6 = link to saved search.
# * $7 = a list of tags belonging to this saved search.
# * $8 = file where the results for this search are stored (contains raw results).
use strict;
use warnings;
#
use XMLRPC::Lite;
use Data::Dumper;
#
my $summary = $ARGV[3];
my $description = "An alert was triggered in splunk with the following information:" . "\n\nSearch String: " . $ARGV[1] . "\nNumber of Results:" . $ARGV[0] . "\nAlert Name:" . $ARGV[3] . "\nReason Alarm Triggered:" . $ARGV[4] . "\n\nLink to Search:" . $ARGV[5];
#
my $jira = XMLRPC::Lite->proxy('http://jira.domain.com:8080/rpc/xmlrpc');
my $auth = $jira->call("jira1.login", "username", "password")->result();
my $call = $jira->call("jira1.createIssue", $auth, {
'project' => 'AI',
'type' => 30,
'summary' => $summary,
'description' => $description,
'assignee' => 'usertoassignto',
});
my $fault = $call->fault();
if (defined $fault) {
die $call->faultstring();
} else {
print "issue created:\n";
print Dumper($call->result());
}
$jira->call("jira1.logout", $auth);

3. Add a new input to your savedsearches.conf file in $SPLUNK_HOME/etc/system/local/savedsearches.conf. Note the action_script is set to openJiraTicket.pl which means whenever the relation field is met the script openJiraTicket.pl will be executed.


[PCIDSS-10_3_4-LinuxFailedLogins]
action_email = linux.admins@domain.com
action_rss = 0
action_script = openJiraTicket.pl
counttype = number of events
enableSched = 1
quantity = 0
relation = greater than
role = productionLinux
schedule = 0 * * * *
search = index=production pam_unix failure startminutesago=60
sendresults = 1
userid = myuser

4. Now create a few failed login attempts via ssh to a server that is logging to Splunk and you should see an issue get opened in Jira by Splunk.

splunkjirapci2

Some other enhancements that can be made to this setup are:

1. Creating an event inside of Splunk itself when the issue is opened in Jira. Then log when the Jira ticket is closed to Splunk as well and measure the variance in time of the opening and closing of issues as a KPI of the operations staff.

2. Extend the functionality of the perl script with an array (or maybe use sqlite) to match the alert name in Splunk to a component in Jira. This would allow you to compartmentalize the alerts into groups in Jira (i.e windows in one component, linux in another, network in another, etc).

3. Use encrypted password in the perl script and use XMLRPC over ssl. I will be doing this shortly. A quick howto

Encrypt your password file

openssl rand 32 -out key.temp
openssl des3 -e -pass file:key.temp -in passwd -out passwd.enc
openssl des3 -d pass file:key.dec -in passwd.enc -out passwd.dec
openssl des3 -d -pass file:key.dec -in passwd.enc -out passwd.dec
openssl des3 -d -pass file:key.temp -in passwd.enc -out passwd.dec

Unencrypt your password from within the script using the key. I learned chomp is VERY important ;)

if ($enpasswd) {
$passwd = `openssl des3 -d -pass file:/etc/pki/tls/private/dbi.key -in $enpasswd |awk NF`;
chomp($passwd);
}

Configuring Oracle ASM on Enterprise Linux 5

Friday, February 20th, 2009

Configuring Oracle ASMLib on Multipath Disks

There are two issues with using multipath disks in ASM. ASM cannot handle seeing the same disk twice. If it does, it will cause an error. A single disk can appear three times in a multipath configuration:

1. The first path to the disk
2. The second path to the disk
3. The multipath access point

Here’s an example: say a system has one local disk, /dev/sda, and one disk attached via external storage. The machine has two connections, or paths, to that external storage. The Linux SCSI driver will see both paths. They will appear as /dev/sdb and /dev/sdc. The system may access sdb or sdc, the access ends up in the same place.

If multipathing is enabled, there will be a multipath disk, for example /dev/multipatha, that can access both paths. That is, any I/O to multipatha can use either path. If a system were using the sdb path, and that cable is unplugged, the system will get an error. But the multipath disk will know to switch to the sdc path.

Most system software will be unaware of the multipath configuration. It can use any of the paths, sdb, sdc, or multipatha, and it won’t know the difference. ASMLib is the same way. The default configuration does not care which path it uses.

ASMLib will choose only one of the paths, because ASM cannot handle seeing the same disk twice. This solves the first issue. ASM only sees one path, and it is happy. Here is the second issue: which path does ASM see? In its default configuration, ASMLib will choose the first path it finds. This is the first path as reported by Linux. Depending on your storage driver, it could be the multipath, or it could be one of the single paths.

The system administrator wants ASMLib to always use the multipath disk. What’s the point of having it if Oracle is not using it? There is no way, however, for ASMLib to know what a multipath looks like. It must be told via its configuration.

Disk Scan Ordering

Disks are marked for ASMLib using a process described in ASMLib Installation. ASMLib learns what disk are marked during a process called disk scanning. ASMLib runs this scan every time it starts up. The system administrator can also force a scan via the /etc/init.d/oracleasm scandisks command.

ASMLib examines each disk in the system. It checks if the disk has been marked for ASMLib. Any disk that has been marked will be made available to ASMLib. Normally, ASMLib checks each disk in the exact order the OS lists them. This is fine for most systems.

In the last section, we described a case where the OS order isn’t good enough. The system administrator wants ASMLib to see multipath disks before it sees single path disks. This allows ASMLib to choose the multipath disk and make it available to Oracle.

ASMLib allows two modifications to the disk scan order. First, it allows exclusion of certain disks. In other words, ASMLib will ignore those disks completely. Second, the system administrator can specify disks that are to be scanned first. Disks in this list are scanned before the rest of the disks in the system.

A multipath configuration can use either facility. The system administrator could choose to exclude all the single path disks. ASMLib will then ignore them, only scanning the multipath disks. Conversely, the administrator could specify that the multipath disks are scanned first. ASMLib will see them first, and prefer them over the single paths.

Configuring the Scan Order

The Oracle ASMLib configuration file is located at /etc/sysconfig/oracleasm. It contains all the startup configuration that the system administrator specified via the /etc/init.d/oracleasm configure command. That command cannot configure scan ordering.

The configuration file contains many configuration variables. The ORACLEASM_SCANORDER variable specifies disks to be scanned first. The ORACLEASM_SCANEXCLUDE variable specifies the disks that are to be ignored.

The variables take a whitespace-seperated list of prefix strings to match. In other words, if a disk starts with one of the prefix strings, it matches. For example, the prefix string sd will match all SCSI devices. The disk sda starts with sd. Note that these are not globs. They do not use wildcards. They are simple prefixes. Also note that the /dev/ path is not part of the prefix.

NOTE: When scanning, only the device names known by the kernel are scanned. With device-mapper, the kernel sees the devices as /dev/dm-XX. The /dev/mapper/XXX names are created by udev for human readability. Any configuration of ORACLEASM_SCANORDER or ORACLEASM_SCANEXCLUDE must use the dm prefix.

A couple examples:

Multipath Disks First

The system administrator configures ASMLib to scan the multipath disks first. In the ASMLib configuration file she edits the ORACLEASM_SCANORDER variable to look like so:

ORACLEASM_SCANORDER="multipath sd"

During a scan, ASMLib first tries all disks that start with “multipath”. The multipath device /dev/multipatha certainly matches. It is scanned first. Next, ASMLib tries all disks that start with “sd”. This is all the SCSI disks. The local disk /dev/sda will be scanned, but it is not an ASM disk. The single path disks /dev/sdb and /dev/sdc are also scanned. They are ASM disks, but ASMLib will see that it already has a path to that disk. It will ignore them. Finally, ASMLib will scan any other disks that did not match either prefix.

Excluding Single Path Disks

The system administrator configures ASMLib to ignore the single path disks. In the ASMLib configuration, he edits the ORACLEASM_SCANEXCLUDE variable to look like so:

ORACLEASM_SCANEXCLUDE="sdb sdc"

Here, the system administrator has been more specific. ASMLib should ignore exactly the disks /dev/sdb and /dev/sdc. It should not ignore other SCSI disks. While scanning, ASMLib will ignore those paths, only seeing the /dev/multipath disk. Once again, Oracle will use the multipath disk.

EMC PowerPath and ASMLib
A lot of administrators want to use EMC PowerPath for multipathing and ASMLib for disk access.

However, PowerPath and the 2.4 kernels are not supported by EMC. Systems with the Linux 2.6 kernel, such as RHEL 4 or SLES 9, and the 2.0 ASMLib kernel driver are supported. Please reference the EMC Support Matrix to verify that any/all interoperability requirements are met.

If you have ANY issue related to support for ASMLib and PowerPath on systems using the Linux 2.4 kernel, such as RHEL 3 or SLES 8, please contact EMC.

In order to keep ASMLib from scanning local disks first run fdisk to determine how many local disks you have.


fdisk -l |grep sd |grep -v bytes |awk -F" " '{print $1}' |cut -c 6-8 |sort -n |uniq
sda
sdb
sdc
sdd
sde
sdf
sdg

Place the out from the fdisk command in the /etc/sysconfig/oracleasm.

ORACLEASM_SCANEXCLUDE="sda sdb sdc sdd sde sdf sdg"

Warning!
Once this is complete the oracleasm service must be restarted. Make sure this action is coordinated with a DBA so Oracle is shutdown on the node being modified!


[root@hostname~]# /etc/init.d/oracleasm listdisks
QAORADATA
QAORALOG
[root@hostname~]# /etc/init.d/oracleasm querydisk QAORADATA
Disk "QAORADATA" is a valid ASM disk on device [8, 33] <-- local disks so failover won't work
[root@hostname~]# /etc/init.d/oracleasm querydisk QAORALOG
Disk "QAORALOG" is a valid ASM disk on device [8, 49] <-- local disks so failover won't work
[root@hostname~]# /etc/init.d/oracleasm stop
Unmounting ASMlib driver filesystem: [ OK ]
Unloading module "oracleasm": [ OK ]
[root@hostname~]# /etc/init.d/oracleasm start
Loading module "oracleasm": [ OK ]
Mounting ASMlib driver filesystem: [ OK ]
Scanning system for ASM disks: [ OK ]
[root@hostname~]# /etc/init.d/oracleasm listdisks
QAORADATA
QAORALOG
[root@hostname~]# /etc/init.d/oracleasm querydisk QAORADATA
Disk "QAORADATA" is a valid ASM disk on device [120, 33] <-- power device, failover will work
[root@hostname~]# /etc/init.d/oracleasm querydisk QAORALOG
Disk "QAORALOG" is a valid ASM disk on device [120, 1] <-- power device, failover will work
[root@hostname~]# ls -al /dev/emcpowerc1
brw-r----- 1 root disk 120, 33 Mar 27 08:47 /dev/emcpowerc1
[root@hostname~]# ls -al /dev/emcpowera1
brw-r----- 1 root disk 120, 1 Mar 27 08:47 /dev/emcpowera1

oracleasm configure


/etc/init.d/oracleasm configure
Configuring the Oracle ASM library driver.

This will configure the on-boot properties of the Oracle ASM library
driver. The following questions will determine whether the driver is
loaded on boot and what permissions it will have. The current values
will be shown in brackets ('[]'). Hitting without typing an
answer will keep that current value. Ctrl-C will abort.

Default user to own the driver interface []: 500
Default group to own the driver interface []: 500
Start Oracle ASM library driver on boot (y/n) [n]: y
Fix permissions of Oracle ASM disks on boot (y/n) [y]: y

Workaround for ASM in EL5

For some reason using the oracleasm command doesn't work in RHEL5. I found a work around is to use asmtool to manually force label the ASM disk.


asmtool -C -l /dev/oracleasm/ -n "QAORADATA" -s "/dev/emcpowerc1" -a force=yes
asmtool: Device "/dev/emcpowerc1" is not a partition
asmtool: Continuing anyway

Audit Database Activity with Splunk or Syslog

Friday, February 20th, 2009

Here are the steps I took to audit database activity and get the data into Splunk. You could technically send this to any log repository capable of listening on a TCP socket. The challenge is that many RDBMS don’t allow you to get database information. Oracle is notorious for this (because they want you to purchase Database Vault). Of course if you don’t have money (like me) for that you can use this solution. You really need three things:

1. Install DBI and Oracle:DBD on the host that will be polling the database.
2. Grant a user who will run the script SELECT on dba_audit_trail table (or whatever table you want to poll and pull into splunk).
3. Setup the perl script with the proper inputs and encrypt the password file for no plain text on the file system.

1. Install DBD::Oracle on 64-bit Linux and Oracle 11g

DBD::Oracle 1.21 to install on a 64-bit Linux OS against the Oracle 11 full client. As root, we downloaded DBD::Oracle from CPAN.


# perl -MCPAN -eshell
cpan> install DBI
cpan> get DBD::Oracle
...
# cd /root/.cpan/build/DBD-Oracle-1.21
# export ORACLE_HOME=actual value of Oracle Home
# export ORACLE_SID=actual value of ORACLE_SID
# export ORACLE_USERID=working ORACLE_USERID
# export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$ORACLE_HOME
# perl Makefile.PL
...
# make
...
# make test
...
# make install

Now you can install the dbipoll perl script.

Generate a random key, encode a file with it, and then decode a file with it. Useful to generate a password, encrypt it and then get rid of the plain text password inside scripts.

openssl rand 32 -out key.temp
openssl des3 -e -pass file:key.temp -in passwd -out passwd.enc
openssl des3 -d pass file:key.dec -in passwd.enc -out passwd.dec
openssl des3 -d -pass file:key.dec -in passwd.enc -out passwd.dec
openssl des3 -d -pass file:key.temp -in passwd.enc -out passwd.dec

Verify you are running encrypted. Add the following temporarily to your sqlnet.ora

TRACE_DIRECTORY_CLIENT=/ora-main/app/oracle/product/11.1.0.6/db/network/trace
#TRACE_FILE_CLIENT=client
TRACE_LEVEL_CLIENT=16

Inspect the trace for encryption

[root@mymachine trace]# cat cli_9668.trc |grep encry
(2863411744) [13-FEB-2009 15:37:50:911] naeshow: These are the encryption algorithms that the client will accept:
(2863411744) [13-FEB-2009 15:37:50:911] naeshow: Choice 0: no algorithm; encryption inactive
service: encryption
(2863411744) [13-FEB-2009 15:37:50:934] na_info: Oracle Advanced Security: encryption service for Linux: Version 11.1.0.6.0 - Production
service: encryption
(2863411744) [13-FEB-2009 15:37:50:935] nacomps: service encryption
(2863411744) [13-FEB-2009 15:37:50:940] nacomus: service encryption
service: encryption
service: encryption
(2863411744) [13-FEB-2009 15:37:50:952] naeecom: The server chose the 'RC4_256' encryption algorithm
(2863411744) [13-FEB-2009 15:37:50:952] na_tns: encryption is active, using RC4_256

Note the last two lines indicating we are encrypted.

Here is the dbipoll.pl script.


#!/usr/bin/perl
#
#
# Now accepts arguments for countkey, table, and columns.
# countkey will ideally be a timestamp field, such as EXTENDED_TIMESTAMP in AUD$.
#
# Need to deal with the following variations...
# 1 - Oracle local
# 2 - Oracle remote
# 3 - mysql local
# 4 - mysql remote
# 5 - Sybase local
# 6 - Sybase remote
#
# Oracle connect string: $dbh = DBI->connect("dbi:Oracle:host=$server;sid=$db;port=$dbport", $user, $passwd);
# Oracle connect string without env vars: $dbh = DBI->connect( "dbi:Oracle:$db", $username, $passwd )
# mysql connect string: $dbh = DBI->connect("dbi:mysql:database=$db\;host=$server;port=$dbport","$user","$passwd");
# Sybase connect string: $dbh = DBI->connect("dbi:sybase", $username, $password, $db);
use IO::Socket;
use strict;
use DBI;
use Getopt::Long;
use Sys::Syslog;
#
our ($server,$db,$table,$columns,$user,$enpasswd,$passwd,$dbtype,$tcphost,$tcpport,
$countfile,$countkey,$ofile,$dbport,$baseconnect,$dbh);
#
GetOptions(
"dbtype=s" =>\$dbtype, # Type of DB (Oracle, MySQL, etc.)
"server=s" =>\$server, # IP address of database server.
"dbport=s" =>\$dbport, # Port for connecting to remote DB.
"db=s" =>\$db, # Database to retriever records from.
"user=s" =>\$user, # DB username.
"password=s" =>\$passwd, # Unencrypted password.
"xpassword=s" =>\$enpasswd, # Encrypted password.
"table=s" =>\$table, # Table to retrieve from.
"columns=s" =>\$columns, # Columns to fetch.
"tcphost=s" =>\$tcphost, # Splunk server IP.
"tcpport=s" =>\$tcpport, # Splunk tcp listener port.
"countfile=s" =>\$countfile, # File to store row count status.
"countkey=s" =>\$countkey, # Field to use as count value
"o=s" =>\$ofile, # File for output (default is stdout).
);
#
# Deal with encrypted passwords first...
if (!$passwd) {
if ($enpasswd) {
$passwd = `openssl des3 -d -pass file:/etc/pki/tls/private/dbi.key -in $enpasswd |awk NF`;
chomp($passwd);
}
}
#
if ($dbtype =~ /oracle/i) {
$ENV{'DBI_DRIVER'} = "Oracle";
$dbtype = "Oracle";
$dbh->{InactiveDestroy} = 1;
#$baseconnect = DBI->connect("dbi:$dbtype:$db");
if (!$dbport) {
# Assume a local DB.
$dbh = DBI->connect("dbi:$dbtype:$db", "$user", "$passwd") ||
die( $DBI::errstr . "\n" );
#
} else {
if (!$passwd) {
$dbh = DBI->connect("dbi:Oracle:host=$server;sid=$db;port=$dbport", $user) ||
die( $DBI::errstr . "\n" );
} else {
$dbh = DBI->connect("dbi:Oracle:host=$server;sid=$db;port=$dbport", $user, $passwd) ||
die( $DBI::errstr . "\n" );
}
}
#
}
if ($dbtype =~ /mysql/i) {
$ENV{'DBI_DRIVER'} = "mysql";
$dbtype = "mysql";
#$baseconnect = DBI->connect("dbi:$dbtype:database=$db;host=$server");
if (!$dbport) {
# Assume a local DB.
$dbh = DBI->connect("dbi:$dbtype:host=$server;database=$db","$user","$passwd") ||
die( $DBI::errstr . "\n" );
#$dbh = $baseconnect . "$user" . "$passwd";
} else {
if (!$passwd) {
$dbh = DBI->connect("dbi:$dbtype:database=$db;host=$server;port=$dbport","$user") ||
die( $DBI::errstr . "\n" );
} else {
$dbh = DBI->connect("dbi:$dbtype:database=$db;host=$server;port=$dbport","$user","$passwd") ||
die( $DBI::errstr . "\n" );
}
}
#
}
if (($dbtype =~ /sybase/i) && (!$ENV{'SYBASE'})) {
$ENV{'SYBASE'} = '/usr/local/freetds';
$ENV{'DSQUERY'} = "$server";
$dbtype = "sybase";
#$baseconnect = DBI->connect("dbi:$dbtype", "$db");
if (!$dbport) {
# Assume a local DB.
$dbh = DBI->connect("dbi:$dbtype:database=$db","$user","$passwd") ||
die( $DBI::errstr . "\n" );
} else {
if (!$passwd) {
$dbh = DBI->connect("dbi:$dbtype:database=$db;port=$dbport","$user") ||
die( $DBI::errstr . "\n" );
} else {
$dbh = DBI->connect("dbi:$dbtype:database=$db;port=$dbport","$user","$passwd") ||
die( $DBI::errstr . "\n" );
}
}
}
my $sth;
if ( -f $countfile) {
# Compare vs. file with saved count
open (CF, "<$countfile");
my $filecount = readline CF;
chomp $filecount;
close (CF);
# Adding an ORDER BY clause here may be a requirement
$sth = $dbh->prepare("SELECT $columns FROM $table where $countkey > \'$filecount\' order by $countkey");
} else {
$sth = $dbh->prepare("SELECT $columns FROM $table order by $countkey");
}
#
our $handle;
if (($tcphost) && ($tcpport)) {
# Send directly to splunk server...
# Create tcp socket to send the data to the splunk server
my $remote = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $tcphost,
PeerPort => $tcpport,
Type => SOCK_STREAM)
or die "cannot connect to tcp daemon on $tcphost";
$remote->autoflush(1);
$handle = $remote;
#$target = "\$remote";
} elsif ($ofile) {
# Write to local outfile...
open (OFILE, ">$ofile");
$handle = *OFILE;
#$target = *OFILE;
} else {
# send to stdout
open (STDOUT, ">&1");
$handle = *STDOUT;
#$target = *STDOUT;
}
#
# Get some Rows...
$sth->execute or die $sth->errstr;
open (CF, ">$countfile");
while(my $hash_ref = $sth->fetchrow_hashref) {
my $output = "";
my $lastrow;
foreach my $key (keys(%$hash_ref)) {
my $str = $hash_ref->{$key};
if ($key ne "EXTENDED_TIMESTAMP") {
if ($str =~ /"/) {
(my $xstr = $str) =~ s/"/\\"/g;
$str = "\"$xstr\"";
} else {
$str = "\"$str\"" if ($str =~ / /);
$str = "\"$str\"" if ($str =~ /,/);
}
$output = $output . $key . "=" . $str . ",";
}
if ($key eq "EXTENDED_TIMESTAMP") {
# if ($key eq "TO_CHAR(EXTENDED_TIMESTAMP,'MM/DD/YYYYHH24:MI:SS')") {
$str =~ s/\"//g;
$lastrow = $str; #if (lc($str) eq lc($countkey));
}
#
}
chop($output);
# Update countfile
# Where did we leave off?...
*CF->autoflush(1);
seek(CF,0,0);
print CF "$lastrow\n";
#
# And send them somewhere.
print $handle $output . "\n";
}
#
$sth->finish();
$dbh->disconnect;
#
close (CF);
close $handle;

Now schedule this to run (I just wrote a wrapper script in ksh exporting the variables to be safe).


#!/usr/bin/ksh
#
export ORACLE_HOME=/ora-main/app/oracle/product/11.1.0.6/db
export ORACLE_SID=rmdev1
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$ORACLE_HOME
#
/usr/bin/dbipoll.pl -dbtype=oracle -server=servername -dbport=1521 -db=development -user=splunk -xpassword=/etc/dbipasswd -table=dba_audit_trail -columns="OS_PROCESS,OS_USERNAME,USERNAME,USERHOST,ACTION,ACTION_NAME,TRANSACTIONID,SCN,SQL_BIND,SQL_TEXT,TO_CHAR(EXTENDED_TIMESTAMP,'MM/DD/YYYY HH24:MI:SS'),EXTENDED_TIMESTAMP" -countfile="/app/oracle/product/monitor/countfile" -countkey="EXTENDED_TIMESTAMP" -tcphost=splunkserver -tcpport=9094

Of course you will also need to setup a tcp input port on your splunk server. Once you have all that in place you can run the following query to see what hosts are performing the largest amount of actions on the database and what users are running the majority of actions on the database. I have removed hostnames and usernames legends from the following for security.

index=indexname sourcetype=databasesource | timechart count(USERNAME) by USERNAME

DBAuditSplunkUsers

index=indexname sourcetype=databasesource |timechart count(USERHOST) by USERHOST

DBAuditSplunkHosts

You can also use this to setup alerts if any users runs an update on a database (useful to catch those sneaky developers). :)