Security Auditing in Payara Server - Part 2
Originally published on 14 Jul 2017
Last updated on 19 Jul 2017
Following up from the first part of the Security Auditing article, where we covered the audit logging, in this part we will focus on creating a custom audit module.
Creating a Custom Audit Module
Imagine that you are responsible for monitoring the access of specific high risk users that use the applications deployed on a Payara Server instance. There could be a need to monitor the access of these in your applications, so detecting unauthenticated and unauthorized access attempts to some services and notifying the relevant company staff of these access attempts will be important to you.
In the previous blog we mentioned that implementing an audit module is easy. Here we will give instructions on how to create a sample custom module that follows these requirements we have set.
First, we start by creating a sample Java application using Maven. Keep in mind that to write our audit module, we need access to the AuditModule
class which is an internal class to Payara Server. We can access it using the payara-embedded-all
dependency. Here is the POM structure we will use:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fish.payara.support</groupId>
<artifactId>CustomAuditModule</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>fish.payara.extras</groupId>
<artifactId>payara-embedded-all</artifactId>
<version>4.1.1.171.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>custom-audit-module</finalName>
</build>
</project>
Now, we need to create our custom audit module. Since we are only interested in detecting failed authentication and authorization events, we will proceed to override the following methods:
init(Properties properties)
authentication(String user, String realm, boolean success)
-
webInvocation(String user, HttpServletRequest request, String type, boolean success)
-
ejbInvocation(String user, String ejb, String method, boolean success)
1 - Since we want to notify interested parties on invalid security events by email, it's best to reuse a JavaMail session object, and we need both an address that will be used as the sender of the message and a list of the interested parties email addresses that will work as recipients of the message. We will use these values of properties for our audit module and configure them in the init
method:
init method
@Override
public void init(Properties properties) {
this.mailSessionResourceName = properties.getProperty("mailSession");
this.fromAddress = properties.getProperty("fromAddress");
this.recipients = properties.getProperty("recipients", "");
this.highRiskUsers = Arrays.asList(properties.getProperty("users", "").split(","))
.stream()
.map(String::trim)
.collect(Collectors.toSet());
}
The purpose of this audit module is to monitor invalid attempts made by a list of high risk users, so we also need to configure this list using a property. You can see that we are parsing this property as a comma-separated list of values and dropping them into a set for easy comparison.
Notice that we are also receiving a property that represents the JNDI reference name of a pre-configured JavaMail session object that will be used to send the email notifications.
2 - Since we want to notify on failed authentication events, we need to override the authentication
method, and implement a simple check for the user and success of the event:
@Override
public void authentication(String user, String realm, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authentication attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authentication attempt at %s", user, LocalDateTime.now()));
}
}
Simple! We are also logging our decision to send the notification email, which has a simple summary of the invalid event and a timestamp for time tracking purposes.
3 - Also, we want to notify on failed authorization events, so we will override the webInvocation
and ejbInvocation
methods and implement similar checks to the one we did on the previous method (We are ignoring the webService
methods to keep the example as simple as possible):
@Override
public void webInvocation(String user, HttpServletRequest request, String type, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authorization attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authorization attempt at %s, trying to access resource %s on method %s", user, LocalDateTime.now(), request.getRequestURI(), request.getMethod()));
}
}
@Override
public void ejbInvocation(String user, String ejb, String method, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authorization attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authorization attempt at %s, trying to access EJB %s, method %s", user, LocalDateTime.now(), ejb, method));
}
}
Pretty straightforward! As with the previous method, we are also logging our decisions to send notification emails, which contain simple summaries and timestamps as well.
Here is the complete code of the class, along with the methods used to send the email notification and getting the JavaMail Session object as well:
public class HighRiskUsersAuditModule extends AuditModule {
private static final Logger LOG = Logger.getLogger(HighRiskUsersAuditModule .class.getName());
private static final String EMAIL_SUBJECT = "Audit Notification from Payara Server";
private String mailSessionResourceName;
private String fromAddress;
private String recipients;
private Set<String> highRiskUsers;
@Override
public void init(Properties properties) {
this.mailSessionResourceName = properties.getProperty("mailSession");
this.fromAddress = properties.getProperty("fromAddress");
this.recipients = properties.getProperty("recipients", "");
this.highRiskUsers = Arrays.asList(properties.getProperty("users", "").split(","))
.stream()
.map(String::trim)
.collect(Collectors.toSet());
}
@Override
public void authentication(String user, String realm, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authentication attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authentication attempt at %s", user, LocalDateTime.now()));
}
}
@Override
public void webInvocation(String user, HttpServletRequest request, String type, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authorization attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authorization attempt at %s, trying to access resource %s on method %s", user, LocalDateTime.now(), request.getRequestURI(), request.getMethod()));
}
}
@Override
public void ejbInvocation(String user, String ejb, String method, boolean success) {
if (!success && highRiskUsers.contains(user)) {
LOG.log(Level.WARNING, "Audit: Detected invalid authorization attempt by user {0}, notifying staff.", user);
this.sendEmail(EMAIL_SUBJECT, String.format("The user %s has made an invalid authorization attempt at %s, trying to access EJB %s, method %s", user, LocalDateTime.now(), ejb, method));
}
}
private void sendEmail(String subject, String text) {
Session mailSession = getEmailSession();
if (mailSession != null) {
try {
Message message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(fromAddress));
message.setRecipients(RecipientType.TO, InternetAddress.parse(recipients));
message.setSubject(subject);
message.setText(text);
Transport.send(message);
} catch (MessagingException ex) {
LOG.log(Level.SEVERE, "Audit: Error sending email notification", ex);
}
}
}
private Session getEmailSession() {
try {
InitialContext context = new InitialContext();
return (Session) context.lookup(this.mailSessionResourceName);
} catch (NamingException ex) {
LOG.log(Level.SEVERE, "Audit: No mail session resource with name " + this.mailSessionResourceName + " found in the server", ex);
return null;
}
}
}
Now that we have written the code for our audit module, run a simple mvn install
command to generate the JAR file that contains our class. Put this JAR either into the ${PAYARA_INSTALL_DIR}/lib
directory (so it applies to all domains) or the ${DOMAIN_DIR}/lib
directory (so it applies to a specific domain only). This is to make sure that the audit module is on the server classpath for registration.
Before proceeding to register the module on the server, we need a JavaMail session resource that can be used to send notification emails. For testing purposes, we will run the following asadmin command to create a session that will connect to an Office 365 mail account:
create-javamail-resource --debug=false --storeProtocol=imap --transportProtocol=smtp --host=m.outlook.com --storeProtocolClass=com.sun.mail.imap.IMAPStore --from=fabio.turizo@payara.fish --transportProtocolClass=com.sun.mail.smtp.SMTPTransport --user=fabio.turizo@payara.fish --enabled=true --target=domain --property "mail.smtp.port=587:mail.smtp.auth=true:mail.smtp.starttls.enable=true:mail.smtp.ssl.trust=m.outlook.com:mail.smtp.password=XXXXXXX" mail/Office365Session
create-resource-ref --enabled=true --target=server mail/Office365Session
Finally, we need to register the audit module on Payara Server. We can do this by going to the server-config → Security → Audit Modules section in the administration console and pressing the New button:
You can also achieve the same result with the following asadmin command:
asadmin create-audit-module --classname fish.payara.support.customaudit.HighRiskUsersAuditModule --property mailSession=mail/Office365Session:fromAddress=security-audit@payara.fish:recipients=sample.user@payara.fish:users=bob high-risk-users
With our audit module correctly configured, we will use the test application we developed to test the default module. Since user bob is on the high users list and he doesn't have access to the /execute
endpoint, let's issue a request using cURL:
curl -u bob:bob -X POST http://localhost:8080/audit-module-test/resources/execute --data "This is a test message from bob, which will be denied from JAAC authorization"
This will generate the following log entries on the server and the following email notification will be delivered to the configured address, sample.user@payara.fish:
Warning: Audit: Detected invalid authorization attempt by user bob, notifying staff
And that's it! Our custom audit module is working correctly and auditing invalid attempts made by the users in the configured list, as intended.
Asynchronous notification warning
Keep in mind that that the code in the overridden methods of the audit module will be called in a synchronous manner, which can slow down and affect the performance of secured applications. If you want to implement this code on a production environment, we would recommend wrapping the email notification code in an asynchronous block (you can even get a ManagedExecutorService
via JNDI as well), to avoid impacting application performance in a negative way.
Summary
Security audit modules are an obscure feature inherited from GlassFish, but a powerful feature nonetheless. With the default audit module shipped into Payara Server you already can create a simple audit trail of all the authentication and authorization decisions made by all the application you have deployed on the server, which is a good starting point for many organizations considering implementing security auditing into their applications.
If you need to go beyond those capabilities and create specific audit reactions based on the security requirements set by your organization, developing your own modules which can use the server's resources as in this example will be very helpful. The pluggable nature of these modules makes them easy to integrate into the server's workings, and their customization capabilities allows for greater fine tuning; is the default logging slow for critical applications? Then implement a module that uses faster logging mechanism, for example!
If you are considering using this feature but think it could be improved by including more entry points for authorization requests or include more information to the audit modules themselves, then let us know! We are open to any suggestions that help us improve and expand the uses cases for audit modules.
Related Posts
Celebrating 25 Years of the CVE Program
Published on 22 Oct 2024
by Chiara Civardi
0 Comments
The Common Vulnerabilities and Exposures (CVE®) Program is celebrating its 25th anniversary today! This marks a major milestone in global cybersecurity. Since 1999, the CVE Program has been critical in helping organizations identify, manage and ...
Eclipse Foundation’s New Open Regulatory Compliance Working Group Launch
Published on 01 Oct 2024
by Dawn Baird
0 Comments