As I was preparing for Accelerate 2026 and the Ultimate Fabric Challenge, one of the topics I wanted to focus on a lot more was Security Operations (SecOps), because this is an area I was always weak in.
SecOps, in this context, means working with FortiAnalyzer automation, i.e. event handlers, playbooks, incidents and connectors. A part I really wanted to immerse myself in was how FortiAnalyzer handles variables, and in the process, I think I gained some useful knowledge that I now want to show the world. So, dear reader, please, read on, and maybe this proves useful to you. It was relatively quick for me to complete, but it’s not going to be short, though there are lots of pictures!
The setup
For this post, I have the following components:
- FortiAnalyzer running 7.6.6
- FortiGate running 7.6.6
- FortiAuthenticator running 8.0.1
- An Ubuntu server to receive some POST requests
A primer on playbooks
FortiAnalyzer uses playbooks to automate various workflows using connectors that integrate other systems, like FortiGates, FortiClient EMS, FortiAuthenticator, etc. Every playbook uses a trigger, which is usually an event or an incident, but you could also run it on demand or on a schedule. After this trigger various tasks are executed that define what connector is used, various parameters, and variables, to accomplish, well, lots of things.
Playbooks can be simple, only sending a mail with information from an event, and fully orchestrate an incident response flow, by triggering on an event, creating an incident, running a report, banning IPs, sending mails, and you could go on and on, really. For this post, we’ll keep it simple and focus on how to work with variables.
Setting the stage
For the first type of variables, we’ll look at connectors and, in this case, specifically, the one for FortiAuthenticator (FAC). The FAC connector is one that works with HTTP-based communication, so whatever FortiAnalyzer sends to FAC can be easily read in cleartext.
The FAC connector has a few actions, and for our example, we’ll look at two:
- Get User List, which fetches a type of user (LDAP, RADIUS, or local) and saves it in a list
- Update User Status, which we will use to deactivate a user using an ID
I have prepared the following playbook:

There are four tasks in this:
- An On Demand trigger
- The Get User List task, using the FAC connector, to get the user list
- The Update User Status task, using the FAC connector, to deactivate a user by ID
- A generic webhook task that sends an HTTP POST request
The generic webhook is what is used to demonstrate one way we can check what FortiAnalyzer sends.
The configuration of the webhook looks like this:

The protocol being HTTP is important.
The target of the webhook is the Ubuntu server that uses http-echo-server to simply echo any incoming POST requests to the terminal.
[Update 2026-03-26]: Chris Eddisford from Fortinet has shown me webhook.site, which you can use as a generic webhook receiver, so you can send your POST requests to your unique URL and immediately see the result. No sign-up or Ubuntu server needed. Thanks a lot, Chris!
Here is the configuration of all tasks, except the On Demand trigger, because there isn’t anything to show for that one:
If I run this playbook as is, it won’t actually do anything on FAC, because the Update User Status task expects a specific user ID, and not a list of users, but we’ll get to that. At this point, we care about what FortiAnalyzer sends.
Note: I want to stress that this playbook exists purely as an exercise to show the process. In reality, you wouldn’t actually do this. In a production environment, you’d get the username from a triggered event, pass the username to the Get User action to retrieve the user ID and then use the Update User Status action with the user ID as your input.
Getting the variable values from HTTP-based connectors
If I run this playbook, the playbook will run successfully, and my Ubuntu server will get a POST request.

Ubuntu POST echo
[server] event: listening (port: 8081)
[server] event: connection (socket#1)
[socket#1] event: resume
[socket#1] event: data
--> POST / HTTP/1.1
--> Host: 192.168.1.151:8081
--> Accept-Encoding: identity
--> User-Agent: python-urllib3/2.3.0
--> Content-Length: 1164
-->
--> {"meta": {"limit": 10, "next": null, "offset": 0, "previous": null, "total_count": 2}, "objects": [{"active": true, "company": null, "department": null, "dn": "CN=manager,OU=USERS,OU=LAB,DC=ad,DC=labdomain,DC=com", "email": "", "fido": false, "first_name": "manager", "ftm_act_method": "", "id": 2, "is_locked": false, "last_name": "", "mobile_number": "", "reason": null, "recovery_by_question": false, "resource_uri": "/api/v1/ldapusers/2/", "server_address": "win-ad.ad.labdomain.com", "server_name": "WIN-AD", "token_auth": false, "token_serial": "", "token_type": "", "username": "manager@ad.labdomain.com"}, {"active": false, "company": "LAB", "department": "NetSec", "dn": "CN=adkevin,OU=USERS,OU=LAB,DC=ad,DC=labdomain,DC=com", "email": "adkevin@ad.labdomain.com", "fido": false, "first_name": "adkevin", "ftm_act_method": "email", "id": 1, "is_locked": false, "last_name": "LAB", "mobile_number": "", "reason": 0, "recovery_by_question": false, "resource_uri": "/api/v1/ldapusers/1/", "server_address": "win-ad.ad.labdomain.com", "server_name": "WIN-AD", "token_auth": false, "token_serial": "", "token_type": "", "username": "adkevin@ad.labdomain.com"}]}
[socket#1] event: prefinish
[socket#1] event: finish
[socket#1] event: readable
[socket#1] event: end
[socket#1] event: close
In the POST request we see that FortiAnalyzer uses a Python library to send the request (as a matter of fact, playbooks in general use Python, which you see clearly if you have an error), and we get the JSON payload consisting of two important dictionaries: meta, which has, as the name says, meta information, and objects, which holds a list of dictionaries that have the actual entries that were retrieved from FAC.
You can paste the entire payload into your favourite JSON formatter, like JSON formatter, to make it more readable.

But let’s assume you don’t have a Linux server with this http-echo-server available, and you want to get the information using only FortiAnalyzer. Don’t worry, this requires only marginally more work.
Important point: Before FortiAnalyzer sends a POST, it checks if it can connect to the destination over the specified port (it doesn’t matter if the port can do something with the payload), so if you don’t have a target with an open port, this won’t work. Luckily, FortiAnalyzer is definitely available, so you can use that as your target in the webhook connector as well as the task and send an HTTP POST to FortiAnalyzer.
First, start a sniffer on FortiAnalyzer that captures Ethernet data. Using my generic connector, my sniffer looks like this:
diagnose sniffer packet any ‘host 192.168.1.151 and port 8081’ 3 0 a
Once that is set up, run the playbook again and look at the output of the sniffer to find the payload, which starts with the meta dictionary.
FortiAnalyzer sniffer output
2026-03-23 18:53:44.784960 192.168.1.151.8081 -> 192.168.1.171.57014: psh 154846471 ack 44744771
0x0000 0000 0000 0001 000c 2931 3adc 0800 4500 ........)1:...E.
0x0010 05c9 398d 4000 4006 770f c0a8 0197 c0a8 ..9.@.@.w.......
0x0020 01ab 1f91 deb6 093a c507 02aa c043 5018 .......:.....CP.
0x0030 0209 ff1a 0000 4461 7465 3a20 4d6f 6e20 ......Date:.Mon.
0x0040 4d61 7220 3233 2032 3032 3620 3138 3a35 Mar.23.2026.18:5
0x0050 333a 3434 2047 4d54 2b30 3030 3020 2843 3:44.GMT+0000.(C
0x0060 6f6f 7264 696e 6174 6564 2055 6e69 7665 oordinated.Unive
0x0070 7273 616c 2054 696d 6529 0d0a 436f 6e6e rsal.Time)..Conn
0x0080 6563 7469 6f6e 3a20 636c 6f73 650d 0a43 ection:.close..C
0x0090 6f6e 7465 6e74 2d54 7970 653a 2074 6578 ontent-Type:.tex
0x00a0 742f 706c 6169 6e0d 0a41 6363 6573 732d t/plain..Access-
0x00b0 436f 6e74 726f 6c2d 416c 6c6f 772d 4f72 Control-Allow-Or
0x00c0 6967 696e 3a20 2a0d 0a0d 0a50 4f53 5420 igin:.*....POST.
0x00d0 2f20 4854 5450 2f31 2e31 0d0a 486f 7374 /.HTTP/1.1..Host
0x00e0 3a20 3139 322e 3136 382e 312e 3135 313a :.192.168.1.151:
0x00f0 3830 3831 0d0a 4163 6365 7074 2d45 6e63 8081..Accept-Enc
0x0100 6f64 696e 673a 2069 6465 6e74 6974 790d oding:.identity.
0x0110 0a55 7365 722d 4167 656e 743a 2070 7974 .User-Agent:.pyt
0x0120 686f 6e2d 7572 6c6c 6962 332f 322e 332e hon-urllib3/2.3.
0x0130 300d 0a43 6f6e 7465 6e74 2d4c 656e 6774 0..Content-Lengt
0x0140 683a 2031 3136 340d 0a0d 0a7b 226d 6574 h:.1164....{"met
0x0150 6122 3a20 7b22 6c69 6d69 7422 3a20 3130 a":.{"limit":.10
0x0160 2c20 226e 6578 7422 3a20 6e75 6c6c 2c20 ,."next":.null,.
0x0170 226f 6666 7365 7422 3a20 302c 2022 7072 "offset":.0,."pr
0x0180 6576 696f 7573 223a 206e 756c 6c2c 2022 evious":.null,."
0x0190 746f 7461 6c5f 636f 756e 7422 3a20 327d total_count":.2}
0x01a0 2c20 226f 626a 6563 7473 223a 205b 7b22 ,."objects":.[{"
0x01b0 6163 7469 7665 223a 2074 7275 652c 2022 active":.true,."
0x01c0 636f 6d70 616e 7922 3a20 6e75 6c6c 2c20 company":.null,.
0x01d0 2264 6570 6172 746d 656e 7422 3a20 6e75 "department":.nu
0x01e0 6c6c 2c20 2264 6e22 3a20 2243 4e3d 6d61 ll,."dn":."CN=ma
0x01f0 6e61 6765 722c 4f55 3d55 5345 5253 2c4f nager,OU=USERS,O
0x0200 553d 4c41 422c 4443 3d61 642c 4443 3d6c U=LAB,DC=ad,DC=l
0x0210 6162 646f 6d61 696e 2c44 433d 636f 6d22 abdomain,DC=com"
0x0220 2c20 2265 6d61 696c 223a 2022 222c 2022 ,."email":."",."
0x0230 6669 646f 223a 2066 616c 7365 2c20 2266 fido":.false,."f
0x0240 6972 7374 5f6e 616d 6522 3a20 226d 616e irst_name":."man
0x0250 6167 6572 222c 2022 6674 6d5f 6163 745f ager",."ftm_act_
0x0260 6d65 7468 6f64 223a 2022 222c 2022 6964 method":."",."id
0x0270 223a 2032 2c20 2269 735f 6c6f 636b 6564 ":.2,."is_locked
0x0280 223a 2066 616c 7365 2c20 226c 6173 745f ":.false,."last_
0x0290 6e61 6d65 223a 2022 222c 2022 6d6f 6269 name":."",."mobi
0x02a0 6c65 5f6e 756d 6265 7222 3a20 2222 2c20 le_number":."",.
0x02b0 2272 6561 736f 6e22 3a20 6e75 6c6c 2c20 "reason":.null,.
0x02c0 2272 6563 6f76 6572 795f 6279 5f71 7565 "recovery_by_que
0x02d0 7374 696f 6e22 3a20 6661 6c73 652c 2022 stion":.false,."
0x02e0 7265 736f 7572 6365 5f75 7269 223a 2022 resource_uri":."
0x02f0 2f61 7069 2f76 312f 6c64 6170 7573 6572 /api/v1/ldapuser
0x0300 732f 322f 222c 2022 7365 7276 6572 5f61 s/2/",."server_a
0x0310 6464 7265 7373 223a 2022 7769 6e2d 6164 ddress":."win-ad
0x0320 2e61 642e 6c61 6264 6f6d 6169 6e2e 636f .ad.labdomain.co
0x0330 6d22 2c20 2273 6572 7665 725f 6e61 6d65 m",."server_name
0x0340 223a 2022 5749 4e2d 4144 222c 2022 746f ":."WIN-AD",."to
0x0350 6b65 6e5f 6175 7468 223a 2066 616c 7365 ken_auth":.false
0x0360 2c20 2274 6f6b 656e 5f73 6572 6961 6c22 ,."token_serial"
0x0370 3a20 2222 2c20 2274 6f6b 656e 5f74 7970 :."",."token_typ
0x0380 6522 3a20 2222 2c20 2275 7365 726e 616d e":."",."usernam
0x0390 6522 3a20 226d 616e 6167 6572 4061 642e e":."manager@ad.
0x03a0 6c61 6264 6f6d 6169 6e2e 636f 6d22 7d2c labdomain.com"},
0x03b0 207b 2261 6374 6976 6522 3a20 6661 6c73 .{"active":.fals
0x03c0 652c 2022 636f 6d70 616e 7922 3a20 224c e,."company":."L
0x03d0 4142 222c 2022 6465 7061 7274 6d65 6e74 AB",."department
0x03e0 223a 2022 4e65 7453 6563 222c 2022 646e ":."NetSec",."dn
0x03f0 223a 2022 434e 3d61 646b 6576 696e 2c4f ":."CN=adkevin,O
0x0400 553d 5553 4552 532c 4f55 3d4c 4142 2c44 U=USERS,OU=LAB,D
0x0410 433d 6164 2c44 433d 6c61 6264 6f6d 6169 C=ad,DC=labdomai
0x0420 6e2c 4443 3d63 6f6d 222c 2022 656d 6169 n,DC=com",."emai
0x0430 6c22 3a20 2261 646b 6576 696e 4061 642e l":."adkevin@ad.
0x0440 6c61 6264 6f6d 6169 6e2e 636f 6d22 2c20 labdomain.com",.
0x0450 2266 6964 6f22 3a20 6661 6c73 652c 2022 "fido":.false,."
0x0460 6669 7273 745f 6e61 6d65 223a 2022 6164 first_name":."ad
0x0470 6b65 7669 6e22 2c20 2266 746d 5f61 6374 kevin",."ftm_act
0x0480 5f6d 6574 686f 6422 3a20 2265 6d61 696c _method":."email
0x0490 222c 2022 6964 223a 2031 2c20 2269 735f ",."id":.1,."is_
0x04a0 6c6f 636b 6564 223a 2066 616c 7365 2c20 locked":.false,.
0x04b0 226c 6173 745f 6e61 6d65 223a 2022 4c41 "last_name":."LA
0x04c0 4222 2c20 226d 6f62 696c 655f 6e75 6d62 B",."mobile_numb
0x04d0 6572 223a 2022 222c 2022 7265 6173 6f6e er":."",."reason
0x04e0 223a 2030 2c20 2272 6563 6f76 6572 795f ":.0,."recovery_
0x04f0 6279 5f71 7565 7374 696f 6e22 3a20 6661 by_question":.fa
0x0500 6c73 652c 2022 7265 736f 7572 6365 5f75 lse,."resource_u
0x0510 7269 223a 2022 2f61 7069 2f76 312f 6c64 ri":."/api/v1/ld
0x0520 6170 7573 6572 732f 312f 222c 2022 7365 apusers/1/",."se
0x0530 7276 6572 5f61 6464 7265 7373 223a 2022 rver_address":."
0x0540 7769 6e2d 6164 2e61 642e 6c61 6264 6f6d win-ad.ad.labdom
0x0550 6169 6e2e 636f 6d22 2c20 2273 6572 7665 ain.com",."serve
0x0560 725f 6e61 6d65 223a 2022 5749 4e2d 4144 r_name":."WIN-AD
0x0570 222c 2022 746f 6b65 6e5f 6175 7468 223a ",."token_auth":
0x0580 2066 616c 7365 2c20 2274 6f6b 656e 5f73 .false,."token_s
0x0590 6572 6961 6c22 3a20 2222 2c20 2274 6f6b erial":."",."tok
0x05a0 656e 5f74 7970 6522 3a20 2222 2c20 2275 en_type":."",."u
0x05b0 7365 726e 616d 6522 3a20 2261 646b 6576 sername":."adkev
0x05c0 696e 4061 642e 6c61 6264 6f6d 6169 6e2e in@ad.labdomain.
0x05d0 636f 6d22 7d5d 7d com"}]}
Copy the payload from meta to the curly brackets at the end to your text editor of choice (mine is Notepad++) and perform the following find and replace actions:
- Replace \r\n (line breaks) with no character using the Extended function
- Replace :. (colon dot) with . (dot)
- Replace :, (comma dot) with , (comma)
After that, you should have the entire payload on a single line, and you can, again, paste it into a JSON formatter.
Here is a short video where I show this process if you want to see it in action:
With the JSON information available, we can easily find out what information has been retrieved, how it’s structured, and how it can be further used.
JSON payload
{
"meta": {
"limit": 10,
"next": null,
"offset": 0,
"previous": null,
"total_count": 2
},
"objects": [
{
"active": true,
"company": null,
"department": null,
"dn": "CN=manager,OU=USERS,OU=LAB,DC=ad,DC=labdomain,DC=com",
"email": "",
"fido": false,
"first_name": "manager",
"ftm_act_method": "",
"id": 2,
"is_locked": false,
"last_name": "",
"mobile_number": "",
"reason": null,
"recovery_by_question": false,
"resource_uri": "/api/v1/ldapusers/2/",
"server_address": "win-ad.ad.labdomain.com",
"server_name": "WIN-AD",
"token_auth": false,
"token_serial": "",
"token_type": "",
"username": "manager@ad.labdomain.com"
},
{
"active": false,
"company": "LAB",
"department": "NetSec",
"dn": "CN=adkevin,OU=USERS,OU=LAB,DC=ad,DC=labdomain,DC=com",
"email": "adkevin@ad.labdomain.com",
"fido": false,
"first_name": "adkevin",
"ftm_act_method": "email",
"id": 1,
"is_locked": false,
"last_name": "LAB",
"mobile_number": "",
"reason": 0,
"recovery_by_question": false,
"resource_uri": "/api/v1/ldapusers/1/",
"server_address": "win-ad.ad.labdomain.com",
"server_name": "WIN-AD",
"token_auth": false,
"token_serial": "",
"token_type": "",
"username": "adkevin@ad.labdomain.com"
}
]
}
Using my payload, I can get the ID for the user “adkevin@ad.labdomain.com” by accessing objects[1][‘id’].
With this information, I can now change the Update User Status task accordingly.
Putting the pieces together
When you edit a task that includes variables, you have two methods to set variables:
- Select them from a selector menu
- Writing the variable string yourself
You can combine these methods by first selecting a variable, clicking on the “A” and then editing the variable as you need it.
The FAC Update User Status task wants the ID that FAC itself assigned, so if we first select the user_list variable from the previous task, then switch to the manual input, we can now add the information we got previously. This can look like the following:

If we now run the playbook again, the user with the ID we got will now be correctly disabled on FAC, as signalled by the red X in Status.

This was for HTTP-based connectors, but I also want to show how you can get variable information to use in FortiGate automation stitches.
FortiGate automation stitches and FortiAnalyzer
If a FortiGate logs to FortiAnalyzer and you have configured an automation stitch that uses an Incoming Webhook Call as a trigger, the FortiGate will show up as a connected device in the “FortiOS Connector”

For this post, I have configured a basic automation stitch that, as an action, creates an address object and adds it to a group. I have this group as the source in a deny policy, and the idea is that if this stitch is triggered, the offending IP will get automatically blocked.
The trigger, action, and stitch configuration is as follows:
But we’re getting ahead of ourselves here, because in the action picture, I’m using variables, but how do I even know what they are called and what they represent?
Setting the stage for automation stitches
A FortiGate gets the information from FortiAnalyzer in the log variable, which can be accessed using %%log%% in an action. This information is directly available in the GUI by clicking on the % symbol.

This is already a big help, but it doesn’t help with showing you the entire list of variables that FortiAnalyzer makes available to you, so let’s find out how to get this information.
First, we need to change the action on the FortiGate. By using the %%log%% variable, as shown in the picture above, we can assign this variable a value on FortiAnalyzer with a playbook.
Second, we need a playbook, and in this case, I have a simple one with two tasks:
- Trigger on an event
- Execute a FortiOS webhook

The trigger matches on the event SSH-GOOGLE, and the event handler simply checks for SSH connections to the Google DNS server.
The FortiOS webhook task has the information about the FortiGate device where this action gets executed, the webhook name, and the variables, which are taken from the automation action.

An important point here is that the variables you use in an action will be dynamically and periodically synced to FortiAnalyzer, meaning that if you change or add variables in an action and wait a bit, FortiAnalyzer will show the updated variables in the task. You see this in two places:
- The task itself
- By clicking on the “x device(s) connected” message in the FortiOS Connector (see the picture above)
Here is a comparison of how it looks if we use the variables from the action we will use to create and add an object to a group, and the action we use to get the information for the full information.

The last piece we need, before seeing the values for the variables, is telling FortiAnalyzer what variable to send, and this is done in the FortiOS webhook task.
In this task, we have, much like with the FAC example from earlier, two ways to accomplish this:
- Select the variable from a selector menu
- Writing the variable string yourself
By selecting the variable, you can go through the entire list of available ones, but if you’re not quite sure what you’re looking for, this is tedious, because you’d need as many variables in the FortiGate’s action as there are variables.
The better method is simply sending everything from the trigger, which is accessed via the variable ${trigger}.

With this trigger variable set for the FortiGate’s log variable, we can finally get to finding our values, and this is quite simple.
Getting automation stitch variable values
On the FortiGate, we are going to set up a debug for the automation stitch application, and there we’ll see what is being delivered by FortiAnalyzer. The necessary commands are as follows:
diagnose debug application autod -1
diagnose debug enable
With this prepared, we can trigger the event handler by trying to connect to Google’s 8.8.8.8 DNS via SSH, and after a bit, the debug messages will come in.
FortiGate automation stitch log debug
__action_cli_script_open()-171: cli script action:CREATE-BAD-HOST is called. svc ctx:0x55a1ff3aef80
accprof:super_admin script:
%%log%%
__read_cli_script_result()-117: cli script:
autod.3
output:
========== #1, 2026-03-23 19:33:24 ==========
FGT02 date=2026-03-23 time=19:33:24 eventtime=1774290803211381959 tz="+0100" logid="0100065301" type="event" subtype="system" level="notice" vd="root" logdesc="Internal Message" path="system" name="automation-stitch" action="webhook" mkey="Incoming Webhook Call" log="{ \"adom_name\": \"LAB\", \"adom_prefix\": \"FSFADOM198\", \"handler_name\": \"SSH-GOOGLE\", \"group_value\": \"192.168.1.231::\", \"event_id\": \"202603231000010013\", \"event_time\": \"1774290774\", \"devid\": \"FGVMSLTM26006351\", \"vdom\": \"root\", \"severity\": 2, \"epid\": \"3\", \"epname\": \"192.168.1.231\", \"epip\": \"192.168.1.231\", \"dst_epid\": \"101\", \"dst_epname\": \"8.8.8.8\", \"dst_epip\": \"8.8.8.8\", \"dvid\": \"1062\", \"euid\": \"3\", \"handler_type\": \"basic\", \"rule_name\": \"SSH-GOOGLE\", \"euname\": \"N\\\/A\", \"subject\": \"srcip:192.168.1.231<G>1:6:13<\\\/G>\", \"groupby1\": \"192.168.1.231\", \"logtype\": \"traffic\", \"devtype\": \"FortiGate\", \"extrainfo\": \"{ }\", \"targets\": [ { \"srcip\": \"192.168.1.231\" }
Unknown action 0
======= end of #1, 2026-03-23 19:33:24 ======
And here we got our variable names and their associated values, right for the picking and inserting into our automation action. Now we can change our script in the FortiGate action to create an object using epip in the name and subnet, putting mkey and date in the comment, and adding the object to our group (see above or the debug below for the full script).
Trigger the event handler again, and everything looks as it should.
FortiGate automation stitch real debug
__action_cli_script_open()-171: cli script action:CREATE-BAD-HOST is called. svc ctx:0x55a1ff3be410
accprof:super_admin script:
config firewall address
edit "H_%%log.epip%%"
set subnet %%log.epip%% 255.255.255.255
set comment "%%log.mkey%%_%%log.date%%"
next
end
config firewall addrgrp
edit G_BAD-GROUP
append member "H_%%log.epip%%"
next
end
__read_cli_script_result()-117: cli script:
autod.2
output:
========== #1, 2026-03-23 19:25:33 ==========
FGT02 config firewall address
FGT02 (address) edit "H_192.168.1.231"
FGT02 (H_192.168.1.231) set subnet 192.168.1.231 255.255.255.255
FGT02 (H_192.168.1.231) set comment "Incoming Webhook Call_2026-03-23"
FGT02 (H_192.168.1.231) next
FGT02 (address) end
FGT02 config firewall addrgrp
FGT02 (addrgrp) edit G_BAD-GROUP
FGT02 (G_BAD-GROUP) append member "H_192.168.1.231"
FGT02 (G_BAD-GROUP) next
FGT02 (addrgrp) end
======= end of #1, 2026-03-23 19:25:34 ======

Wrapping up
I have uploaded the various Fortinet resources from this post (playbooks, event handler and FortiGate configuration) to a GitHub repository, if you want to look at everything yourself. In the future, I will put all blog-related resources in this repository.
I did basically everything here before Accelerate 2026, and it helped me become more confident in SecOps tasks, which was why I put this topic on my to-do list in the first place.
There is definitely more that can be shown regarding this topic, and a better version of this post exists somewhere in a parallel universe, but perfection is something you can strive for; reaching it should never be the goal. My main goal was to cover blind spots that are not mentioned anywhere. I hope this covering of blind posts helps you, dear reader.








Leave a Reply