Writing Custom Rules for OSSEC: OpenVPN Edition

2 minute read Published:

I wrote some custom rules for OpenVPN on OSSEC yesterday; the full step-by-step with instructions for beginners is included in the latest revision of The Seven Minute Server. But I figure if you’re here, you were searching for this specifically, so here’s the basics:

Basic decoder in /var/ossec/etc/local_decoder.xml:

<decoder name="openvpn">  
<prematch>^\w\w\w\s\w\w\w\s+\d+\s\d\d(:)\d\d(:)\d\d\s\d\d\d\d</prematch>
</decoder>

I’ll be honest, I’m not a total fan of this approach, but the error logs aren’t formatted consistently, and the only static portion is the date; on the Amazon Linux AMI, it’s the only program that logs in this format (dracut is close, but adds timezone before the year). I’m also not a fan of not being able to \w{3} or \d{4} in OSSEC rules, but such is the way of the world…and at least we have regexability.

If I was logging to syslog, I could just decode on program name, but I want OpenVPN’s logs to go to their own spot where they can be overwritten on restart and periodically deleted without mucking with syslog history.

Here are the subsequent decoders; we need two because different errors show the client IP in different spots in the log string. The first one will catch authentication errors and the second will catch someone connecting without their TLS auth key:

<decoder name="openvpn-connection">
  <parent>openvpn</parent>
  <regex offset="after_parent">::ffff:(\d+.\d+.\d+.\d+) TLS Error</regex>
  <order>srcip</order>
</decoder>

<decoder name="openvpn-connection">
  <parent>openvpn</parent>
  <regex offset="after_parent">TLS Error: cannot locate HMAC in incoming packet from [AF_INET6]::ffff:(\S+):\d+</regex>
  <order>srcip</order>
</decoder>

Here are the rules I set up (/var/ossec/rules/local_rules.xml):

<group name="syslog,openvpn,">

  <rule id="700000" level="0">
     <decoded_as>openvpn</decoded_as>
     <description>OpenVPN messages to analyze</description>
  </rule>

  <rule id="700001" level="3">
     <if_sid>700000</if_sid>
     <match>TLS Error</match>
     <description>TLS Error</description>
     <group>OpenVPN error</group>
  </rule>

 <rule id="700002" level="10">
    <if_sid>700000</if_sid>
    <match>Peer connection Initiated</match>
    <description>Successful connection</description>
   <group>OpenVPN connection</group>
 </rule>
<rule id="700003" level="0">
    <if_sid>700000</if_sid>
    <srcip>192.168.0.15</srcip>
    <description>Ignore errors from 192.168.0.15</description>
  </rule>
</group>
  • 700000: Default catch-all rule
  • 700001: Catches any error
  • 700002: Catches successful connections; you may not want this, it’s noisy if you use your VPN on mobile, but I wanted it for testing.
  • 700003: Ignores any errors or connections from 192.168.0.15 – change this to your own IP address if you want to ignore alerts from your own connections/breakage testing.

If you’re customizing rules for your own installation and breaking things on your server and clients to catch errors to match, you probably want to change your logging method in /etc/openvpn/server.conf from log /var/log/openvpn.log to log-append /var/log/openvpn.log and restart OpenVPN to keep your logs from being blown away when you stop and start. Then switch it back to log after you’re done.