4 Java-Based Audit Trail Collection Plug-ins

Oracle Audit Vault and Database Firewall provides a set of Java-based audit trial collection plug-in, which enable you to create custom plug-ins.

4.1 About Java-Based Collection Plug-ins

For situations where a template-based collection plug-in cannot easily handle audit data, you can use Java-based collection plug-ins

Creating a custom collection plug-in using the Java APIs gives you much flexibility in how you design your collection plug-in.

In general, use the Java type of collection plug-in if you need it to:

  • Read trails not written in database tables or XML files.

  • Read complex trails written to tables or XML files.

4.2 JDK Requirement for Using the Java-Based Collection Plug-in

To use a Java-based collection plug-in with Oracle Audit Vault and Database Firewall, you must have the JDK to compile and test your code.

Because the collection plug-in runs under the same JVM after it is shipped, Oracle recommends that you use the same JDK as the JDK that you use to start the agent. Compile your classes with the JDK by setting the -target option of the javac compiler to the same JDK version. Refer to the JDK documentation for details.

4.3 About the Flow of Control Inside the Java-Based Collection Plug-in

Learn how Oracle Audit Vault accesses an audit trail, maps the trail to Oracle Audit Vault events, starts the correct Java-based collection plug-in, and creates audit records.

When a collection plug-in accesses an audit trail, it extracts an audit record and its related fields from the audit trail. Next, it maps the audit record to an Oracle Audit Vault event, and all the fields to Oracle Audit Vault fields. The collection plug-in then passes the Oracle Audit Vault event and fields to the Collection Framework, which sends the information to the Oracle Audit Vault Server.

The sequence of control processes for the audit trail collection is as follows.

Control Process Sequence for Audit Trail Collection

  1. The Oracle Audit Vault Server commands the Agent Framework to create a thread to collect from a specific audit trail.

  2. The new thread, just created by the agent, collects a specific audit trail.

    At this point, control is handed to the Collection Framework.

  3. Within the thread, the Collection Framework connects to the Oracle Audit Vault Server, and queries for configuration information for the audit trail being collected.

    In addition, it requests information for the last checkpoint set for that trail.

  4. With the information it now has, the Collection Framework uses the plug-in manifest file to determine the correct Java class to start within the correct collection plug-in. It passes the configuration information to this class, and asks it to initialize itself.

  5. After the collection plug-in has initialized itself, the Collection Framework loops repeatedly. Within each loop, the Collection Framework does the following:

    • Asks the collection plug-in for any additional audit records in the audit trail.

      The collection plug-in transforms (by mapping) any further audit records into the form of audit records that Audit Vault expects, and hands them to the Collection Framework through the Collection API.

  6. The collection plug-in can periodically send Checkpoint and metric information to the Collection Framework. The collection plug-in can do so in the same flow when it has the control, for example when the Collection Framework calls hasNext().

  7. If the Oracle Audit Vault Server sends commands to the Collection Framework, the Collection Framework passes them to the collection plug-in to act on.

    If the Collection Framework receives a STOP command from the Audit Vault Server, it notifies the collection plug-in to stop sending record. Then it exits the collection thread and shuts itself down.

    If the Collection Framework receives a RECONFIGURE command from the Audit Vault Server, it notifies the collection plug-in to set an attribute using setAttribute().

4.4 Useful Classes and Interfaces in the Collection Framework

Learn about Oracle Audit Vault Java-based collection plug-in classes and interfaces that can be particularly useful for your own collections

The image below shows the relationships between the classes and interfaces from the AuditService, CollectorContext, and Class AVLogger.

Figure 4-1 Classes and Interfaces from AuditService, CollectorContext, and Class AVLogger

Description of Figure 4-1 follows
Description of "Figure 4-1 Classes and Interfaces from AuditService, CollectorContext, and Class AVLogger"

The following diagram shows the various classes and interfaces in the Collection Framework that you need to know about to write a Java-based collection plug-in.

Figure 4-2 Classes and Interfaces from Collection Framework Used in Collection Plug-in


Description of Figure 4-2 follows
Description of "Figure 4-2 Classes and Interfaces from Collection Framework Used in Collection Plug-in"

4.5 How to Create a Java-Based Collection Plug-in

Review the tasks required to create and use Java-based collection plug-ins for Oracle AVDF.

4.5.1 About Creating a Java-Based Collection Plug-in

The Oracle Audit Vault documentation provides examples of a Java-based collection plug-in implementation, including a hypothetical source that writes events to a table named AUD.

Implementing Oracle Audit Vault and Database Firewall involves writing Java classes that implement the AuditEventCollectorFactory interface, and that extend the AuditEventCollector class, which are part of the Audit Vault Collection Framework. The same Java class can both extend the AuditEventCollector class. and implement the AuditEventCollectorFactory interface. Alternately, you can choose to write two separate classes.The sample consists of two classes, SampleEventCollectorFactory which implements the AuditEventCollectorFactory interface and SampleEventCollector which extends from the AuditEventCollector class.

4.5.2 Using the AuditEventCollectorFactory to Get the AuditEventCollector Object

During runtime the collection plug-in can require multiple implementations of AuditEventCollector. The AuditEventCollectorFactory object enables this capability for Oracle Audit Vault and Database Firewall.

The Collection Framework does not create an instance of the AuditEventCollector object directly. Instead, it creates an instance of the AuditEventCollectorFactory class and using the factory object, gets the AuditEventCollector object. This is because the collection plug-in may require multiple implementations of AuditEventCollector. The collection plug-in decides at run time which implementation to use. Therefore, every collection plug-in should have an implementation of AuditEventCollectorFactory.

In the following example, the createAuditEventCollector() always creates and returns an instance of the SampleAuditEventCollector class.

Example 4-1 Creating a SampleAuditEventCollector Class

public class SampleEventCollectorFactory implements AuditEventCollectorFactory {
 
   public AuditEventCollector createAuditCollection(
         CollectorContext collectorContext) throws AuditEventCollectorException {
      return new SampleEventCollector();
   }
}

4.5.3 Using the CollectorContext Class When Creating a Java-Based Collection Plug-in

Learn how to use source attributes, which provide the Oracle AVDF collection plug-in with information about the source that is needed to collect the audit trail effectively.

The Collection Framework passes an instance of the CollectorContext class to the collection plug-in through the initializeCollector method. This instance can be queried by the collection plug-in to obtain information needed to collect the audit trails generated by the source.

4.5.3.1 Basic Source Attributes

To obtain audit trail collection successfully, there are basic source attributes that provide the collection plug-in with information about the source for Oracle Audit Vault and Database Firewall.

Basic source attributes for Oracle Audit Vault and Database Firewall include the user name, password, and connection string. These attributes are returned by these methods respectively: getSecuredTargetUser, getSecuredTargetPassword, and getSecuredTargetLocation. You can retrieve pther source attributes by using getAttributes.

When the Oracle Audit Vault administrator registers the source, you can require the Oracle Audit Vault administrator to provide the required information, in the form of source attributes. Oracle Audit Vault stores these attributes in the Oracle Audit Vault Server repository, and provides them to the collector code on startup.

Some collection plug-ins do not need to connect to the source and in these cases, the Collection Framework may return null for these methods.

4.5.3.2 Basic Trail Attributes

The checkpoint and trail name attributes are the basic attributes that Oracle Audit Vault and Database Firewall obtains.

Checkpoint attributes are returned by the getCheckpoint method, and trail name attributes are returned by the getTrailLocation method. The collection plug-in should use these attributes as follows:

  • The checkpoint returned is the last checkpoint the collection plug-in has set for this trail when it ran the last time. The collection plug-in should start sending only those records which have an event time greater than or equal to the checkpoint. If the collection plug-in is starting for the first time, the collection plug-in receives this value as null. In this case, the collection plug-in must send the records from the beginning.

  • Trail name indicates the target containing the audit events, typically, a table or directory name.

You can retrieve other trail attributes by using getAttributes.

4.5.3.3 Utility Instances

Learn how to use the AVLogger and AuditService attributes with the Oracle Audit Vault collector.

AVLogger and AuditService are returned by the getLogger and getAuditService methods, respectively. The collector should use these attributes as follows:

  • The AVLogger instance logs various messages.

  • The AuditService instance sends checkpoints and metrics to the Audit Vault Server.

These methods never return null.

4.5.3.4 Additional Source or Trail Attributes

You can retreive other attributes that you find the Oracle Audit Vault and Database Firewall collector needs by passing the attribute name to the getAttribute method.

For example, suppose that a source required SourceVersion to collect audit data. In that scenario, to obtain the value of SourceVersion. the collector for the source calls collectorContext.getAttribute('SourceVersion').

If the attribute is present, then the getAttribute method returns the attribute value as a String. Otherwise, the method returns null.

If the collector receives a null or an invalid value for any mandatory attribute, then it must throw an AuditEventCollectorException exception from the initializeCollector method. After throwing an exception, the Collection Framework shuts down.

4.5.4 Initializing the Java-Based Collection Plug-in

Use these examples to understand how to initialize a Java-based collection plug-in, and how to start audit events collection with Oracle Audit Vault and Database Firewall.

The first thing the Collection Framework does after the collection thread starts is to initialize the collector.

The Collection Framework calls the initializeCollector() method of the AuditEventCollector class. The collector sets up the environment appropriately to enable it to start collecting audit events. For example, for a database table collection plug-in, this method connects to the database. For an XML file collection plug-in, this method parses the file mask and may open a particular file to start with. The collection plug-in may also want to retrieve various attributes from the collector context at this point. If there is an error in setting up the environment, this method throws AuditEventCollectorException with an appropriate error message.

Example 4-2 Initializing a Java-Based Collection Plug-in

The following is a example of how to initialize a Java-based collection plug-in:

private AVLogger m_logger;
   private CollectorContext m_collectorContext;
   private long m_timeZoneOffset;
   private AuditService m_auditService;
   private Timestamp m_previousCheckpoint;
 
   public void initializeCollector(CollectorContext collectorContext)
         throws AuditEventCollectorException {
      m_collectorContext = collectorContext;
      m_auditService = m_collectorContext.getAuditService();
      m_previousCheckpoint = m_collectorContext.getCheckpoint();
      m_logger = m_collectorContext.getLogger();
      // Get other attributes of the Source.
      String offset = m_collectorContext.getAttribute("TimeZoneOffset");
      if (offset != null) {          m_timeZoneOffset = getTimeZoneOffsetInMs(offset);
      }
      connectToSource();
 }

Example 4-3 Using the ConnectionManager Utility to Connect and Retrieve Audit Records From a Database

If a collector must connect to a database to retrieve audit records, then it must use the ConnectionManager utility API provided with Oracle Audit Vault. The following example shows how to use the ConnectionManager utilityL

private ConnectionManager m_connectionManager;
   
   private void connectToSource() throws AuditEventCollectorException {
      m_logger.logDebugMethodEntered();
      // Get connection information from collector context.
      String user = m_collectorContext.getSecuredTargetUser();
      String password = new String(m_collectorContext.getSecuredTargetPassword());
      String connectionString = m_collectorContext.getSecuredTargetLocation();
      // Create a ConnectionManager object.
      try {
         m_connectionManager = new ConnectionManagerImpl(connectionString,
               user, password.toCharArray());
         m_connection = m_connectionManager.getConnection();
      } catch (AuditException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.FAILED_CONNECT_TO_SOURCE,
               new Object[] { connectionString }, ex);
      }
      m_logger.logDebugMethodExited();
   }

4.5.5 Connecting, Fetching Events, and Setting Checkpoints

Use these examples to understand how to connect a Java-based collection plug-in, how to fetch events, and how to set checkpoints with Oracle Audit Vault and Database Firewall.

After initialization, the Collection Framework repeatedly calls the hasNext() method of the collector, which internally calls the fetchEvents() method. In this method, the collector fetches audit records from the audit trail in the form of a ResultSet.

The range that is fetched starts from the point that was just finished to nearly the current time. The next fetch is performed when the current ResultSet is exhausted

The collection plug-in sets the checkpoint whenever one ResultSet finishes processing, but before the next one starts. The timing of this is important. When a collection plug-in sets the checkpoint with Timestamp t, the collection plug-in must ensure that all records with an event time less than t are already sent to Collection Framework. However, because ResultSet does not give records in any particular order, setting the checkpoint before the end of a ResultSet can be incorrect. Additionally, the collection plug-in does not use the current time as the upper bound of the range, but rather uses the current time minus the delta time. Using this time method allows for possible delays between generating the event, and inserting the event into the table. When the collection plug-in runs the query, all events up to the upper bound are fetched in the ResultSet to honor the checkpoint contract. The 5-second delay time (the delta) ensures that all of the records, up to the upper bound, are already in the table.

Example 4-4 Fetching ResultSets and Setting Checkpoints

This example shows how collectors should obtain the Connection from ConnectionManager whenever they need one.

   private ResultSet m_resultSet;
   private Timestamp m_nextCheckpoint;
 
   private void fetchEvents() throws AuditEventCollectorException {
      m_logger.logDebugMethodEntered();
      if (m_nextCheckpoint != null) {
         m_auditService.setCheckpoint(m_nextCheckpoint);
         m_previousCheckpoint = m_nextCheckpoint;
      }
     // It is not good to hold on to the Connection for long. As this is the
      // only place we can release the connection, we release and reacquire the
      // connection.
      try {
         if (m_connection != null) {
            m_connectionManager.releaseConnection(m_connection);
         }
      } catch (AuditException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.FAILED_TO_RELEASE_CONNECTION_TO_DB, null, ex);
      }
 
      try {
         m_connection = m_connectionManager.getConnection();
      } catch (AuditException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.FAILED_TO_GET_CONNECTION_TO_DB, null, ex);
      }
 
     // This is the upper bound which is current time minus 5 seconds.
      m_nextCheckpoint = new Timestamp(System.currentTimeMillis() - 5000);
     String query = null;
      try {
         if (m_previousCheckpoint == null) {
            query = "select * from AUD where EVENT_TIME <= ?";
            m_preparedStatement = m_connection.prepareStatement(query);
            m_preparedStatement.setTimestamp(1, m_nextCheckpoint);
         } else {
            query = "select * from AUD where EVENT_TIME > ? and EVENT_TIME <= ?";
            m_preparedStatement = m_connection.prepareStatement(query);
            m_preparedStatement.setTimestamp(1, m_previousCheckpoint);
            m_preparedStatement.setTimestamp(2, m_nextCheckpoint);
         }
         m_resultSet = m_preparedStatement.executeQuery();
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE,
               new Object[] { query }, ex);
      }
      m_logger.logDebugMethodExited();
   }

Example 4-5 Using hasNext to Fetch Records

In this example, the hasNext() method runs the fetchEvents() method that you see defined in the preceding example to to fetch records, and to set checkpoints.
     public boolean hasNext() throws AuditEventCollectorException {
      boolean hasMore;
      try {
         if(m_resultSet == null) {
            fetchEvents();
            return m_resultSet.next();
         }
         hasMore = m_resultSet.next();
         if (!hasMore) {
            fetchEvents();
            hasMore = m_resultSet.next();
         }
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
      return hasMore;
   }

4.5.6 Transforming Source Event Values to Audit Vault Event Values

Learn about when and how the Oracle AVDF collection plug-in transforms source event values to Oracle AVDF values.

The collector retrieves values of specific fields from source. For some fields, in addition to retrieving the values, the collection plug-in must transform the values in certain ways. This section discusses transformations that are required for all source types.

4.5.6.1 Event Time to UTC

See how Oracle Audit Vault and Database Firewall transforms an event time from a source time zone to Coordinated Universal Time (UTC).

Event Time should be sent only in UTC time zone. Therefore, it must be transformed from the source time zone to the UTC time zone before returning a value. If the column from the source database is timezone aware, then this transformation is not necessary.

Example 4-6 Transforming EventTime from Source Time Zone to UTC

   public Timestamp getEventTimeUTC() throws AuditEventCollectorException {
      try {
         Timestamp eventTime = m_resultSet.getTimestamp("EVENT_TIME");
         // As the method name suggests, the timestamp must be returned only in
         // UTC timone.
         return new Timestamp(eventTime.getTime() - m_timeZoneOffset);
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }
4.5.6.2 Source Event Name to Audit Vault Event Name

Use this example to create your own source event names to Oracle Audit Vault event names mapping.

Each source event name maps to one Oracle Audit Vault event name. The getCommandClass() method should transform the source event name into a value that Oracle Audit Vault can accept.

Example 4-7 Mapping Source Event Names to Audit Vault Event Names

private static final Map<Integer, String> eventNameMap = new HashMap<Integer,
         String>();
static {
      eventNameMap.put(1, "CREATE");
      eventNameMap.put(2, "INSERT");
      eventNameMap.put(3, "SELECT");
      eventNameMap.put(4, "CREATE");
      eventNameMap.put(15, "ALTER");
      eventNameMap.put(30, "AUDIT");
      eventNameMap.put(34, "CREATE");
      eventNameMap.put(35, "ALTER");
      eventNameMap.put(51, "CREATE");
      eventNameMap.put(52, "CREATE");
   }
 
   public String getCommandClass() throws AuditEventCollectorException {
      try {
         int eventId = m_resultSet.getInt("ACTION");
         return eventNameMap.get(eventId);
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }
4.5.6.3 Source Event ID to Source Event Name

Learn how to map the source event identifiers (IDs) to descriptive source event names in Oracle Audit Vault and Database Firewall.

For some sources, events reported as IDs may not mean anything if users are unfamiliar with the IDs. Therefore, it may be best to map the source event IDs to descriptive source event names. If the audit record itself contains descriptive event names, then they can directly be returned without any mapping. The source event name is optional, so the collection plug-in can return null if it does not have the information.

In the following example, you can see how to map the source event IDs to descriptive source event names.

Example 4-8 Mapping Source Event Ids to Source Event Names

private static final Map<Integer, String> sourceEventMap = 
        new HashMap<Integer, String>();
 
   static {
      targetTypeMap.put(1, "OBJECT:CREATED:TABLE");
      targetTypeMap.put(2, "INSERT INTO TABLE");
      targetTypeMap.put(3, "SELECT FROM TABLE");
      targetTypeMap.put(4, "OBJECT:CREATED:TABLE");
      targetTypeMap.put(15, "OBJECT:ALTERED:TABLE");
      targetTypeMap.put(30, "AUDIT OBJECT");
      targetTypeMap.put(34, "OBJECT:CREATED:DATABASE");
      targetTypeMap.put(35, "OBJECT:ALTERED:DATABASE");
      targetTypeMap.put(51, "OBJECT:CREATED:USER");
      targetTypeMap.put(52, "OBJECT:CREATED:ROLE");
   }
 
   public String getEventName() throws AuditEventCollectorException {
      try {
         int eventId = m_resultSet.getInt("ACTION");
         return sourceEventMap.get(eventId);
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }
4.5.6.4 Mapping Source Event Name or ID to Target Type

Learn how to map source event identifiers (IDs) to Oracle Audit Vault target types.

A target type is the type of the object on which an event has taken place. For example, if the event is a SELECT operation on a table, then the target type is table. In some sources, the target type can be present within the source event name and ID. For example, an event name can be select table, which implies that the target type is a table. In this case, you must map the source event name or ID to a target type. Target type is an optional field, so the collection plug-in can return null if there is no such information.

In the following example, source event IDs are mapped to Oracle Audit Vault target types.

Example 4-9 Mapping Source ID to Target Type

private static final Map<Integer, String> targetTypeMap = 
      new HashMap<Integer, String>();
 
   static {
      targetTypeMap.put(1, "TABLE");
      targetTypeMap.put(2, "TABLE");
      targetTypeMap.put(3, "TABLE");
      targetTypeMap.put(4, "CLUSTER");
      targetTypeMap.put(15, "TABLE");
      targetTypeMap.put(30, "OBJECT");
      targetTypeMap.put(34, "DATABASE");
      targetTypeMap.put(35, "DATABASE");
      targetTypeMap.put(51, "USER");
      targetTypeMap.put(52, "ROLE");
   }
 
   public String getTargetType() throws AuditEventCollectorException {
      try {
         int eventId = m_resultSet.getInt("ACTION");
         return targetTypeMap.get(eventId);
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }
4.5.6.5 Source Event Status to Oracle Audit Vault Event Status

Oracle Audit Vault has three EventStatus values. See how you can transform source event status values to the Oracle Audit Vault values.

There are only three allowed values for EventStatus. They are SUCCESS, FAILURE, and UNKNOWN. You must configure transformations of any source event values to one of the three supported Oracle Audit Vault values. In the following example, you can see how source values are transformed to Oracle Audit Vault values.

Example 4-10 Transforming Source Values to Oracle Audit Vault EventStatus Values

   public EventStatus getEventStatus() throws AuditEventCollectorException {
      try {
         int status = m_resultSet.getInt("STATUS");
         if (status == 1) {
            return EventStatus.SUCCESS;
         } else if (status == 0) {
            return EventStatus.FAILURE;
         } else {
            return EventStatus.UNKNOWN;
         }
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
            ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }

4.5.7 Retrieving Other Audit Field Values

When field values do not require transformations, the Oracle Audit Vault Java-based collection plug-in returns the value it obtains from the source.

In the following example, the Oracle Audit Vault collection plug-in obtains a user name, and returns it.

Example 4-11 Returning Values that Do Not Need Transformation

   public String getUserName() throws AuditEventCollectorException {
      try {
         return m_resultSet.getString("USER_ID");
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }

4.5.8 Changing Oracle AVDF Attributes at Run Time

If you are an administrator, then you can change the attributes that a Java-based collector plug-in uses during Oracle AVDF audit trail collection.

If you are an Oracle AVDF administrator, then you can update attributes, including source attributes, at any time. To update attributes, you can use either the Audit Vault Server console, or you can use the AVCLI command-line tool.

How Audit Vault Server Manages Attribute Updates

If the update occurs while collectors are collecting audit trails, then first Audit Vault Server notifies all running collectors dynamically, by calling the setAttribute method of the collector. Next, the collection plug-in must start using the new value immediately. If the collection plug-in is not configured for the attribute value, or if the value cannot be used, then the collector responds with the message SetAttributeException.

In the following example, the collection plug-in receives and handles a new time zone offset to use in converting the EventTime to Coordinated Universal Time (UTC) time zone for all subsequent events.

Example 4-12 Changing an Oracle Audit Vault and Database Firewall Attribute

public void setAttribute(String name, String value)
         throws SetAttributeException {
           if (name.equalsIgnoreCase("TimeZoneOffset")) {
              m_timeZoneOffset = getTimeZoneOffsetInMs(value);
      } else {
         throw new SetAttributeException(ErrorCodes.INVALID_ATTRIBUTE_NAME,
               new Object[] { name, value }, null);
      }
   }

Effects After Modifying Attributes

Use the preceding example to understand how the time zone offset transformation is affected in the example you can find in the following topic:

Event Time to UTC

4.5.9 Changing Custom Attributes at Run Time

See how administrators can change custom collector plug-in attributes at runtime while Oracle Audit Vault and Database Firewall collects the audit trail.

As with Oracle Audit Vault and Database Firewall (Oracle AVDF) attributes, you can also change custom attributes at runtime.

If you want to change custom attributes, then you must implement the following methods to validate the custom attributes before you can use custom attributes in the setAttribute method.

Example 4-13 Changing a Custom Attribute

private static final String[] s_attributes = new String[] { "av.collector.configureParameter1", "av.collector.configureParameter2" };
public String[] getAttributeNames() throws AuditEventCollectorException {
         return s_attributes.clone();
 }
 
public void setAttribute(String name, String value)
         throws SetAttributeException {
           if (name.equalsIgnoreCase("configureParameter1")) {
           // use value
           }else if (name.equalsIgnoreCase("configureParameter2"))
           {
           // use value
           }else {
         throw new SetAttributeException(ErrorCodes.INVALID_ATTRIBUTE_NAME,
               new Object[] { name, value }, null);
      }
   }

4.5.10 Creating Extension Fields

See an example of how to create an extension field for Java-based collection plug-ins in Oracle Audit Vault and Database Firewall.

The extension field contains all the fields of the source event which are of interest to the user, but do not correspond to any of the core or large fields. The collector must form one string which contains the names and values of these extra fields. The format of this string is up to the collection plug-in. The Collection Framework never tries to parse this string. In this example of creating an extension field, it uses the following format, repeating as needed:

 <field_name>=<field_value>;

Note that this example extension file sends three fields:

Example 4-14 Creating an Extension Field

  public String getExtension() throws AuditEventCollectorException {
      try {
         StringBuilder sb = new StringBuilder();
         sb.append("DB_ID=" + m_resultSet.getString("DB_ID") + ";");
         sb.append("INSTANCE=" + m_resultSet.getString("INSTANCE") + ";");
         sb.append("PROCESS=" + m_resultSet.getString("PROCESS"));
         return sb.toString();
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }

4.5.11 Handling Large Audit Fields

See an example of how Oracle Audit Vault and Database Firewall handles very large audit record fields.

Some audit record fields can be very large, so that returning them as a string is not feasible. Therefore, methods corresponding to those fields return an object of type Reader. If the source field is a character large object (aCLOB), then the Reader can be obtained by using clob.getCharacterStream().

Note:

The Reader is only valid as long as the Connection to the source is alive.

You must design the collection plug-in to keep the connection to the source alive until all events using readers have been sent to the Audit Vault Server.

If the collector wants to reset the Connection to the source, it must do so immediately after setting the checkpoint. This is because the Collection Framework sends batches of records, and then sets the checkpoint. The time the ckeckpoint is sent is the only time that the collector knows that all records are flushed to Oracle Audit Vault Server. Note that there are other occasions that records are sent to the Oracle Audit Vault Server, but this is the only one with a checkpoint.

If the Reader instance cannot be obtained directly, then the collector must create and return a Reader.

Example 4-15 Creating Large Fields

   public Reader getCommandText() throws AuditEventCollectorException {
      try {
         Clob clob = m_resultSet.getClob("SQL_TEXT");
         return clob.getCharacterStream();
      } catch (SQLException ex) {
         throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }

4.5.12 Creating Markers to Uniquely Identify Records

See an example of how to create a marker, and review guidelines for how to create markers for Oracle Audit Vault and Database Firewall.

The collector must generate markers which the collection framework uses to uniquely identify a record.

The collector must generate a unique marker for each record in a particular trail. It can use more than one event field to create a marker. It can even use information not present in the audit event, such as, table name, file name, file creation time, and so on if it is not a template-based collection plug-in. Smaller sized markers are preferred because they take less time to create, less space in recovery phase, and less time to match. Markers are useful in certain scenarios, particularly in the recovery phase, where any records which were sent after the last checkpoint must be filtered. When the collection plug-in starts collecting events, it starts from the last checkpoint of the last run. However, some records might have been collected after that checkpoint and sent to the Audit Vault Server. To prevent duplication, the Collection Framework compares incoming records against existing records in the Audit Vault Server using the record markers.

In the following example, The strings Session_ID and Entry_ID are used to form a marker.

Example 4-16 Creating Markers

public String getMarker() throws AuditEventCollectorException {
      // ENTRY_ID will identify an audit event uniquely with in a session. Hence
      // ENTRY_ID along with SESSION_ID will uniquely identify an audit event
      // across sessions.
      try {
         return m_resultSet.getString("SESSION_ID") + ":"
               + m_resultSet.getString("ENTRY_ID");
      } catch (SQLException ex) {
           throw new AuditEventCollectorException(
               ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
      }
   }

4.5.13 Closing the Java-Based Collection Plug-in

See how you can close a Java-based collection plug-in for Oracle Audit Vault and Database Firewall.

The process of closing a Java-based collection plug-in begins when the Collection Framework receives a command to stop collecting a particular audit trail, causing the Collection Framework to notify the collector using the close() method. In this method, the collector performs clean-up tasks, such as closing database connections or file handles. Once the close() method returns control to the Collection Framework, the collection thread ends.

This method should not result in any exceptions errors. In case of an exception, it should log an error message, as shown in the following example.

Example 4-17 Calling Close and Releasing Resources

public void close() {
      try {
         if (m_resultSet != null) {
            m_resultSet.close();
            m_resultSet = null;
         }
         if (m_connectionManager != null) {
            m_connectionManager.destroy();
            m_connectionManager = null;
         }
         m_previousCheckpoint = null;
         m_nextCheckpoint = null;
         m_logger = null;
      } catch (SQLException ex) {
         m_logger.logError("SampleEventCollector", "close",
               "SQLException occurred. ", ex);
      } catch (AuditException ex) {
         m_logger.logError("SampleEventCollector", "close",
               "AuditException occurred. ", ex);
      }
   }  

4.5.14 Using Exceptions in Collection Plug-ins

An Oracle Audit Vault and Database Firewall collector can generate several different types of exceptions.

The collector can throw the following two checked exceptions: AuditEventCollectorException and SetAttributeException.

The setAttribute method can throw the SetAttributeException exception when the method cannot set a new attribute. Upon receiving this exception, it is possible that the Collection Framework does not stop the collector (see an example in "Changing Audit Vault and Database Firewall Attributes at Run Time").

The rest of the methods except the close method throw AuditEventCollectorException. This exception must be thrown only if an unrecoverable condition has occurred. Upon receiving this exception, the Collection Framework stops the collector. Before stopping the collector, the Collection Framework calls close method. See the previous sections for sample code to create and throw these exceptions.

4.6 Java-Based Collection Plug-in Utility APIs

In addition to the Collection Framework, the Oracle Audit Vault API includes Java utility APIs that make the task of writing a collector easier.

4.6.1 About Connection to Database Sources Using ConnectionManager API

All of the Oracle Audit Vault and Database Firewall components (collectors, agents, and server) that are written in Java must use the ConnectionManager API to manage their connections to databases.

You use the ConnectionManager API to manage connections to source databases, such as Oracle Database, Microsoft SQL Server, Sybase Adaptive Server, and IBM DB2.

Benefits of Using the ConnectionManager API

  • It reduces the resource usage on the database server.

  • It makes client-side operations more graceful, so clients do not hang or die abruptly.

  • It provides better performance.

You must instantiate a concrete implementation of the connection manager with the appropriate parameters required for setting up a connection pool. Several constructors are available for use. All optional parameters that are not supplied by the caller take default Oracle Audit Vault-specific values, as follows:

  • CONNECTION_FACTORY_CLASSNAME=oracle.jdbc.pool.OracleDataSource

  • MIN_POOL_SIZE=0

  • INACTIVE_CONNECTION_TIMEOUT=1800

  • INITIAL_POOL_SIZE=0

  • VALIDATE_CONNECTION_ON_BORROW=true

4.6.2 Example of Using the ConnectionManager API to Connect to Database Sources

See how to use the ConnectionManager API to manage Oracle Audit Vault and Database Firewall Java compnent connections to databases.

The ConnectionManager API is based on the acquire, use, and release model for managing the database connections. All of the Oracle Audit Vault and Database Firewall components (collectors, agents, and server) that are written in Java must use the ConnectionManager API to manage their connections to databases.
The caller is expected to complete these steps in the order shown:
  1. Create an instance of ConnectionManager API.
  2. Get a connection to a database.
  3. Use the connection
  4. Release the connection back to the pool.
  5. Repeat steps 2 through 4 as many times as needed.
  6. Destroy the Connection Manager instance.

Example 4-18 Using the Connection Manager to Handle Connection Pooling

//Connection Manager
ConnectionManager cManager = null;
 
try {
  /*
   * Connection Pool Properties.
   * Set the pool properties such as URL
   * Initial pool size, Min pool size, etc.
   * The set of supported connection pool properties are
   * documented in the Oracle UCP documentation
   */
  Properties pProps = new Properties(); 
  pProps.put(URL, "jdbc:oracle:thin:@hostname:port:sid");
 
  /*
   *  Connection Properties
   *
   *  Set the connection properties here.
   *  The set of connection properties that can be set
   *  depends on the driver.  To enable SSL using the
   *  the oracle jdbc driver, you need to set the following
   *     Properties cProps = new Properties();
   *     String walletLoc =    "/path/to/walletdirectory/cwallet.sso";
   *     cProps.setProperty("oracle.net.authentication_services","(TCPS)");
   *     cProps.setProperty("javax.net.ssl.trustStore", walletLoc);
   *     cProps.setProperty("oracle.net.ssl_server_dn_match", "true")  ;
   *     cProps.setProperty("javax.net.ssl.trustStoreType","SSO");
   *     cProps.setProperty("javax.net.ssl.keyStore", walletLoc);
   *     cProps.setProperty("javax.net.ssl.keyStoreType","SSO");
   */
  Properties cProps = new Properties();
 
  cManager =  new ConnectionManagerImpl(pProps, cProps);
           
  String username;
  char[] passwd;
  Connection conn = null;
 
  /* Do something */
  ...
 
  /* Retrieve and set the username and password for user1 */
  username = "user1";
  passwd   = "user1passwd".toCharArray();
 
  /* Get a connection as "user1"*/
  conn = cManager.getConnection(username, passwd);      
 
 
  /* Use the "user1" connection and do something useful */
  ...         
 
  /* Release the connection */
  cManager.releaseConnection(conn);
          
  /* Retrieve and set the username and password for user2 */
  username = "user2";
  passwd   = "user2passwd".toCharArray();
 
  /* Get a connection as "user2" */
  conn = cManager.getConnection(username, passwd);      
 
 
  /* Use the "user2" connection and do something useful */
  ...         
 
  /* Release the connection */
  cManager.releaseConnection(conn);
 
} catch (Exception e) {
  /* Take appropriate action here */
 
} finally {
  if (cManager != null) {
    try {
      cManager.destroy();
    } catch (AuditException ae) {
      /* Take appropriate action here */
    }
} 

The ConnectionManager API is designed so that a caller can acquire and release database connections using different user credentials at any point in time. For example, a caller can acquire a connection using alice's database credentials, and then later on acquire a connection with robert's database credentials using the same connection manager.

Note:

Ensure that the caller does not do the following:

  • Keep a reference to the connection locally (through an instance or class variable).
  • Hold on to the connection for a long time.

These requirements enable the connection pool to automatically recover connections that have the following behaviors:

  • They have exceeded the TIME_TO_LIVE time limit.
  • They have abandoned connections, that is, connections that have not been in use for a while.
  • There are connections that have been borrowed too many times. This requirement ensures that they to avoid resource leaks.

4.6.3 Using the Windows Event Log Access API

To parse Microsoft Windows event logs, you can use the Microsoft Windows EventLog API.

The Windows EventLog API is a wrapper on Windows APIs that access the Windows Event Log. This API is available only on the Windows platform, for collectors that need to extract audit records.

The following diagram shows the classes that you can use to parse the Windows event logs.

Figure 4-3 Structure of Windows Event Logs


Description of Figure 4-3 follows
Description of "Figure 4-3 Structure of Windows Event Logs"

The EventLogRecord class contains one record in the event log. The EventLogReader class helps to fetch event log records one by one. Operator classes help filter the event log records. An operator works on a particular field of event log record and determines whether the record is to be filtered based on the value of the field. For example, you can use the Equals operator to filter all event log records where the value of the field does not equal the value specified. The InRange and OutsideRange operators are ternary operators. The rest are binary operators.

To collect event log records, follow these steps:

  1. Create the EventLogReader instance.

    For example, to open the application event log:

    EventLogReader eventLogReader = new EventLogReader();
    eventLogReader.openLog()
    

    To open other event logs such security or system event logs, use the overloaded method openLog(String log). For example, to open a security event log:

    EventLogReader eventLogReader = new EventLogReader();
    eventLogReader.openLog("Security");
    

    To open an application event log from a specific record number, use the openLog(int startRecNum) overloaded method. For example, to open an application event log from audit record number 1234:

    EventLogReader eventLogReader = new EventLogReader();
    eventLogReader.openLog(1234);
    

    To open security or system event logs from a specific record number, use the overloaded method, openLog(String log, int startRecNum). For example, to open a security event log from record number 4321:

    EventLogReader eventLogReader = new EventLogReader();
    eventLogReader.openLog("Security",4321);
    
  2. Add the appropriate filters.

    For example, to bind an equals filter to the SourceName field, so that the EventLogReader only receives records that have the source name MSSQL$SQLEXPRESS:

    eventLogReader.addFilter(EventLogReader.SOURCE_NAME,
    Equals.getInstance(), "MSSQL$SQLEXPRESS");
    

    To get event records between Timestamp, m_lowerBoundTime, and m_upperBoundTime, use following filters:

    m_eventLogReader.addFilter(EventLogReader.TIME_GENERATED, GreaterThan.getInstance(), m_lowerBoundTime);
    
    m_eventLogReader.addFilter(EventLogReader.TIME_GENERATED, LessThan.getInstance(), m_upperBoundTime);
    
  3. Fetch and process the EventLogRecord.

    The following example code obtains the next EventLogRecord, and extracts various fields from it.

    if(eventLogReader.hasNext()) {
                  EventLogRecord record = (EventLogRecord)eventLogReader.next();
                  Long eventID = record.getEventId(); 
                  String userID = record.getUserSid();
                  String hostName = record.getComputerName();
      ...
       }
    
  4. Close the EventLogReader instance.

    When the collector is stopped, use the following code to close down the EventLogReader instance:

    eventLogReader.closeLog();

4.6.4 Using Windows EventMetaData API

To obtain metadata of events, you can use this Microsoft Windows Metadata Java API procedure.

Microsoft Windows provides a new API that can obtain metadata of events from version 2008 and on. Given a publisher name, this API obtains metadata for each event. The following figure illustrates how the Windows Metadata Java is a wrapper over the Windows API.

Figure 4-4 EventMetaData_Classes

Description of Figure 4-4 follows
Description of "Figure 4-4 EventMetaData_Classes"

The EventMetaDataRecord contains the metadata of one event. The EventMetaDataReader helps to fetch event metadata records one by one. Use this API as follows:

  1. Create an instance of EventMetaDataReader.
    EventMetaDataReader eventMetaDataReader = new EventMetaDataReader();
     
  2. Obtain metadata of all events for a publisher.
    Map<Long, List<EventMetaDataRecord>> eventLogRecordMap = eventMetaDataReader 
       getEventMetaData("Microsoft-Windows-Security-Auditing");
    
  3. Obtain the metadata list of a particular event from the map.
    List<EventMetaDataRecord> eventRecordList = m_eventRecordMap
       get(m_eventLogRecord.getEventId());
    
  4. Obtain metadata from the list, and obtain event data names from it.
    EventMetaDataRecord eventMetaDataRecord = eventRecordList.get(i);
    for(int i=0; i<eventMetaDataRecord.getNumEventDataNames(); i++) {
       String eventDataName = eventMetaDataRecord.getEventDataName(i);
       ....
    }

4.6.5 Using the AVLogger API to Log Messages

To log errors, warnings, informational, and debug messages into the Oracle Audit Vault and Database Firewall logs, you can use the AVLogger API.

Example 4-19 Using the AVLogger API

import oracle.av.platform.common.util.AVLogger;
import oracle.av.platform.common.exception.AuditException;
import oracle.av.platform.common.AuditErrorCodes;
 
public class Test
{
  public static void main(String[] args)  {
     /* Logger objects */
     AVLogger myModule = null;
     try {
       /* get Logger instances; this will auto-create the logger instance */
       /* if one does not exist */
       myModule = AVLogger.getLogger("someModule");
       /* print INFO level message */
       /* check log level if you are concatenating strings to avoid expensive */
       /* string operations */
       if(myModule.isInfoEnabled()) {
           avServer.logInfo("Testing INFO level message...." + "another String" +
             "one more string"); 
       }
       /* No need to check the log level if there is no string concatenation */
       myModule.logInfo("Testing INFO level message for another component....");
       /* changing the log level dynamically */
       myModule.setLogLevel(AVLogger.AV_LOG_LEVEL_DEBUG);
       myModule.logWarn("Testing WARN level message ....");
       myModule.logDebug("Testing DEBUG level message ....");
       /* Reset the log level back to INFO */
       myModule.setLogLevel(AVLogger.AV_LOG_LEVEL_INFO);
       /* Testing Exceptions: For now on, all exceptions will have */
       /* an OAV-XXXX error code printed out automatically as long as */
       /* they derive from AuditException object */
       throw new AuditException (ErrorCodes.INTERNAL_ERROR, null, null);
     } catch (Exception e) {
       myModule.logError(e);
     }
  }
}

4.6.6 Using the Oracle XML Developer's Kit to Parse XML Files

If you are developing collections, then you can use the Oracle XML Developer's Kit to parse XML files and extract audit records from them.

The Oracle XML Developer's Kit is included. and available to use to develop collections.

See Also:

Oracle XML Developer's Kit Programmer's Guide for detailed information.

4.7 Using an Audit Trail Cleanup with Java-Based Collection Plug-ins

Learn how you can enable audit trail clean-up on the source after Oracle Audit Vault and Database Firewall has archived an audit trail.

Audit trail clean-up is a feature that some sources provide to remove audit records after they have been archived. If this type of feature exists in the source, then an Oracle Audit Vault collection plug-in can integrate with the feature, to tell the source to what extent the audit trail has been archived. When Oracle Audit Vault provides archive status information, a source is enabled to clean up the audit trail (remove the original audit data) to that point, because the Oracle Audit Vault status indicates that the audit trail is archived, and deleting the audit trail to the point of the archive record results in no loss of data. The Oracle Audit Vault collection plug-in gives the clean-up utility information about the checkpoint, which is he point up to which data has been collected.

The collection plug-in can write archive status information into the directory agent_home\av\atc, to a file with a trail-specific name, using the following syntax SecuredTargetName_TrailID.atc (for example, oracl_1.atc).

The content of the atc file should consist of the following:

  • securedTargetType=Oracle
  • SecuredTargetName=orcl
  • TrailType=TABLE
  • TrailName=sys.aud$
  • 2016-04-15 10:26:53.7 (This time stamp represents the last checkpoint for these settings.)

The target clean-up utility can parse the checkpoint from the atc file and purge audit records till this timestamp from audit trail.

For example, Oracle Database sources provide a target cleanup utility, in the DMBS_AUDIT_MGMT PL/SQL package. The Oracle Database prepackaged collection plug-ins integrate with the DMBS_AUDIT_MGMT package, which enables audit trail cleanup operations on the source.

4.8 Java-Based Collection Plug-in Security Considerations

Oracle strongly recommends that you review security guidelines before developing Java-based collection plug-ins.

For sources, such as databases, that require a connection in order to extract audit records, it is your responsibility, as the developer, to properly document the privileges needed to perform this task. Oracle recommends that the account used for connection have only the minimal privileges needed for the job. Any extra privileges can create a security issue.

You must also parse the input audit records properly, and protect Oracle Audit Vault and Database Firewall (Oracle AVDV) from malicious data. For instance, audit records can be crafted to inject SQL or HTML into the audit trail, which could expose data stored in Oracle AVDF to attacks. You must ensure that all incoming audit data is sanitized properly before it is given to the Collection Framework.