Skip to content

Commit

Permalink
Merge pull request #8459 from inverse-inc/feature/parse_fortigate_dhc…
Browse files Browse the repository at this point in the history
…p_syslog

FortiGate event handler DHCP parser
  • Loading branch information
jrouzierinverse authored Jan 7, 2025
2 parents b994376 + 13accba commit d4916d9
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 7 deletions.
Binary file added docs/images/fortigate_syslog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions docs/installation/intrusion_detection_system_integration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ Action -
modify_node: mac, $mac, notes, $notes
=== FortiGate DHCP Parser
PacketFence is able to receive DHCP information from the FortiGate firewall.
On the PacketFence server:
Modify rsyslog configuration to allow incoming UDP packets by uncommenting the following two lines in [filename]`/etc/rsyslog.conf`:
$ModLoad imudp
$UDPServerRun 514
Configure [filename]`/etc/rsyslog.d/fortigate.conf` so it contains the following which will redirect fortigate log entries and stop further processing of current matched message (in that case 192.168.40.1 is the ip of the FortiGate):
if $fromhost-ip=='192.168.40.1' then /usr/local/pf/var/fortigate
& ~
Make sure the receiving alert pipe (FIFO) exists
mkfifo /usr/local/pf/var/fortigate
Restart the rsyslog daemon
service rsyslog restart
On the FortiGate side make sure to configure the syslog configuration as the following:
image::fortigate_syslog.png[scaledwidth="100%",alt="FortiGate Syslog"]
=== Suricata IDS
PacketFence already contains a event handler for Suricata. This is an example to raise a security event from a syslog alert on the Suricata SID.
Expand Down
88 changes: 88 additions & 0 deletions go/detectparser/fortigate_dhcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package detectparser

import (
"errors"
"regexp"
"strings"

"github.com/inverse-inc/go-utils/sharedutils"
)

var fortiGateDhcpRegexPattern1 = regexp.MustCompile(`(\w+)="([^"]*)"|(\w+)=([^\s]+)`)

type FortiGateDhcpParser struct {
Pattern1 *regexp.Regexp
parser
}

func (s *FortiGateDhcpParser) Parse(line string) ([]ApiCall, error) {
matches := s.Pattern1.FindAllStringSubmatch(line, -1)
var mac, ip, lease, hostname, ack string
var err error

attributes := make(map[string]string)
hostname = "N/A"
for _, match := range matches {
if match[1] != "" {
attributes[match[1]] = match[2]
} else {
attributes[match[3]] = match[4]
}
}

if value, ok := attributes["mac"]; ok {
mac = strings.ToLower(value)
}
if value, ok := attributes["ip"]; ok {
ip = value
}
if value, ok := attributes["lease"]; ok {
lease = value
}
if value, ok := attributes["hostname"]; ok {
hostname = value
}
if value, ok := attributes["dhcp_msg"]; ok {
ack = value
}

if ack != "Ack" {
// Silent error to avoid spamming logs
return nil, nil // errors.New("Not an Ack")
}

if ip, err = sharedutils.CleanIP(ip); err != nil {
return nil, errors.New("Invalid IP")
}

if err := s.NotRateLimited(mac + ":" + ip); err != nil {
return nil, err
}
apiCall := []ApiCall{
&PfqueueApiCall{
Method: "update_ip4log",
Params: []interface{}{
"mac", mac,
"ip", ip,
"lease_length", lease,
},
},
}
if hostname != "N/A" && hostname != "" {
apiCall = append(apiCall, &PfqueueApiCall{
Method: "modify_node",
Params: []interface{}{
"mac", mac,
"computername", hostname,
},
})
}
return apiCall, nil
}

func NewFortiGateDhcpParser(config *PfdetectConfig) (Parser, error) {
return &FortiGateDhcpParser{
Pattern1: fortiGateDhcpRegexPattern1,
parser: setupParser(config),
}, nil
}
36 changes: 36 additions & 0 deletions go/detectparser/fortigate_dhcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package detectparser

import (
"testing"
)

func TestFortiGateDhcpParse(t *testing.T) {
parser, _ := NewFortiGateDhcpParser(nil)
var parseTests = []ParseTest{
{
Line: `date=2024-12-24 time=11:56:25 devname="FGT50E3U16014289" devid="FGT50E3U16014289" logid="0100026001" type="event" subtype="system" level="information" vd="root" eventtime=1735059387564643583 tz="-0500" logdesc="DHCP Ack log" interface="VLAN_41" dhcp_msg="Ack" mac="B0:2A:43:C1:97:DC" ip=192.168.41.249 lease=300 hostname="Laptop" msg="DHCP server sends a DHCPACK"`,
Calls: []ApiCall{
&PfqueueApiCall{
Method: "update_ip4log",
Params: []interface{}{
"mac", "b0:2a:43:c1:97:dc",
"ip", "192.168.41.249",
"lease_length", "300",
},
},
&PfqueueApiCall{
Method: "modify_node",
Params: []interface{}{
"mac", "b0:2a:43:c1:97:dc",
"computername", "Laptop",
},
},
},
},
{
Line: `date=2024-12-24 time=11:56:25 devname="FGT50E3U16014289" devid="FGT50E3U16014289" logid="0100026001" type="event" subtype="system" level="information" vd="root" eventtime=1735059387564643583 tz="-0500" logdesc="DHCP Ack log" interface="VLAN_41" dhcp_msg="Nak" mac="B0:2A:43:C1:97:DC" ip=192.168.41.249 lease=300 hostname="Laptop" msg="DHCP server sends a DHCPACK"`,
Calls: nil,
},
}
RunParseTests(parser, parseTests, t)
}
8 changes: 5 additions & 3 deletions go/detectparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package detectparser
import (
"context"
"fmt"
"time"

cache "github.com/fdurand/go-cache"
"github.com/inverse-inc/packetfence/go/pfconfigdriver"
"github.com/inverse-inc/packetfence/go/pfqueueclient"
"time"
)

type PfdetectRegexRule struct {
Expand Down Expand Up @@ -99,8 +100,8 @@ func (*JsonRpcApiCall) Call() error {
}

type PfqueueApiCall struct {
Method string
Params interface{}
Method string
Params interface{}
}

func (c *PfqueueApiCall) Call() error {
Expand Down Expand Up @@ -140,6 +141,7 @@ type ParserCreater func(*PfdetectConfig) (Parser, error)
var parserLookup = map[string]ParserCreater{
"dhcp": NewDhcpParser,
"fortianalyser": NewFortiAnalyserParser,
"fortigate_dhcp": NewFortiGateDhcpParser,
"regex": NewGenericParser,
"security_onion": NewSecurityOnionParser,
"snort": NewSnortParser,
Expand Down
11 changes: 7 additions & 4 deletions go/detectparser/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package detectparser

import (
"github.com/google/go-cmp/cmp"
"errors"
"testing"

"github.com/google/go-cmp/cmp"
)

type ParseTest struct {
Line string
Calls []ApiCall
Err error
}

func RunParseTests(p Parser, tests []ParseTest, t *testing.T) {
for i, test := range tests {
calls, err := p.Parse(test.Line)
if err != nil {
t.Errorf("Error Parsing %d) %s", i, test.Line)
continue

if !errors.Is(test.Err, err) {
t.Errorf("Got expected error expected: `%v` got: `%v`", test.Err, err)
}

if !cmp.Equal(calls, test.Calls) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package pfappserver::Form::Config::Pfdetect::fortigate_dhcp;

=head1 NAME
pfappserver::Form::Config::Pfdetect::fortigate_dhcp - Web form for a pfdetect detector
=head1 DESCRIPTION
Form definition to create or update a pfdetect detector.
=cut

use HTML::FormHandler::Moose;
extends 'pfappserver::Form::Config::Pfdetect';
with qw(pfappserver::Base::Form::Role::PfdetectRateLimit);

has_field '+type' =>
(
default => 'fortigate_dhcp',
);

=over
=back
=head1 COPYRIGHT
Copyright (C) 2005-2024 Inverse inc.
=head1 LICENSE
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
=cut

__PACKAGE__->meta->make_immutable unless $ENV{"PF_SKIP_MAKE_IMMUTABLE"};
1;
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const setup = (props) => {
switch(unref(type)) {
case 'dhcp':
case 'fortianalyser':
case 'fortigate_dhcp':
case 'nexpose':
case 'security_onion':
case 'snort':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import i18n from '@/utils/locale'
export const types = {
dhcp: i18n.t('DHCP'),
fortianalyser: i18n.t('FortiAnalyzer'),
fortigate_dhcp: i18n.t('FortiGate DHCP'),
nexpose: i18n.t('Nexpose'),
regex: i18n.t('Regex'),
security_onion: i18n.t('Security Onion'),
Expand Down
2 changes: 2 additions & 0 deletions lib/pf/UnifiedApi/Controller/Config/EventHandlers.pm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use pf::ConfigStore::Pfdetect;
use pfappserver::Form::Config::Pfdetect;
use pfappserver::Form::Config::Pfdetect::dhcp;
use pfappserver::Form::Config::Pfdetect::fortianalyser;
use pfappserver::Form::Config::Pfdetect::fortigate_dhcp;
use pfappserver::Form::Config::Pfdetect::regex;
use pfappserver::Form::Config::Pfdetect::nexpose;
use pfappserver::Form::Config::Pfdetect::security_onion;
Expand All @@ -41,6 +42,7 @@ our %TYPES_TO_FORMS = (
map { $_ => "pfappserver::Form::Config::Pfdetect::$_" } qw(
dhcp
fortianalyser
fortigate_dhcp
nexpose
regex
security_onion
Expand Down
1 change: 1 addition & 0 deletions lib/pf/constants/pfdetect.pm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use warnings;
our @TYPES = qw(
dhcp
fortianalyser
fortigate_dhcp
nexpose
regex
security_onion
Expand Down

0 comments on commit d4916d9

Please sign in to comment.