Cover V12, I08

Article
Figure 1
Figure 2
Listing 1
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6
Listing 7
Listing 8
Listing 9
Listing 10

aug2003.tar

A Universal Firewall Reporting Tool

Bob McCormick, Greg Hartung, and Zachary Conger

One of the difficult problems in systems and network administration is the management of log files. Most administrators realize the importance of monitoring log files, but often systems and devices generate such a large volume of logs that manual monitoring is impossible. Log watching programs such as Swatch can help automate the process of looking for errors, but they do nothing for reporting or trending, nor do they allow you to query for specific information. Fortunately, with a minimal knowledge of Perl, SQL, and CGI programming, it is not difficult to create homegrown front ends to summarize and report on your logs.

Recently, we were faced with the problem of summarizing and reporting on the logs from several high-traffic firewall systems. Using some relatively simple Perl scripts, a PostgreSQL database, and a network sniffing utility, we developed a system to report on the Internet-bound user HTTP traffic through these firewalls. This system logs user traffic through PIX firewalls and Squid proxy servers to the PostgreSQL database. A CGI front-end then allows administrators and managers to query and generate reports regarding this traffic.

Project Goals

The first step was defining project goals and requirements. The requirements identified were:

  • Log user HTTP requests. We decided that, at least initially, we would only concern ourselves with outbound user HTTP traffic. We allow only limited outbound access on ports other than 80 and 443, so logging and reporting on non-HTTP traffic was not as critical. However, some firewalls, such as the PIX, only log IP addresses from HTTP requests rather than fully qualified domain names. We would need a way to inject the domain information into our logs for such devices.
  • Support diverse firewalls. Specifically, we needed to support Cisco PIX and Squid. We also wanted a solution that was flexible enough to accommodate any future firewall platforms.
  • Support a large number of users and very high traffic levels. We estimated supporting approximately 7000 users between our firewalls. Logging HTTP traffic for this number of users will generate a lot of log information.
  • Real-time or near real-time reporting. We wanted to find out what people were doing now, not just yesterday or last week.

Based on these requirements, we first searched for an existing solution (either commercial or open source) that would meet our requirements or could be readily modified to meet our requirements.

There are quite a few programs, both commercial and non-commercial, for analyzing PIX logs, but none of them do anything with user authentication messages or HTTP traffic messages. There are also quite a few programs to analyze Squid log files. All of the ones we found run as nightly batch processes and produce a group of static report files. This fails our requirement for near real-time reporting. Additionally, our Squid server handles so much traffic that the resulting static report files were often quite large.

Probably the most difficult requirement was finding one tool that could report on a wide variety of firewalls. We didn't just want a reporting tool for one firewall; we wanted a universal firewall reporting tool.

The Solution

Our solution was to write all of the log information to a PostgreSQL database. We would then write a CGI front-end to generate various canned and ad hoc reports by querying the database.

The Squid proxy server logs user authentication information and user traffic information locally to the access.log file. The PIX logs authentication and traffic information to syslog. We ran one small Perl process on the Squid proxy server, and one on the syslog server, that would parse the relevant log messages and insert them as records into the PostgreSQL database. Because of limitations in the way the PIX logs user traffic, we also had to log information from a sniffer placed in front of the PIX. A Perl CGI program on the Web server provided a reporting and querying front-end.

The Database

The database is central to the universal firewall reporting tool. By using a database server, we were able to handle very large amounts of log data much more manageably and more responsively than with flat files. Reports that might have run for hours to get them from our old flat log files could be generated in a matter of minutes. Using an SQL database server as the "middleware" also made generating new reports easier.

For the main table, called "traffic", we needed to record the users' login name, source IP address, where they went (the destination URL), and when they went there. We split the destination URL information into two fields -- one for the domain, and one for the URL path information. Because we have multiple firewalls, we also created a field to record which firewall the traffic passed through. Additionally, we created a status field to record whether the request was permitted or denied. That field is currently used only for data from the Squid proxy server. The table definition can be seen in Listing 1.

This information was easy to parse out of the Squid proxy server logs. However, the PIX, unfortunately, logs the destination IP address of the user's traffic instead of the domain name in the user's original request. For a variety of reasons, virtual hosting, etc., most Internet IP addresses are unlikely to reverse to what the user actually typed. For example, a user might type:

http://www.joeswebsite.com/index.html
The PIX will log:

someuser@192.1.1.1 Accessed URL 10.1.2.3:/index.html
However, the destination address 10.1.2.3 might reverse to:

www.hugehostingcompany.com
If we reassembled the URL using the reversed DNS information, it would read:

someuser@192.1.1.1 Accessed URL www.hugehostingcompany/index.html
This is not even close to what the user typed. This is also not useful for legal purposes nor does it lend itself to guessing the nature of the site.

We captured user traffic through the PIX firewalls with an inline sniffer strategically placed to capture all traffic into the inside interface of the PIX (connected to a span/mirror port on the switch). We selected urlsnarf (part of the dsniff package) as our sniffer application. Urlsnarf captures the actual URL from the HTTP header and records it in the standard Extended Common Log Format used by most Web server logs. This information is parsed with a short Perl script and then remotely inserted into the database table mentioned above.

The HTTP request header captured by urlsnarf only contains the user's source IP address, not their username. This left us without the username. Before we could insert the request data into our database, we needed to find the username. Our solution was to log the user authentication from the PIX in a separate database table called the "auth" table. We then linked these tables using triggers. A trigger is a stored function that "fires" before or after a row is inserted into the database. The table definition for the "auth" table can be seen in Listing 2.

We built the triggers out of PL/SQL code. The first trigger, "auth_prune", fires on the user authentication table before an insert. This trigger deletes any old records with the same source IP field as the new record. The new user authentication record is then inserted without further interruption. It's not a problem to delete old authentication records. Traffic information from dsniff is recorded in real-time, and we only need to know who is currently logged in.

The second trigger fires before an insert into the traffic table. This trigger, "traffic_username" performs an SQL query from the "auth" table to look up the username corresponding to the source IP address of the current traffic packet. We have thousands of users, but because all old records for that source address are removed from the "auth" table, only one record is returned and it comes back very quickly. The PL/SQL source for the trigger functions can be seen in Listing 3.

Collecting the Data

As mentioned previously, we collect data to be inserted into the universal log database from three locations: the Squid log files on the proxy server, the PIX logs on the syslog server, and the urlsnarf application running on the sniffer box. For each of these data sources, a Perl script parses the data and inserts it over the network into the database.

The Squid log file script is shown in Listing 4. The script uses the CPAN module File::Tail to read new log messages from the end of the Squid log file "access.log". The log file message is parsed and the necessary information is inserted into the database using the standard Perl DBI module. When run with the "-d" command-line option, the script will background itself and run as a daemon.

The PIX authentication log script shown in Listing 5 is very similar, except that instead of tailing the log file, we log to a named pipe. The script reads from the named pipe, parses the incoming log messages, and then inserts the results into the database.

The final data collection script runs on the sniffer box. This script, shown in Listing 6, is again very similar to the previous two. In this case, rather than reading from a log file or name pipe, the script executes the urlsnarf application and pipes the standard output of urlsnarf into the filehandle. This output is then parsed and inserted into the database.

The Web Query Front-End

Once the universal log database was constructed and populated, we had a good engine for querying the logs in very flexible ways. We agreed that a Web-based interface to the database would be ideal for providing reporting functionality for ourselves as well as non-technical users of the system. We decided that a set of CGI programs should be constructed to allow general queries, as well as canned reports such as "Top 10 Websites", "Top 10 Users", and the like.

We could have written the CGI programs in any of a number of languages, but we chose Perl for a number of reasons. Perl had been used for the other parts of the log database system, we have experience in it, and it works well for CGI.

To begin, we wrote a general database query tool called "querytool.pl" consisting of a query page and a result page. We reasoned that canned reports could simply be a set of specific queries fed into this more general subroutine. This query tool needed essentially three parts: the query page, the query translator, and the results page.

The query page has these parts:

  • User picker -- A list of users pulled from the current database of users that have at some point authenticated to the firewalls or proxies.
  • String search fields -- Fields that allow a pattern match against domain names or URLs.
  • Date range picker -- A series of drop-down boxes allowing the user to pick a date range for the query.
  • Report format options -- A series of options for the format of the report, such as which columns to show, and how many results to show per page.
  • Sort options -- Columns on which to sort the report.

The query translator transforms the chosen parameters from the query page to an SQL SELECT statement that PostgreSQL will understand. Finally, the results page prints the results in the chosen format. Again, Perl modules made the job simpler. We used the CPAN module CGI.pm to simplify the construction of CGI variables, HTML output, form handling, and cookies. As in the log collection scripts, the DBI module provided hooks into the SQL database.

The Query Page

Since the query page needs to show a list of available users, we connect to the database right away and grab them as an array. The section of code to do this is Listing 7. Then we construct the actual query page using the CGI module as shown in Listing 8. The "start_page" and "end_page" subroutines refer to a subroutine that creates a standardized and theme-able HTML wrapper for the page. The resulting output page can be seen in Figure 1.

When users click the "Submit Query" button, they are directed to the same program, querytool.pl, but with a hidden CGI field "target", which is set to "query_results". The script sees this and knows to run the query_report subroutine, which translates the query, queries the database, and displays the results.

The Query Translator

The query translator separates all of the variables entered from the query form and constructs a rational SQL query from it. This was the most complex part of the query tool, because it involved taking all of the requested bits of information and cramming them into a concise and syntactically correct statement. The query translator can be seen in Listing 9. We end up with two queries. $querything is a SQL statement that selects the actual information we want, limited by the number of results per page (the LIMIT clause) and the offset according to what page we're on (the OFFSET clause). $querycount is an SQL statement that makes the same query, but just returns a count of results. We use the count of results to calculate which page we're on, and what other pages of results are available. In Listing 10, the actual queries are made and the results are returned for printing. A representative output page can be seen in Figure 2.

Conclusion

This project looked daunting when we drew up the initial requirements. It was a large undertaking, but dividing it up into smaller, manageable pieces was the key to making the project a success. Also, using PostgreSQL as a common middle layer allowed us to isolate and abstract out the differences between our data sources. By doing so, each of the data collection scripts had to be concerned only with the specifics of one data source. More importantly, the reporting front-end did not need any information about how the separate firewalls function or the differences in how they record user traffic.

Refinements and features should be simple to add given this foundation. We could write new collection scripts for additional firewalls and proxies, tweak the scripts for performance, prepare canned reports, or add charts and graphs to the mix. But even with the minimal effort so far, we have created a useful and effective universal firewall reporting tool.

Bob McCormick has been a UNIX and network administrator for nine years. He's worked with a variety of UNIX systems, including Solaris, AIX, HP-UX, Linux, and BSD. He currently administers routers, switches, and firewalls supporting tens of thousands of users across the continental United States.

Zachary Conger has been a systems and network engineer for nine years, working as a consultant for companies along the Front Range in Colorado, and specializing in security. He studied psychology and computer science at Iowa State University and the University of Iowa, and neuroscience at the University of Minnesota.

Greg Hartung has been a network engineer, sys admin, and DBA for more than 10 years. In his free time Greg also develops embedded microcontroller applications, is a private pilot, and has returned to school to study physics.