diff --git a/go/detectparser/fortigate_dhcp.go b/go/detectparser/fortigate_dhcp.go new file mode 100644 index 000000000000..a20f13df20d6 --- /dev/null +++ b/go/detectparser/fortigate_dhcp.go @@ -0,0 +1,78 @@ +package detectparser + +import ( + "regexp" + + "github.com/inverse-inc/go-utils/sharedutils" +) + +var fortiGateDhcpRegexPattern1 = regexp.MustCompile(`\s+`) +var fortiGateDhcpRegexPattern2 = regexp.MustCompile(`\=`) + +type FortiGateDhcpParser struct { + Pattern1, Pattern2 *regexp.Regexp + parser +} + +func (s *FortiGateDhcpParser) Parse(line string) ([]ApiCall, error) { + matches := s.Pattern1.Split(line, -1) + var mac, ip, lease, hostname, ack string + var err error + for _, str := range matches { + args := s.Pattern2.Split(str, 2) + if len(args) <= 1 { + continue + } + + if args[0] == "mac" { + mac = args[1] + } else if args[0] == "ip" { + ip = args[1] + } else if args[0] == "lease" { + lease = args[1] + } else if args[0] == "hostname" { + hostname = args[1] + } else if args[0] == "dhcp_msg" { + ack = args[1] + } + } + + if ack == "" || ack != "Ack" { + return nil, nil + } + + if ip, err = sharedutils.CleanIP(ip); err != nil { + return nil, nil + } + + if err := s.NotRateLimited(mac + ":" + ip); err != nil { + return nil, err + } + + return []ApiCall{ + &PfqueueApiCall{ + Method: "update_ip4log", + Params: []interface{}{ + "mac", mac, + "ip", ip, + "lease_length", lease, + }, + }, + &PfqueueApiCall{ + Method: "modify_node", + Params: []interface{}{ + "mac", mac, + "computername", hostname, + }, + }, + }, nil + +} + +func NewFortiGateDhcpParser(config *PfdetectConfig) (Parser, error) { + return &FortiGateDhcpParser{ + Pattern1: fortiGateDhcpRegexPattern1, + Pattern2: fortiGateDhcpRegexPattern2, + parser: setupParser(config), + }, nil +} diff --git a/go/detectparser/fortigate_dhcp_test.go b/go/detectparser/fortigate_dhcp_test.go new file mode 100644 index 000000000000..e35907729ba8 --- /dev/null +++ b/go/detectparser/fortigate_dhcp_test.go @@ -0,0 +1,30 @@ +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="N/A" msg="DHCP server sends a DHCPACK"`, + Calls: []ApiCall{ + &PfqueueApiCall{ + Method: "event_add", + Params: []interface{}{ + "srcip", "172.21.5.11", + "events", map[string]interface{}{ + "detect": "0316013057", + }, + }, + }, + }, + }, + { + 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="N/A" msg="DHCP server sends a DHCPACK"`, + Calls: nil, + }, + } + RunParseTests(parser, parseTests, t) +} diff --git a/go/detectparser/parser.go b/go/detectparser/parser.go index 0aa546e29020..f9d5f75c492a 100644 --- a/go/detectparser/parser.go +++ b/go/detectparser/parser.go @@ -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 { @@ -99,8 +100,8 @@ func (*JsonRpcApiCall) Call() error { } type PfqueueApiCall struct { - Method string - Params interface{} + Method string + Params interface{} } func (c *PfqueueApiCall) Call() error { @@ -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, diff --git a/html/pfappserver/lib/pfappserver/Form/Config/Pfdetect/fortigate_dhcp.pm b/html/pfappserver/lib/pfappserver/Form/Config/Pfdetect/fortigate_dhcp.pm new file mode 100644 index 000000000000..312f1cb9e50f --- /dev/null +++ b/html/pfappserver/lib/pfappserver/Form/Config/Pfdetect/fortigate_dhcp.pm @@ -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; diff --git a/html/pfappserver/root/src/views/Configuration/eventHandlers/_components/TheForm.vue b/html/pfappserver/root/src/views/Configuration/eventHandlers/_components/TheForm.vue index ba7e06c46c8b..c87de635ad06 100644 --- a/html/pfappserver/root/src/views/Configuration/eventHandlers/_components/TheForm.vue +++ b/html/pfappserver/root/src/views/Configuration/eventHandlers/_components/TheForm.vue @@ -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': diff --git a/html/pfappserver/root/src/views/Configuration/eventHandlers/config.js b/html/pfappserver/root/src/views/Configuration/eventHandlers/config.js index 9da6d575b253..3ba96df69be6 100644 --- a/html/pfappserver/root/src/views/Configuration/eventHandlers/config.js +++ b/html/pfappserver/root/src/views/Configuration/eventHandlers/config.js @@ -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'), diff --git a/lib/pf/UnifiedApi/Controller/Config/EventHandlers.pm b/lib/pf/UnifiedApi/Controller/Config/EventHandlers.pm index b7ef0dc14a34..4ac87111892b 100644 --- a/lib/pf/UnifiedApi/Controller/Config/EventHandlers.pm +++ b/lib/pf/UnifiedApi/Controller/Config/EventHandlers.pm @@ -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; @@ -41,6 +42,7 @@ our %TYPES_TO_FORMS = ( map { $_ => "pfappserver::Form::Config::Pfdetect::$_" } qw( dhcp fortianalyser + fortigate_dhcp nexpose regex security_onion diff --git a/lib/pf/constants/pfdetect.pm b/lib/pf/constants/pfdetect.pm index 32cabd0e5cd6..4247bfca0546 100644 --- a/lib/pf/constants/pfdetect.pm +++ b/lib/pf/constants/pfdetect.pm @@ -18,6 +18,7 @@ use warnings; our @TYPES = qw( dhcp fortianalyser + fortigate_dhcp nexpose regex security_onion