FortiGate onboard automation: Stitches, auto-scripts, batch mode

A FortiGate comes with several tools built in to help us with automation and responding to events. In this post, I want to explore these options a bit more and show what you can do with them.

So, dear reader, let’s see what we can do with these tools!

The setup:

  • 2x FortiGate 70G running 7.6.6
  • A FortiAnalyzer VM running 7.6.6

Automation stitches

The biggest, most powerful, and most dynamic option a FortiGate offers are automation stitches. You combine actions, which do something, and triggers, which respond to something, in a stitch, where you handle the logic and the flow, like sequential and parallel execution of actions and delays.

Before I get more into this part, please check out Yuri Slobodyanyuk’s blog post on automation stitches because he already did a lot of work on this topic.

Automation actions

Actions in stitches are the things actually being done. You can send notifications to Slack, Teams, send mails, trigger functions in cloud environments, execute CLI scripts, or execute webhooks.

There isn’t much to say about actions because they are rather self-explanatory, but one thing I want to mention is that CLI scripts are limited to 1023 characters, and you can use some variables in them.

FortiGate automation action help

Automation triggers

Triggers are the second thing we need to care about, because if we don’t know what event interests us, we won’t do anything in response.

FortiOS comes with some default triggers, like Configuration Change, HA Failover, and Conserve Mode, but I will focus on FortiOS Event Log, FortiAnalyzer Event Handler, and Incoming Webhook, because they offer the most flexibility.

FortiOS Event Log triggers

You cannot trigger on every event log, but the ones we can choose from are vast and knowing how to work with them is important.

Let’s take a look at two practical examples.

Example: Link monitor events

First, assume that you are monitoring a server using a link monitor and you need to respond to it being down, like enabling a static route.

The Link monitor status event produces the following log entry:

date=2026-05-02 time=16:19:29 eventtime=1777738768978397230 logid="0100022922" type="event" subtype="system" level="notice" vd="root" logdesc="Link monitor status" name="WIN-AD" msg="Link monitor state is changed from 0 to 1, please check if this triggers HA failover."
Link monitor log details

In this case, it is a down event (changed from 0 to 1). There is also a Link monitor status warning event, which makes this particular scenario clearer and easier to work with, but I won’t focus on that for a reason I will get to later.

In the log details, we can see the message (msg) and the link monitor name (name). This information is enough to build an automation trigger specific to this link monitor using Field filters.

Link monitor down trigger
config system automation-trigger
	edit "LINK-MONITOR-DOWN"
        set event-type event-log
        set logid 22922
        config fields
            edit 1
                set name "name"
                set value "WIN-AD"
            next
            edit 2
                set name "msg"
                set value "*from 0 to 1*"
            next
        end
    next
end

The wildcard symbol * does the magic here, because it allows us to focus on only what we need in the message and the name field covers the specific link monitor.

Use the trigger in a stitch and add whatever action you need. You can also build the reverse trigger, i.e. a link monitor coming alive, using the same event and name, but the message *from 1 to 0*.

The reason I am not using the Link monitor status warning event here is that a dead link monitor uses the warning event (ID 0100022932) in addition to the Link monitor status event (ID 0100022922). A link monitor coming alive uses only Link monitor status (ID 0100022922). I don’t want to use two different IDs for one process (link monitor alive and dead), but you can choose to do that.

Here are all the log messages for these scenarios:

date=2026-05-02 time=16:19:41 eventtime=1777738780479592552 logid="0100022922" type="event" subtype="system" level="notice" vd="root" logdesc="Link monitor status" name="WIN-AD" msg="Link monitor state is changed from 1 to 0, please check if this triggers HA failover."
date=2026-05-02 time=16:19:41 eventtime=1777738780479544752 logid="0100022922" type="event" subtype="system" level="notice" vd="root" logdesc="Link monitor status" name="WIN-AD" interface="undefined" probeproto="ping" msg="Link Monitor changed state from dead to alive, protocol: ping."
date=2026-05-02 time=16:19:29 eventtime=1777738768978397230 logid="0100022922" type="event" subtype="system" level="notice" vd="root" logdesc="Link monitor status" name="WIN-AD" msg="Link monitor state is changed from 0 to 1, please check if this triggers HA failover."
date=2026-05-02 time=16:19:29 eventtime=1777738768978357571 logid="0100022932" type="event" subtype="system" level="warning" vd="root" logdesc="Link monitor status warning" name="WIN-AD" interface="undefined" probeproto="ping" msg="Link Monitor changed state from alive to dead, protocol: ping."
Link monitor log entries

Example: BGP neighborship events

The second example is around BGP neighborships. Assume that you want to create a trigger for a specific BGP neighbor going down and enable a static route if such an event happens. If we look at the logs for a BGP neighborship down event, we see the following:

date=2026-05-02 time=16:23:18 eventtime=1777738998290854913 logid="0103020300" type="event" subtype="router" level="warning" vd="root" logdesc="BGP neighbor status changed" msg="BGP: %BGP-5-ADJCHANGE: VRF 0 neighbor 198.51.100.1 Down BGP Notification FSM-ERR"
BGP down log detail

In this case, we only have the message to work with, so a trigger to respond to such an event can look like this:

BGP neighbor down trigger
config system automation-trigger
    edit "BGP-DOWN"
        set event-type event-log
        set logid 20300
        config fields
            edit 1
                set name "msg"
                set value "*neighbor 198.51.100.1 Down*"
            next
        end
    next
end

A full debug of the event being generated and the automation stitch firing can be seen here (in my case, I am simply disabling port2):

BGP down event debug

autod(pid:1622) log packet: total sz:470 data sz:230 fld_num:8

autod(pid:1622) log datetime: 2026-05-01 11:56:27

autod(pid:1622) log header: logid:20300 vfid:0 sever:4 cat:1 subcat:3 key:0 flags:0404 reqlen:110 timestamp:1777636586845355006
fields:
                id:10 name:(9)eventtime value:(19)1777636586845355006
                id:2 name:(5)logid value:(10)0103020300
                id:3 name:(4)type value:(5)event
                id:4 name:(7)subtype value:(6)router
                id:5 name:(5)level value:(7)warning
                id:6 name:(2)vd value:(4)root
                id:38 name:(7)logdesc value:(27)BGP neighbor status changed
                id:24 name:(3)msg value:(80)BGP: %BGP-5-ADJCHANGE: VRF 0 neighbor 198.51.100.1 Down BGP Notification FSM-ERR
pid:1622-__subscr_close_cur_pkg()-141: close package size:1536 logs:2
__action_cli_script_open()-169: cli script action:PORT2-DOWN is called. svc ctx:0x559d57c000
accprof:super_admin script:
config system interface
edit port2
set status down
next
end

__read_cli_script_result()-117: cli script:
autod.10
 output:

========== #1, 2026-05-01 11:56:27 ==========
70G-02  config system interface
70G-02 (interface)  edit port2
70G-02 (port2)  set status down
70G-02 (port2)  next
70G-02 (interface)  end

======= end of #1, 2026-05-01 11:56:27 ======


__action_cli_script_close()-219: cli script action is done. script:
config system interface
edit port2
set status down
next
end
output:

========== #1, 2026-05-01 11:56:27 ==========
70G-02  config system interface
70G-02 (interface)  edit port2
70G-02 (port2)  set status down
70G-02 (port2)  next
70G-02 (interface)  end

======= end of #1, 2026-05-01 11:56:27 ======


pid:1622-__handle_msg()-428: Subscriber:4 received package. pubid:0 pkgid:1206 pkg_index:0
pid:1622-__handle_pkg_logs()-370: Subscriber:4 processing package size:2253 logs:3 pickup:3

autod(pid:1622) log packet: total sz:1194 data sz:252 fld_num:12

autod(pid:1622) log datetime: 2026-05-01 11:56:28

autod(pid:1622) log header: logid:46600 vfid:0 sever:5 cat:1 subcat:0 key:0 flags:0484 reqlen:131 timestamp:1777636587533074284
fields:
                id:10 name:(9)eventtime value:(19)1777636587533074284
                id:2 name:(5)logid value:(10)0100046600
                id:3 name:(4)type value:(5)event
                id:4 name:(7)subtype value:(6)system
                id:5 name:(5)level value:(6)notice
                id:6 name:(2)vd value:(4)root
                id:38 name:(7)logdesc value:(27)Automation stitch triggered
                id:321 name:(6)stitch value:(8)BGP-DOWN
                id:322 name:(7)trigger value:(8)BGP-DOWN
                id:377 name:(12)stitchaction value:(10)PORT2-DOWN
                id:226 name:(4)from value:(3)log
                id:24 name:(3)msg value:(29)stitch:BGP-DOWN is triggered.

autod(pid:1622) log packet: total sz:626 data sz:298 fld_num:14

autod(pid:1622) log datetime: 2026-05-01 11:56:28

autod(pid:1622) log header: logid:44547 vfid:0 sever:6 cat:1 subcat:0 key:0 flags:0404 reqlen:180 timestamp:1777636587767735898
fields:
                id:10 name:(9)eventtime value:(19)1777636587767735898
                id:2 name:(5)logid value:(10)0100044547
                id:3 name:(4)type value:(5)event
                id:4 name:(7)subtype value:(6)system
                id:5 name:(5)level value:(11)information
                id:6 name:(2)vd value:(4)root
                id:38 name:(7)logdesc value:(27)Object attribute configured
                id:57 name:(2)ui value:(11)auto-script
                id:12 name:(6)action value:(4)Edit
                id:59 name:(6)cfgtid value:(9)600113152
                id:62 name:(7)cfgpath value:(16)system.interface
                id:63 name:(6)cfgobj value:(5)port2
                id:64 name:(7)cfgattr value:(16)status[up->down]
                id:24 name:(3)msg value:(27)Edit system.interface port2

autod(pid:1622) log packet: total sz:433 data sz:193 fld_num:8

autod(pid:1622) log datetime: 2026-05-01 11:56:28

autod(pid:1622) log header: logid:32549 vfid:0 sever:6 cat:1 subcat:0 key:0 flags:0404 reqlen:67 timestamp:1777636587778632118
fields:
                id:10 name:(9)eventtime value:(19)1777636587778632118
                id:2 name:(5)logid value:(10)0100032549
                id:3 name:(4)type value:(5)event
                id:4 name:(7)subtype value:(6)system
                id:5 name:(5)level value:(11)information
                id:6 name:(2)vd value:(4)root
                id:38 name:(7)logdesc value:(29)Autoscript stop automatically
                id:24 name:(3)msg value:(37)script autod.10 stopped automatically

You can also create a reverse trigger and use the BGP neighbor up message.

Here are all the log messages for these scenarios:

date=2026-05-02 time=16:23:47 eventtime=1777739026657108610 logid="0103020300" type="event" subtype="router" level="warning" vd="root" logdesc="BGP neighbor status changed" msg="BGP: %BGP-5-ADJCHANGE: VRF 0 neighbor 198.51.100.1 Up "
date=2026-05-02 time=16:23:47 eventtime=1777739026657075206 logid="0103020300" type="event" subtype="router" level="warning" vd="root" logdesc="BGP neighbor status changed" msg="BGP: %BGP-5-ADJCHANGE: VRF 0 neighbor 198.51.100.1 Down Peer being deleted"
date=2026-05-02 time=16:23:47 eventtime=1777739026657040239 logid="0103020304" type="event" subtype="router" level="warning" vd="root" logdesc="Routing log warning" msg="BGP: %BGP-3-NOTIFICATION: sending to 198.51.100.1 6/0 (CeaseUnspecified Error Subcode) 0 data-bytes []"
date=2026-05-02 time=16:23:18 eventtime=1777738998290854913 logid="0103020300" type="event" subtype="router" level="warning" vd="root" logdesc="BGP neighbor status changed" msg="BGP: %BGP-5-ADJCHANGE: VRF 0 neighbor 198.51.100.1 Down BGP Notification FSM-ERR"
date=2026-05-02 time=16:23:18 eventtime=1777738998290842322 logid="0103020304" type="event" subtype="router" level="warning" vd="root" logdesc="Routing log warning" msg="BGP: %BGP-3-NOTIFICATION: received from 198.51.100.1 4/0 (Hold Timer Expired/Unspecified Error Subcode) 0 data-bytes []"
BGP Up/Down log entries
config system automation-trigger
    edit "BGP-UP"
        set event-type event-log
        set logid 20300
        config fields
            edit 1
                set name "msg"
                set value "*neighbor 198.51.100.1 Up*"
            next
        end
    next
end

Incoming Webhook

This option is powerful, but boring. You create the trigger, and you get a URL to send HTTP POST requests to. Moving on… 

An automation debugging aside

If you ever need to debug automation stitches, you do that using the autod application and a triggered stitch looks like this:

Automation stitch executed

__action_cli_script_open()-169: cli script action:PORT2-DOWN is called. svc ctx:0x55a8894cf0
accprof:super_admin script:
config system interface
edit port2
set status down
next
end

__read_cli_script_result()-117: cli script:
autod.0
 output:

========== #1, 2026-05-02 17:28:27 ==========
70G-02  config system interface
70G-02 (interface)  edit port2
70G-02 (port2)  set status down
70G-02 (port2)  next
70G-02 (interface)  end

======= end of #1, 2026-05-02 17:28:27 ======


__action_cli_script_close()-219: cli script action is done. script:
config system interface
edit port2
set status down
next
end
output:

========== #1, 2026-05-02 17:28:27 ==========
70G-02  config system interface
70G-02 (interface)  edit port2
70G-02 (port2)  set status down
70G-02 (port2)  next
70G-02 (interface)  end

======= end of #1, 2026-05-02 17:28:27 ======

Some more options regarding debugging are in Yuri’s blog post (again, check it out), like testing, statistics, etc.

One thing I think is interesting is the diagnose test application autod 1 option. In Yuri’s blog, the description is “Enable automation stitches logging.” but the description of the option is actually “Enable/disable log dumping”, and this really does mean that. It dumps all logs straight to the CLI.

Turn it on, and you get a live feed of every log being created, and since not only the examples of link monitors or BGP events create log entries, but also configuration changes, we can get some nice information quickly. Here is the output of a new firewall policy being created, as well as the regular log entry, for example:

autod(pid:1622) log header: logid:44547 vfid:0 sever:6 cat:1 subcat:0 key:0 flags:0404 reqlen:359 timestamp:1777637024413047347
fields:
                id:10 name:(9)eventtime value:(19)1777637024413047347
                id:2 name:(5)logid value:(10)0100044547
                id:3 name:(4)type value:(5)event
                id:4 name:(7)subtype value:(6)system
                id:5 name:(5)level value:(11)information
                id:6 name:(2)vd value:(4)root
                id:38 name:(7)logdesc value:(27)Object attribute configured
                id:22 name:(4)user value:(5)admin
                id:57 name:(2)ui value:(20)https(192.168.1.101)
                id:12 name:(6)action value:(3)Add
                id:59 name:(6)cfgtid value:(8)96927855
                id:58 name:(4)uuid value:(36)cc0608f6-4555-51f1-0501-0fe328bf84a4
                id:62 name:(7)cfgpath value:(15)firewall.policy
                id:63 name:(6)cfgobj value:(1)6
                id:64 name:(7)cfgattr value:(150)name[DEBUG-LOG-POLICY]srcintf[port1]dstintf[SDWAN-OUTSIDE]action[accept]srcaddr[all]dstaddr[all]schedule[always]service[ALL]logtraffic[all]nat[enable]
                id:24 name:(3)msg value:(21)Add firewall.policy 6
New policy creation log

Cool, right?

FortiAnalyzer Event Handlers

The FortiOS Event Log triggers are nice, but they aren’t flexible. If there is no log for the scenario you need, if you need something more specific, or if you need some logic, they hit their limit. FortiAnalyzer takes care of this. Now, this is not strictly “onboard” because you do need another product, but I would be remiss not to mention it.

If you integrate FortiAnalyzer as a Fabric Connector, you can leverage the Event Handlers you create on FortiAnalyzer as a trigger on a FortiGate. Let’s take a practical example.

FortiAnalyzer comes with the Default-Brute-Force-Account-Login-Attack-FGT event handler, which does exactly what it says. It recognizes brute force administrator logins on a FortiGate. It uses a correlation sequence, where failed logins are checked, and if they aren’t followed by a successful login after 5 minutes, the event triggers.

If we want to use this event handler on our FortiGate, we clone it, because event handlers of the Built-in origin cannot be used for this, enable the Automation Stitch option, change the Threshold Duration to be the same as the NOT_FOLLOWED_BY value and then we go onto the FortiGate and create our trigger.

FortiAnalyzer brute force login event handler
FortiAnalyzer Event Handler trigger
config system automation-trigger
    edit "FAZ-BRUTE-FORCE"
        set event-type faz-event
        set faz-event-name "Custom-Brute-Force-Account-Login-Attack-FGT"
    next
end

Debugging this communication is done on FortiAnalyzer using the oftpd application, like such (192.168.1.201 is my FortiGate):

FAZVM64 # diagnose debug application oftpd 8 192.168.1.201
oftpd debug filter:     filter(string)==192.168.1.201
FAZVM64 # diagnose debug enable

The full debugs for such an event handler triggering, with the regular communication removed, look like this:

FortiAnalyzer oftpd debug output

[T2313:oftp_restapi_util.c:1110] [FGT70GSERIAL] OFTP_RESTAPI_GENERIC_REQ sched success, uuid=e0a31dd8-4647-11f1-8ce6-000c29563bff, u
rl=/api/v2/monitor/log/event/generate?vdom=root, data={ "log_type": "security-event", "fields": { "ackflag": "no", "alertid": "202605021
000010016", "logcount": "5", "alerttime": "1777740995", "devid": "FGT70GSERIAL", "devname": "70G-01", "eventtype": "", "groupby1": "
FGT70GSERIAL", "groupby2": "", "groupby3": "", "readflag": "no", "severity": "medium", "subject": "Brute force account login attack
on FGT device FGT70GSERIAL detected", "tag": "Credential Access,login,attack", "triggername": "Custom-Brute-Force-Account-Login-Atta
ck-FGT", "vdom": "root", "epid": "3", "euid": "3", "epip": "192.168.1.231", "srcip": "192.168.1.231", "dstip": "192.168.1.201", "epname"
: "192.168.1.231", "euname": "N\/A", "extrainfo": "", "ephostname": "", "epmac": "", "eposname": "", "eposversion": "", "fctuid": "", "m
itre_info": "{\"enterprise\": [\"T1110.001\"]}" } }.

[T2320:oftp_restapi.c:592] [FGT70GSERIAL] req :
POST /api/v2/monitor/log/event/generate?vdom=root HTTP/1.1
Host: 127.0.0.1
Connection: close
User-Agent: FortiAnalyzer/7.6 (FortiAnalyzer-VM64; FAZ-VMTMSERIAL; v7.6.6-build3654 260127 (GA.M))
Accept: */*
Cookie: __fake_cookie_authorized_device;
X-CSRFTOKEN: __fake_cookie_authorized_device
Content-Length: 813
Content-Type: application/x-www-form-urlencoded

{ "log_type": "security-event", "fields": { "ackflag": "no", "alertid": "202605021000010016", "logcount": "5", "alerttime": "1777740995"
, "devid": "FGT70GSERIAL", "devname": "70G-01", "eventtype": "", "groupby1": "FGT70GSERIAL", "groupby2": "", "groupby3": "", "re
adflag": "no", "severity": "medium", "subject": "Brute force account login attack on FGT device FGT70GSERIAL detected", "tag": "Cred
ential Access,login,attack", "triggername": "Custom-Brute-Force-Account-Login-Attack-FGT", "vdom": "root", "epid": "3", "euid": "3", "ep
ip": "192.168.1.231", "srcip": "192.168.1.231", "dstip": "192.168.1.201", "epname": "192.168.1.231", "euname": "N\/A", "extrainfo": "",
"ephostname": "", "epmac": "", "eposname": "", "eposversion": "", "fctuid": "", "mitre_info": "{\"enterprise\": [\"T1110.001\"]}" } }

[T2301:oftp_restapi_resp.c:1946] [FGT70GSERIAL] OFTP_TASK_T_GENERIC_RESP uuid=e0a31dd8-4647-11f1-8ce6-000c29563bff, url=/api/v2/moni
tor/log/event/generate?vdom=root, req_data={ "log_type": "security-event", "fields": { "ackflag": "no", "alertid": "202605021000010016",
 "logcount": "5", "alerttime": "1777740995", "devid": "FGT70GSERIAL", "devname": "70G-01", "eventtype": "", "groupby1": "FGT70GSERIAL",
 "groupby2": "", "groupby3": "", "readflag": "no", "severity": "medium", "subject": "Brute force account login attack on FGT devi
ce FGT70GSERIAL detected", "tag": "Credential Access,login,attack", "triggername": "Custom-Brute-Force-Account-Login-Attack-FGT", "v
dom": "root", "epid": "3", "euid": "3", "epip": "192.168.1.231", "srcip": "192.168.1.231", "dstip": "192.168.1.201", "epname": "192.168.
1.231", "euname": "N\/A", "extrainfo": "", "ephostname": "", "epmac": "", "eposname": "", "eposversion": "", "fctuid": "", "mitre_info":
 "{\"enterprise\": [\"T1110.001\"]}" } }

First, we get the requests for a REST API action, which includes all information from the log that generated the event.

Then we get the actual HTTP POST request towards the FortiGate, which includes the log information in JSON.

Lastly, we get the response to the task.

On the FortiGate, this communication can be seen in the httpsd application, because FortiAnalyzer sends a POST request after all.

Enable the debugs and check the output.

diagnose debug application httpsd -1
diagnose debug enable

[httpsd 4590 - 1777740996     info] fweb_debug_init[614] -- New POST request for "/api/v2/monitor/log/event/generate" from "/tmp/httpsd-faz.sock:0"
[httpsd 4590 - 1777740996     info] fweb_debug_init[617] -- User-Agent: "FortiAnalyzer/7.6 (FortiAnalyzer-VM64; FAZ-VMTMSERIAL; v7.6.6-build3654 260127 (GA.M))"
[httpsd 4590 - 1777740996     info] fweb_debug_init[622] -- Handler "api_monitor_v2-handler" assigned to request
[httpsd 4590 - 1777740996     info] api_access_check_for_http_authd_session[51] -- Session key request authorized for daemon_admin.
[httpsd 4590 - 1777740996     info] api_store_parameter[298] -- add API parameter 'vdom' (type=string)
[httpsd 4590 - 1777740996     info] api_store_parameter[298] -- add API parameter 'vdom' (type=string)
[httpsd 4590 - 1777740996     info] api_store_parameter[298] -- add API parameter 'log_type' (type=string)
[httpsd 4590 - 1777740996     info] api_store_parameter[298] -- add API parameter 'fields' (type=object)
[httpsd 4590 - 1777740996     info] api_endpoint_execute_handler[922] -- new API request (action='generate',path='log',name='event',vdom='root',user='daemon_admin')
[httpsd 4590 - 1777740996     info] generate_event[1026] -- Created an internal event: ackflag="no" alertid="202605021000010016" logcount="5" alerttime="1777740995" devid="FGT70GSERIAL" devname="70G-01" eventtype="" groupby1="FGT70GSERIAL" groupby2="" groupby3="" readflag="no" severity="medium" subject="Brute force account login attack on FGT device FGT70GSERIAL detected" tag="Credential Access,login,attack" triggername="Custom-Brute-Force-Account-Login-Attack-FGT" vdom="root" epid="3" euid="3" epip="192.168.1.231" srcip="192.168.1.231" dstip="192.168.1.201" epname="192.168.1.231" euname="N\/A" extrainfo="" ephostname="" epmac="" eposname="" eposversion="" fctuid="" mitre_info="{\"enterprise\": [\"T1110.001\"]}"
[httpsd 4590 - 1777740996     info] api_endpoint_execute_handler[937] -- completed API request (mem_start=55268, mem_end=55332, mem_change=64)
[httpsd 4590 - 1777740996     info] fweb_debug_final[455] -- Completed POST request for "/api/v2/monitor/log/event/generate" (HTTP 200)

There isn’t a lot going on here that we haven’t already seen, but we know it’s there.

A FortiGate dynamically pulls the available event handlers every time you try to select one in the trigger, by the way. This is done using an OFTP JSONRPC request. This also shows up on FortiAnalyzer in the oftpd debugs:

FortiGate requesting FortiAnalyzer Event Handlers

[T2321:oftps.c:2148 :192.168.1.201] SSL clienthello incoming on sockfd[26]
[T2321:oftps.c:1436 :192.168.1.201] dft-idx=0 inited=1.
[T2321:oftps.c:1927 :192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] SSL_new() success.
[T2321:oftps.c:1804 :192.168.1.201] ssl verify peer cert
[T2321:oftps.c:1826 :192.168.1.201] Peer is using a fortinet certificate. ON=Fortinet
[T2321:oftps.c:1839 :192.168.1.201] Peer cert info, CommonName(CN=FGT70GSERIAL).
[T2321:oftps.c:2160 :192.168.1.201] SSL_accept one client SUCCESS [ protocol : (772) TLS 1.3  ]
[T2321:oftps.c:2199 :192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] SSL_accepted
[T2322:oftps.c:2257 :192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] received [512] bytes:
[T2322:oftps.c:2257 :192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] received [83] bytes:
[T2322:main.c:5147 :192.168.1.201] handle LOGIN_REQUEST_LEGACY
[T2323:login.c:3539 :192.168.1.201] login-ver: 0.0
[T2323:login.c:3443 :192.168.1.201] host = 'FGT-70G01'
[T2323:login.c:3489 :192.168.1.201] Version: FortiGate-70G v8.0.0,build0167,260420 (GA.F)
Virus-DB: 1.00001(2026-04-02 13:48)
IPS-DB: 36.00208(2026-04-23 01:22)
APP-DB: 36.00206(2026-04-21 03:52)
FMWP-DB: 26.00040(2026-04-10 09:35)
Industrial-DB: 6.00741(2015-12-01 02:30)
Serial-Number: FGT70GSERIAL
Virtual domain configuration: disable
Current HA mode: a-p
Current HA group: labcluster
[T2323:login.c:348 :192.168.1.201] os_type(0) os_ver(8) mr(0) patch(0) build(167) beta(-1)
[T2323:login.c:398 :192.168.1.201] ha_group_name:labcluster, ha_mode:1.
[T2323:login.c:3448 :192.168.1.201] vdom = 1
[T2323:login.c:3468 :192.168.1.201] tz-offset = 7200, tz-name = Europe/Vienna
[T2323:oftps.c:2323 FGT70GSERIAL:192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] sent [103] bytes:
[T2323:login.c:3943 FGT70GSERIAL:192.168.1.201] login succeed
[T2322:oftps.c:2257 FGT70GSERIAL:192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] received [236] bytes:
[T2322:main.c:5147 FGT70GSERIAL:192.168.1.201] handle RESTAPI REQUEST
[T2329:oftp_svc_fwd.c:812 FGT70GSERIAL:192.168.1.201] handle_oftp_jsonrpc_request entry
[T2329:oftp_svc_fwd.c:726 FGT70GSERIAL:192.168.1.201] cannot find display-timezone for FGT70GSERIAL
[T2329:oftp_svc_fwd.c:898 FGT70GSERIAL:192.168.1.201] sent req={ "id": 1777742118, "params": [ { "url": "eventmgmt\/dev\/FGT70GSERIAL
\/vdom\/root\/config\/trigger", "apiver": 3, "filter": [ "handlertype", "==", "handler-type-remote" ] } ], "jsonrpc": "2.0", "metho
d": "get" }
 response '{ "jsonrpc": "2.0", "id": 1777742118, "result": { "status": { "code": 0, "message": "OK" }, "data": [ { "auto-raise-incident"
: 0, "automation-stitch": 1, "creation-time": 0, "description": "Default event handler to detect botnet communication and report to Fort
iGate", "enable": 1, "enable-time": 0, "handler-id": "30000", "mitre-domain": "enterprise", "mitre-info": "{\"enterprise\": [\"T1584.005
\", \"T1071\"]}", "mitre-tech-id": "T1584.005,T1071", "name": "Default-Botnet-Communication-Detection", "protected": 1, "rule": [ { "agg
regate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${dstip}:${dstpor
t}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "filter-expr": "logid==0202009248", "filter-relation": 0, "groupby1": "endp
oint", "groupby2": "virus", "groupby3": null, "indicator": null, "logtype": "virus", "name": "Traffic to Botnet CnC blocked in virus log
", "risk-severity": 4, "rule-id": "1", "severity": 1, "subject": "Traffic to Botnet C&C $groupby2 blocked", "tags": "Default,Botnet,IP,C
&C", "thres-duration": 1440, "utmevent": null }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "eventstatus": "open",
"eventtype": null, "extrainfo": "C&C: ${dstip}:${dstport}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "filter-expr": "logi
d==0202009249", "filter-relation": 0, "groupby1": "endpoint", "groupby2": "virus", "groupby3": null, "indicator": null, "logtype": "viru
s", "name": "Traffic to Botnet CnC detected in virus log", "risk-severity": 4, "rule-id": "2", "severity": 0, "subject": "Traffic to Bot
net C&C $groupby2 detected", "tags": "Default,Botnet,IP,C&C", "thres-duration": 1440, "utmevent": null }, { "aggregate-expr": "COUNT(*)
>= 1", "devtype": 0, "enable": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "Traffic path: ${devname} (Policy ID:${policyid
})\\${dstintf}\\${dstip}", "extrainfo-type": 1, "filter": null, "filter-expr": "logid==1501054601 OR logid==1501054600", "filter-relatio
n": 0, "groupby1": "endpoint", "groupby2": "qname", "groupby3": null, "indicator": null, "logtype": "dns", "name": "DNS traffic to Botne
t CnC blocked", "risk-severity": 4, "rule-id": "3", "severity": 1, "subject": "DNS traffic to Botnet C&C $groupby2 blocked", "tags": "De
fault,Botnet,Domain,C&C", "thres-duration": 1440, "utmevent": null }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "e
ventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${dstip}:${dstport}, Traffic path: ${devname} (Policy ID:${policyid})\\${dsti
ntf}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "filter-expr": "attack ~ Botnet and direction=incoming and (action=='dete
cted' or action=='pass session')", "filter-relation": 0, "groupby1": "endpoint", "groupby2": "attack", "groupby3": null, "indicator": nu
ll, "logtype": "ips", "name": "Traffic to Botnet CnC detected in ips log 1", "risk-severity": 4, "rule-id": "4", "severity": 0, "subject
": "Traffic to Botnet C&C $groupby2 detected", "tags": "Default,Botnet,Signature,C&C,Incoming", "thres-duration": 1440, "utmevent": null
 }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${dstip
}:${dstport}, Traffic path: ${devname} (Policy ID:${policyid})\\${dstintf}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "fi
lter-expr": "attack ~ Botnet and direction=incoming and action!='detected' and action!='pass session'", "filter-relation": 0, "groupby1"
: "endpoint", "groupby2": "attack", "groupby3": null, "indicator": null, "logtype": "ips", "name": "Traffic to Botnet CnC blocked in ips
 log 1", "risk-severity": 4, "rule-id": "5", "severity": 1, "subject": "Traffic to Botnet C&C $groupby2 blocked", "tags": "Default,Botne
t,Signature,C&C,Incoming", "thres-duration": 1440, "utmevent": null }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "
eventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${srcip}:${srcport}, Traffic path: ${devname} (Policy ID:${policyid})\\${src
intf}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "filter-expr": "attack ~ Botnet and direction=outgoing and (action=='det
ected' or action=='pass session')", "filter-relation": 0, "groupby1": "dstendpoint", "groupby2": "attack", "groupby3": null, "indicator"
: null, "logtype": "ips", "name": "Traffic from Botnet CnC detected in ips log", "risk-severity": 4, "rule-id": "6", "severity": 0, "sub
ject": "Traffic from Botnet C&C $groupby2 detected", "tags": "Default,Botnet,Signature,C&C,Outgoing", "thres-duration": 1440, "utmevent"
: null }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "C&C: $
{srcip}:${srcport}, Traffic path: ${devname} (Policy ID:${policyid})\\${srcintf}, Reference: ${ref}", "extrainfo-type": 1, "filter": nul
l, "filter-expr": "attack ~ Botnet and direction=outgoing and action!='detected' and action!='pass session'", "filter-relation": 0, "gro
upby1": "dstendpoint", "groupby2": "attack", "groupby3": null, "indicator": null, "logtype": "ips", "name": "Traffic from Botnet CnC blo
cked in ips log", "risk-severity": 4, "rule-id": "7", "severity": 1, "subject": "Traffic from Botnet C&C $groupby2 blocked", "tags": "De
fault,Botnet,Signature,C&C,Outgoing", "thres-duration": 1440, "utmevent": null }, { "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "en
able": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${dstip}:${dstport}, Reference: ${ref}", "extrainfo-type": 1, "fi
lter": null, "filter-expr": "logid==0422016400", "filter-relation": 0, "groupby1": "endpoint", "groupby2": "attack", "groupby3": null, "
indicator": null, "logtype": "ips", "name": "Traffic to Botnet CnC blocked in ips log 2", "risk-severity": 4, "rule-id": "8", "severity"
: 1, "subject": "Traffic to Botnet C&C $groupby2 blocked", "tags": "Default,Botnet,IP,C&C", "thres-duration": 1440, "utmevent": null },
{ "aggregate-expr": "COUNT(*) >= 1", "devtype": 0, "enable": 1, "eventstatus": "open", "eventtype": null, "extrainfo": "C&C: ${dstip}:${
dstport}, Reference: ${ref}", "extrainfo-type": 1, "filter": null, "filter-expr": "logid==0422016401", "filter-relation": 0, "groupby1":
 "endpoint", "groupby2": "attack", "groupby3": null, "indicator": null, "logtype": "ips", "name": "Traffic to Botnet CnC detected in ips
 log 2", "risk-severity": 4, "rule-id": "9", "severity": 0, "subject": "Traffic to Botnet C&C $groupby2 detected", "tags": "Default,Botn
et,IP,C&C", "thres-duration": 30, "utmevent": null } ], "template-url": "\/fazcfg-template\/basic-handler\/fgt", "update-time": 0, "vers
ion": 2, "handlertype": 1, "correlationtype": "basic-handler" }, { "auto-raise-incident": 0, "automation-stitch": 1, "content-pack-id":
"", "content-pack-uuid": "", "creation-time": 1777736984, "data-selector": "", "description": "", "enable": 1, "enable-time": 0, "handle
r-id": "679_809_2dd_c09", "mitre-info": "", "name": "SINGLE-LOGIN-FAILED", "notification": "", "protected": 0, "rule": [ { "aggregate-ex
pr": "COUNT(*)>=1", "devtype": 0, "enable": 1, "eventstatus": "auto", "eventtype": null, "extrainfo": null, "extrainfo-type": 0, "filter
": [ { "id": 1, "key": "logid", "oper": 0, "value": "0100032002" } ], "filter-expr": "", "filter-relation": 1, "groupby1": "srcip", "gro
upby2": "", "groupby3": "", "indicator": null, "logtype": "event", "name": "SINGLE-LOGIN-FAILED", "risk-severity": 4, "rule-id": "401_4b
9_c06_724", "severity": 2, "tags": "", "thres-duration": 1, "utmevent": "system" } ], "template-url": "", "update-time": 1777742099, "uu
id": "", "version": 2, "handlertype": 1, "correlationtype": "basic-handler" }, { "auto-raise-incident": 0, "automation-stitch": 1, "cont
ent-pack-id": "", "content-pack-uuid": "", "creation-time": 1777735167, "data-selector": "", "description": "This handler is to detect i
f an account login failed many times not followed by a login success for FortiGate.", "enable": 1, "enable-time": 0, "eventstatus": "aut
o", "extrainfo-type": 0, "handler-id": "360_fba_9a7_67b", "indicator": null, "mitre-domain": "enterprise", "mitre-info": "{\"enterprise\
": [\"T1110.001\"]}", "mitre-tech-id": "T1110.001", "name": "Custom-Brute-Force-Account-Login-Attack-FGT", "notification": "", "protecte
d": 0, "risk-severity": 4, "rule": [ { "aggregate-expr": "COUNT(*) >= 5", "devtype": 0, "filter": [ { "id": 1, "key": "logid", "oper": 0
, "value": "0100032002" } ], "filter-expr": "", "filter-relation": 1, "groupby1": "devid", "groupby2": "", "groupby3": "", "logtype": "e
vent", "name": "Login Failed 5 times", "rule-id": "rule_1", "utmevent": "system" }, { "aggregate-expr": "COUNT(*)>=1", "devtype": 0, "fi
lter": [ { "id": 1, "key": "logid", "oper": 0, "value": "0100032001" } ], "filter-expr": "", "filter-relation": 1, "groupby1": "devid",
"groupby2": "", "groupby3": "", "logtype": "event", "name": "Login-Success", "rule-id": "rule_2", "utmevent": "system" } ], "rule-relati
on": "rule_1 NOT_FOLLOWED_BY[1m] rule_2", "rule-relation-constraint": "rule_1.devid=rule_2.devid", "severity": 2, "subject": "Brute forc
e account login attack on FGT device ${Login Failed 5 times.groupby1} detected", "tags": "Credential Access,login,attack", "template-url
": "\/fazcfg-template\/correlation-handler\/fgt", "thres-duration": 1, "update-time": 1777736344, "uuid": "", "version": 2, "handlertype
": 1, "correlationtype": "correlation-handler" } ] } }', len=9427.

[T2322:oftps.c:2235 FGT70GSERIAL:192.168.1.201] The SSL/TLS connection has been closed
[T2322:main.c:937 FGT70GSERIAL:192.168.1.201] Client connection closed. Reason 0(OK)
[T2322:oftps.c:2342 FGT70GSERIAL:192.168.1.201] SSL pid[1688] ssl[0x7f3e50041820] shuting down sockfd[26] ip[192.168.1.201] connecte
d[1]
[T2322:oftps.c:2361 FGT70GSERIAL:192.168.1.201] SSL_shutdown SUCCESS
[T2322:oftps.c:2369 FGT70GSERIAL:192.168.1.201] SSL socket[26] pid[1688] ssl[0x7f3e50041820] destroy_SSL_context

With FortiAnalyzer Event Handlers, the sky really is the limit. You can do practically everything your heart desires if you speak FortiAnalyzer.

Auto-scripts

The next automation option is auto-scripts, which are CLI-only in config system auto-script, and not that well-known, but they are powerful. Just look at the options:

70G-02 (LAB-SCRIPT) # set
interval       Repeat interval in seconds.
repeat         Number of times to repeat this script (0 = infinite).
start          Script starting mode.
script         List of FortiOS CLI commands to repeat.
output-size    Number of megabytes to limit script output to (10 - 1024, default = 10).
timeout        Maximum running time for this script in seconds (0 = no timeout).

The script option takes whatever FortiOS CLI commands you want, but there is a limit of 1023 characters, so you won’t be able to import certificates.

You can set the interval, how often the script runs and if it should start automatically or be executed manually.

If you ever need to escape some characters in the script, like double quotes (), you can do that using backslashes \ as can be seen here:

config system auto-script
    edit "LAB-SCRIPT"
        set start auto
        set script "config firewall address
edit \"TEST\"
set subnet 192.0.2.0/24
set comment \"Escape double quotes\"
next
end
"
    next
end

Auto-scripts have the nice advantage of being able to be created by outside systems but run with local privileges on a FortiGate, which becomes important if you try to automate things that outside systems aren’t allowed to do. A good example is FortiManager. FortiManager cannot change some options on a FortiGate, like certain options related to HA or the central management options itself, meaning FortiManager cannot change the FortiManager options on a FortiGate.

To get around these limitations, you can create an auto-script on FortiManager and change whatever you need in that.

For debugging around auto-scripts, you use the execute auto-script command, and lots of information is available in this technical tip: Technical Tip: Automated scripts (auto-script). Execution, testing and verification explained with examples 

Importantly, auto-scripts can be created and controlled in automation actions, which opens up a lot of possibilities.

One use case for auto-scripts I want to highlight is regarding the execute auto-script result option. Since this command shows the result of an auto-script and auto-scripts can run diagnose and get commands, you can use this as a crude information storage.

Imagine that you run into the dreaded conserve mode, and you want to gather some information during that time, like which process took the memory. FortiGates come with a Conserve Mode automation trigger, and if you create the following automation action, you can later check what the status of your processes was using diagnose sys top 1 99 1 when conserve mode was triggered.

config system auto-script
    edit "LAB-SCRIPT-TOP"
        set script "diagnose sys top 1 99 1"
    next
end

The reason I am performing an execute auto-script start LAB-SCRIPT-TOP after creating the auto-script is because setting set start auto is, for some reason, not possible with an automation action.

========== #1, 2026-05-01 12:39:06 ==========
70G-02  config system auto-script
70G-02 (auto-script)      edit "LAB-SCRIPT-TOP"
70G-02 (LAB-SCRIPT-TOP)          set start auto
Script start-mode could not be changed from auto-script.
node_check_object fail! for start auto

value parse error before 'auto'
Command fail. Return code -37
70G-02 (LAB-SCRIPT-TOP)          set script "diagnose sys top 1 99 1"
70G-02 (LAB-SCRIPT-TOP)      next
70G-02 (auto-script)  end
70G-02  execute auto-script start LAB-SCRIPT-TOP

======= end of #1, 2026-05-01 12:39:06 ======

Once your conserve mode problem has been solved, or it has solved itself, run an execute auto-script result LAB-SCRIPT-TOP and check your output.

Auto-script output

70G-02 # execute auto-script result LAB-SCRIPT-TOP
Script LAB-SCRIPT-TOP output:

========== #1, 2026-05-02 18:13:44 ==========
70G-02  diagnose sys top 1 99 1
06:13:46 PM up 0 days, 2 hours and 21 minutes
0U, 0N, 0S, 100I, 0WA, 0HI, 0SI, 0ST; 3708T, 2315F
         miglogd     1714      S       0.5     1.3    2
            node     1572      S       0.0     2.6    3
       ipsengine     1893      S       0.0     2.1    3
       ipshelper     1604      S       0.0     2.1    2
       ipsengine     1895      S       0.0     2.1    2
       ipsengine     1894      S       0.0     2.1    0
             wad     1665      S       0.0     1.6    3
             wad     1663      S       0.0     1.5    1
         cmdbsvr     1484      S       0.0     1.5    0
          cw_acd     1614      S       0.0     1.3    2
       forticron     1558      S       0.0     1.3    0
       extenderd     1631      S       0.0     1.2    1
         miglogd     1570      S       0.0     1.2    0
            csfd     1634      S       0.0     1.2    3
             wad     1664      S       0.0     1.2    0
          newcli     5113      S       0.0     1.2    3
          newcli     2866      S       0.0     1.2    2
          httpsd     5707      S       0.0     1.1    1
             wad     1656      S       0.0     1.1    0
           fgfmd     1613      S       0.0     1.1    2
 initXXXXXXXXXXX        1      S       0.0     1.1    3
             wad     1580      S       0.0     1.0    1
      http_authd     1551      S       0.0     1.0    2
             wad     1659      S       0.0     1.0    0
             wad     1657      S       0.0     0.9    2
            iked     1718      S       0.0     0.9    3
             wad     1661      S       0.0     0.9    1
             wad     1662      S       0.0     0.8    0
         fgtlogd     1593      S       0.0     0.8    3
             wad     1655      S       0.0     0.8    0
             wad     1660      S       0.0     0.8    1
             wad     1658      S       0.0     0.8    2
          authd3     1726      S       0.0     0.8    1
          csfd 0     1964      S       0.0     0.8    3
          authd2     1725      S       0.0     0.8    3
 eap_proxy_worke     1728      S       0.0     0.8    3
            iked     1716      S       0.0     0.8    3
      telemetryd     1633      S       0.0     0.8    1
            iked     1717      S       0.0     0.7    0
          fcnacd     1567      S       0.0     0.7    0
        dnsproxy     1617      S       0.0     0.7    3
       forticldd     1559      S       0.0     0.7    1
          cu_acd     1623      S       0.0     0.7    0
          httpsd     1550      S       0.0     0.7    2
             imi     1546      S       0.0     0.7    3
            bgpd     1543      S <     0.0     0.7    2
             nsm     1542      S       0.0     0.7    3
         updated     1589      S       0.0     0.7    0
           authd     1560      S       0.0     0.7    2
          fltund     1626      S       0.0     0.6    0
           snmpd     1596      S       0.0     0.6    3
      cid-config     1610      S N     0.0     0.6    2
          fnbamd     1557      S       0.0     0.6    3
         fortimq     1637      S       0.0     0.6    3
            iotd     1598      S       0.0     0.6    1
           voipd     1579      S       0.0     0.6    0
         foauthd     1561      S       0.0     0.6    1
            sshd     1600      S       0.0     0.6    3
         wpad_ac     1618      S       0.0     0.6    2
            ntpd     1599      S <     0.0     0.6    2
          flcfgd     1624      S       0.0     0.6    2
      http_authd     1554      S       0.0     0.5    2
           radvd     1603      S       0.0     0.5    2
            iked     1715      S       0.0     0.5    0
       forticron     5778      S       0.0     0.5    2
          lnkmtd     1601      S       0.0     0.5    3
           autod     1635      S       0.0     0.5    2
  zebos_launcher     1527      S       0.0     0.5    3
       cloudapid     1636      S       0.0     0.5    3
            sshd     2865      S       0.0     0.4    3
         syslogd     1591      S       0.0     0.4    3
       locallogd     1595      S       0.0     0.4    2
       forticron     5779      R       0.0     0.4    3
       forticron     5777      S       0.0     0.4    1
        cid-scan     1620      S       0.0     0.4    0
      fortilinkd     1622      S       0.0     0.4    2
             vwl     1602      S       0.0     0.4    2
  ikecryptd_dhw0     1583      S       0.0     0.4    3
  ikecryptd_dhw2     1585      S       0.0     0.4    3
  ikecryptd_dhw1     1584      S       0.0     0.4    3
      ipsmonitor     1555      S       0.0     0.3    1
          flpold     1625      S       0.0     0.3    2
   cw_acd_helper     1733      S       0.0     0.3    2
        dnsproxy     1606      S       0.0     0.3    1
    ipmc_sensord     1548      S       0.0     0.3    1
          dhcp6c     1630      S       0.0     0.3    1
       ikecryptd     1581      S       0.0     0.3    1
           getty     1552      S <     0.0     0.3    0
       alertmail     1605      S       0.0     0.3    3
       eap_proxy     1611      S       0.0     0.3    3
  merged_daemons     1556      S       0.0     0.3    2
       clearpass     1563      S       0.0     0.3    3
         uploadd     1547      S       0.0     0.3    2
       fsso_ldap     1569      S       0.0     0.3    3
             fas     1566      S       0.0     0.3    1
        httpclid     1565      S       0.0     0.3    2
             fsd     1629      S       0.0     0.3    1
        kmiglogd     1549      S       0.0     0.3    3
           getty     1553      S       0.0     0.2    3

======= end of #1, 2026-05-02 18:13:47 ======

You can of course also send a mail with all this information, as is detailed in this technical tip: Technical Tip: Automation stitch for conserve mode

Batch mode

The last topic for automation I want to write about is batch mode. This isn’t a way to automate something, but you can use it in automation, so it gets a spot.

Batch mode is something you start, write down your CLI commands, and once you end the mode, the commands get executed. This allows you to make lots of changes in a single “transaction”, not to be confused with the actual config transactions, where the individual configurations might cut off your access at some point.

Imagine you are managing a FortiGate via a VPN, and you need to drastically change the VPN configuration, which would normally disconnect you somewhere along the configuration. With batch mode, you can do this all without disruption.

One very nice thing about batch mode is that you don’t have to worry about formatting, character limits or anything of the sort. Since you’re working like normal on the CLI, you can do just about every configuration. In the auto-script section, I mentioned how the character limit is 1023, so importing certificates won’t work. This is no problem with batch mode.

Batch mode certificate import

70G-02 # execute batch start

Enter batch mode...

70G-02 # config vpn certificate remote
70G-02 #     edit "TEST-CERT"
70G-02 #         set remote "-----BEGIN CERTIFICATE-----
> MIIHSzCCBTOgAwIBAgITMgAAADd86bYatj17eQACAAAANzANBgkqhkiG9w0BAQ0F
> ADBVMRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbGFiZG9t
> YWluMRIwEAYKCZImiZPyLGQBGRYCYWQxDzANBgNVBAMTBldJTi1DQTAeFw0yNTA4
> MDkxOTU0MDdaFw0yNzA4MDkxOTU0MDdaMCMxITAfBgNVBAMMGGxhYnVzZXJAYWQu
> bGFiZG9tYWluLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrJ
> EQqx06rKJ9OULN2155KGM31WjJvUDM+aPEiHgmAUE9pPmob/kLzQ97ylnj3AzY9e
> FF+coqgphfpxdLhlYEOUSAzQsK2B/Jn7MS8dhwfaYGtVvtfEjOZa833pXWmz1bQE
> Onfrnp1n3QZL6WYkSLHUbBESQY6tmfzOohcu9rfNfMVFEsPz/MPTHOFkqn5CfPMT
> p9gV3X3/UhoWrXBphUfKjHf5+NY2kBVySRIsaFxv/yTI/ypbc0Zk3a7Ux4dJRRcd
> jIGfHxoN6d9EphlMd/fEY27yJ0Ul1HZJh7skRsP9y0egMQtbEbsOmCxeyXysNuLe
> bTMYVxJ5w9DFB+bbmYUCAwEAAaOCA0QwggNAMCcGA1UdJQQgMB4GCCsGAQUFBwMC
> BggrBgEFBQcDBAYIKwYBBQUHAwEwMwYJKwYBBAGCNxUKBCYwJDAKBggrBgEFBQcD
> AjAKBggrBgEFBQcDBDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUTQ/QuOQjE0fjDKCy
> ajTzs7KtenYwDgYDVR0PAQH/BAQDAgWgMCMGA1UdEQQcMBqCGGxhYnVzZXJAYWQu
> bGFiZG9tYWluLmNvbTAfBgNVHSMEGDAWgBTzzdZs1ME1aNfuqgd6qFpzuEhs1TCC
> AQcGA1UdHwSB/zCB/DCB+aCB9qCB84aBt2xkYXA6Ly8vQ049V0lOLUNBKDIpLENO
> PVdJTi1BRCxDTj1DRFAsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2Vy
> dmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1hZCxEQz1sYWJkb21haW4sREM9Y29t
> P2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxE
> aXN0cmlidXRpb25Qb2ludIY3aHR0cDovL1dJTi1BRC5hZC5sYWJkb21haW4uY29t
> L0NlcnRFbnJvbGwvV0lOLUNBKDIpLmNybDCCAR8GCCsGAQUFBwEBBIIBETCCAQ0w
> ga0GCCsGAQUFBzAChoGgbGRhcDovLy9DTj1XSU4tQ0EsQ049QUlBLENOPVB1Ymxp
> YyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24s
> REM9YWQsREM9bGFiZG9tYWluLERDPWNvbT9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
> ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTBbBggrBgEFBQcwAoZPaHR0
> cDovL1dJTi1BRC5hZC5sYWJkb21haW4uY29tL0NlcnRFbnJvbGwvV0lOLUFELmFk
> LmxhYmRvbWFpbi5jb21fV0lOLUNBKDIpLmNydDA9BgkrBgEEAYI3FQcEMDAuBiYr
> BgEEAYI3FQiFrZZuhK/KeoeNhyWH9NB3gfixAQWDyMVZhdXVWQIBZAIBCTANBgkq
> hkiG9w0BAQ0FAAOCAgEAvQt0Mm/hcFZ4GX5KZj7mlj3Aqm4HyapaRSAHVp7eN+I2
> kZiWtzn8WP6fEcniHNXn4hs/5c/MEBzpIbQ4z0UvymH2AHijiZAEq/zOqOlGCOjy
> 8/lhRFY0cjGxg6GfpN7Pm9p0PEEOGKvapn92hxE/rAL4S5m4jxu9Nukfcw8mcb7g
> kaGr/3HAlRcsttU0VyJichmfBOYutpj3mhJ38Sc7eCxbTqj+U71NSjdrNx2WN2jh
> PAhS31tP1YTyNjW7Tgrn9unplxi2BqjDc0j72/247/E6DBdroV5HiQ2F3e2u1eXk
> Ss3IKYKtU1UixDAYsN28Nir4u87P4yo1xcpGTHr5Qne1E2tHNH1jb951MZPMuhh6
> bSIm+U0b0EzNgfQp6wI+a/9ZtliZfpxrvzyp7mPwhri9ZbNtDkWPTOjjWrw4yEVZ
> /e+UFt9ghYB0xSvllvd8fquzG5bgOv46EfwSw0J8a4PGwy4Wy7kiyjrsphimNY6/
> ql8WEeg48QTukluhULsKosO1JNx6RLe85gCcQ7Jb9Q77q2DJiTHjqu7OACns4DkV
> BKPtYE0+40/TOCc4GgsRHk/iLy0/vnh7KH5ITblokygKMFAjhBlmSmdhajK/7Vyl
> cWMKYzZMGk6FvNU8XXx/fjVc8dL2lj+P3E6UOvtYtX4Yb3TuVUp+NtJSJZ6eLiA=
> -----END CERTIFICATE-----"
70G-02 #     next
70G-02 # end
70G-02 # execute batch end
Exit and run batch commands...

70G-02 # execute batch lastlog
0: config vpn certificate remote
0:     edit "TEST-CERT"
0:         set remote "-----BEGIN CERTIFICATE-----
0: MIIHSzCCBTOgAwIBAgITMgAAADd86bYatj17eQACAAAANzANBgkqhkiG9w0BAQ0F
0: ADBVMRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbGFiZG9t
0: YWluMRIwEAYKCZImiZPyLGQBGRYCYWQxDzANBgNVBAMTBldJTi1DQTAeFw0yNTA4
0: MDkxOTU0MDdaFw0yNzA4MDkxOTU0MDdaMCMxITAfBgNVBAMMGGxhYnVzZXJAYWQu
0: bGFiZG9tYWluLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrJ
0: EQqx06rKJ9OULN2155KGM31WjJvUDM+aPEiHgmAUE9pPmob/kLzQ97ylnj3AzY9e
0: FF+coqgphfpxdLhlYEOUSAzQsK2B/Jn7MS8dhwfaYGtVvtfEjOZa833pXWmz1bQE
0: Onfrnp1n3QZL6WYkSLHUbBESQY6tmfzOohcu9rfNfMVFEsPz/MPTHOFkqn5CfPMT
0: p9gV3X3/UhoWrXBphUfKjHf5+NY2kBVySRIsaFxv/yTI/ypbc0Zk3a7Ux4dJRRcd
0: jIGfHxoN6d9EphlMd/fEY27yJ0Ul1HZJh7skRsP9y0egMQtbEbsOmCxeyXysNuLe
0: bTMYVxJ5w9DFB+bbmYUCAwEAAaOCA0QwggNAMCcGA1UdJQQgMB4GCCsGAQUFBwMC
0: BggrBgEFBQcDBAYIKwYBBQUHAwEwMwYJKwYBBAGCNxUKBCYwJDAKBggrBgEFBQcD
0: AjAKBggrBgEFBQcDBDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUTQ/QuOQjE0fjDKCy
0: ajTzs7KtenYwDgYDVR0PAQH/BAQDAgWgMCMGA1UdEQQcMBqCGGxhYnVzZXJAYWQu
0: bGFiZG9tYWluLmNvbTAfBgNVHSMEGDAWgBTzzdZs1ME1aNfuqgd6qFpzuEhs1TCC
0: AQcGA1UdHwSB/zCB/DCB+aCB9qCB84aBt2xkYXA6Ly8vQ049V0lOLUNBKDIpLENO
0: PVdJTi1BRCxDTj1DRFAsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2Vy
0: dmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1hZCxEQz1sYWJkb21haW4sREM9Y29t
0: P2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxE
0: aXN0cmlidXRpb25Qb2ludIY3aHR0cDovL1dJTi1BRC5hZC5sYWJkb21haW4uY29t
0: L0NlcnRFbnJvbGwvV0lOLUNBKDIpLmNybDCCAR8GCCsGAQUFBwEBBIIBETCCAQ0w
0: ga0GCCsGAQUFBzAChoGgbGRhcDovLy9DTj1XSU4tQ0EsQ049QUlBLENOPVB1Ymxp
0: YyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24s
0: REM9YWQsREM9bGFiZG9tYWluLERDPWNvbT9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
0: ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTBbBggrBgEFBQcwAoZPaHR0
0: cDovL1dJTi1BRC5hZC5sYWJkb21haW4uY29tL0NlcnRFbnJvbGwvV0lOLUFELmFk
0: LmxhYmRvbWFpbi5jb21fV0lOLUNBKDIpLmNydDA9BgkrBgEEAYI3FQcEMDAuBiYr
0: BgEEAYI3FQiFrZZuhK/KeoeNhyWH9NB3gfixAQWDyMVZhdXVWQIBZAIBCTANBgkq
0: hkiG9w0BAQ0FAAOCAgEAvQt0Mm/hcFZ4GX5KZj7mlj3Aqm4HyapaRSAHVp7eN+I2
0: kZiWtzn8WP6fEcniHNXn4hs/5c/MEBzpIbQ4z0UvymH2AHijiZAEq/zOqOlGCOjy
0: 8/lhRFY0cjGxg6GfpN7Pm9p0PEEOGKvapn92hxE/rAL4S5m4jxu9Nukfcw8mcb7g
0: kaGr/3HAlRcsttU0VyJichmfBOYutpj3mhJ38Sc7eCxbTqj+U71NSjdrNx2WN2jh
0: PAhS31tP1YTyNjW7Tgrn9unplxi2BqjDc0j72/247/E6DBdroV5HiQ2F3e2u1eXk
0: Ss3IKYKtU1UixDAYsN28Nir4u87P4yo1xcpGTHr5Qne1E2tHNH1jb951MZPMuhh6
0: bSIm+U0b0EzNgfQp6wI+a/9ZtliZfpxrvzyp7mPwhri9ZbNtDkWPTOjjWrw4yEVZ
0: /e+UFt9ghYB0xSvllvd8fquzG5bgOv46EfwSw0J8a4PGwy4Wy7kiyjrsphimNY6/
0: ql8WEeg48QTukluhULsKosO1JNx6RLe85gCcQ7Jb9Q77q2DJiTHjqu7OACns4DkV
0: BKPtYE0+40/TOCc4GgsRHk/iLy0/vnh7KH5ITblokygKMFAjhBlmSmdhajK/7Vyl
0: cWMKYzZMGk6FvNU8XXx/fjVc8dL2lj+P3E6UOvtYtX4Yb3TuVUp+NtJSJZ6eLiA=
0: -----END CERTIFICATE-----"
0:     next
0: end

You can, of course, use batch mode in combination with other automation options, like automation actions in stitches, but keep the 1023-character limit in mind.

In combination with automation actions, I want to note that batch mode does not display output of the command being executed, so if you do a get system status in batch mode, you don’t have any output of this directly. You have to do an execute batch lastlog to get the output.

A use case at the end: Push API feed

A little-known feature a FortiGate has is being able to host feeds itself using the Push API method. The idea is that you use API requests to send IPs, MAC addresses, domains, etc., to the FortiGate; it adds or removes the entries from the feed, and you use the feed wherever it makes sense, like in firewall policies.

One caveat is that the information in the feed is volatile and does not survive a reboot.

Let’s combine a few things from this post to create an example.

We start with our FortiAnalyzer Event Handler Custom-Brute-Force-Account-Login-Attack-FGT (this is the cloned event handler from above) as our trigger. If it gets triggered, we add the source IP from that event to the Push API feed using a webhook,ban the IP and also send an email notification with the log information.

Push API stitch
Push API feed valid

Push API automation stitch configuration

config system automation-trigger
    edit "FAZ-BRUTE-FORCE"
        set event-type faz-event
        set faz-event-name "Custom-Brute-Force-Account-Login-Attack-FGT"
    next
end
config system automation-action
    edit "PUSH-API-WEBHOOK"
        set action-type webhook
        set protocol https
        set uri "192.168.1.201/api/v2/monitor/system/external-resource/dynamic"
        set http-body "{ \"commands\": [{ \"name\": \"PUSH-FEED\", \"command\": \"add\", \"entries\": [ \"%%log.srcip%%\" ] } ]}"
        set port 8443
        config http-headers
            edit 1
                set key "Authorization"
                set value "4xdzrjdhdrs3417b51wqt4cw05p0rd"
            next
        end
        set verify-host-cert disable
    next
end
config system automation-stitch
    edit "FAZ-BRUTE-FORCE-PUSH-API"
        set status disable
        set trigger "FAZ-BRUTE-FORCE"
        config actions
            edit 3
                set action "IP Ban"
                set required enable
            next
            edit 2
                set action "PUSH-API-WEBHOOK"
                set required enable
            next
            edit 4
                set action "Email Notification"
                set required enable
            next
        end
    next
end

Wrapping up

I realize that nothing I covered in this post is that advanced, but I saw some of the things I covered being asked a few times, and some things, like the log dumping, Push API feeds, and auto-scripts as information storage, are things I personally wanted to look at a bit more, and I turned it into a post.

So, dear reader, thank you for your time in indulging me.

Comments

Be civil. Stay on topic. Don’t lie.

Leave a Reply

Your email address will not be published. Required fields are marked *