Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FortiGate event handler DHCP parser #8459

Merged
merged 11 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
}
}

jrouzierinverse marked this conversation as resolved.
Show resolved Hide resolved
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",
},
},
},
},
jrouzierinverse marked this conversation as resolved.
Show resolved Hide resolved
{
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