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 theAuditModuleclass which is an internal class to Payara Server. We can access it using thepayara-embedded-alldependency. Here is the POM structure we will use:
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:
1 – Since we want to notify interested parties on invalid security events by email, it’s best to reuse aJavaMailsession 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 theinitmethod:
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-configuredJavaMailsession object that will be used to send the email notifications.
2 – Since we want to notify on failed authentication events, we need to override theauthenticationmethod, and implement a simple check for the user and success of the event:
authentication method
@Overridepublicvoidauthentication(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 thewebInvocationandejbInvocationmethods and implement similar checks to the one we did on the previous method (We are ignoring thewebServicemethods to keep the example as simple as possible):
authorization methods
@OverridepublicvoidwebInvocation(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()));}}@OverridepublicvoidejbInvocation(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 theJavaMailSession object as well:
HighRiskUsersAuditModule.java
publicclassHighRiskUsersAuditModuleextends AuditModule {privatestaticfinal Logger LOG = Logger.getLogger(HighRiskUsersAuditModule .class.getName());privatestaticfinal String EMAIL_SUBJECT ="Audit Notification from Payara Server";private String mailSessionResourceName;private String fromAddress;private String recipients;private Set<String> highRiskUsers;@Overridepublicvoidinit(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());}@Overridepublicvoidauthentication(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()));}}@OverridepublicvoidwebInvocation(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()));}}@OverridepublicvoidejbInvocation(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));}}privatevoidsendEmail(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);returnnull;}}}
Now that we have written the code for our audit module, run a simplemvn installcommand to generate the JAR file that contains our class. Put this JAR either into the ${PAYARA_INSTALL_DIR}/libdirectory (so it applies to all domains) or the${DOMAIN_DIR}/libdirectory (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 aJavaMailsession 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:
Finally, we need to register the audit module on Payara Server. We can do this by going to the server-config →Security →Audit Modulessection in the administration console and pressing the New button:
You can also achieve the same result with the following asadmin command:
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/executeendpoint, let’s issue a request using cURL:
Second endpoint test
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:
Audit Trail #3
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.