Lately I have been growing tired of using CLI to configure network devices, so when I was faced with the project to deploy about 100 of Fortigate firewalls, I have decided that I am not that interested in copy-pasting configs via CLI and I want to do something different. Luckily for me, Fortigate did roll out pretty good API in the code 5.4, which can be used to configure most (if not all) of the parameters of the device. I already had automation that would generate configuration for all the devices by pulling IPAM (I may write a different post about that at a later time), so I just needed something to push that config to devices. That’s the part I’ll concentrate on in this post.
Because I am tired of screen scraping. Because I am tired of having to deal with different carriage return symbols between portX interfaces and mgmtX interfaces. Yes, I could have used already built library, but I just don’t like the principle of it. REST API is not ideal, in fact it has shortcomings compared to CLI in Fortigate implementation, but I see it as a lesser of 2 evils. Plus, more and more devices are starting to support it, so I decided that I could use some experience with it.
Why write library myself, instead of using one by Fortinet?
Fortinet used to hide their APIs behind the paywall, but now you can get into its documentation if you know 2 people with @fortinet.com e-mail addresses. They do have a python library that you can use to make API calls, but I chose to write my own for the following reasons:
- Fortinet’s library required python 2.7, there are some machines in my environment that only have 2.6 and I wanted flexibility to use them if needed.
- I wanted to be able to use socks proxy for API calls. Yes, I could ssh with -L and do port forwarding, but it was much easier to ssh with -D and do socks proxy.
- Fortinet’s library didn’t seem to do anything complicated, so I figured I could replicate it fairly easily (which ended up being mostly true)
At the end, I came up with this: https://github.com/eoprede/fortigate_api
Please note that this code by no means is a full-baked solution, it was created for a very narrow use case and it does it fairly well. Hopefully I’ll have time to keep working on it and adding functionality.
Interfacing with the device via REST API
To make a very simple script that calls to a Fortigate at IP 220.127.116.11 and queries and prints configuration of port1, download the fw_api_test.py file and create the following python script in the same folder.
from fw_api_test import fortigate_api
fw = fortigate_api('18.104.22.168', 'user', 'password')
a = fw.show(['cmdb', 'system', 'interface', 'port1'])
There are more examples available in the readme on github.
Since I wanted to avoid as much manual labor as possible, I ended up writing a bunch of automation around the provisioning as well. At the end, the provisioning process looked like this:
- Populate IPAM with all the information about the new device(s), including interface IPs and some systems that firewall is protecting.
- Create a JSON file that has common configuration shared between all the devices (like snmp and syslog settings, ddos profiles, etc)
- Pull IPAM and create a JSON file with unique configuration for each firewall.
- Wire up the firewalls, turn them on, set mgmt IPs via the console.
- Run a script, which reads both of the JSON files and in 20 seconds joins the firewalls in the cluster, creates VDOMs and sets up the rest of the config.
Unfortunately, due to security concerns, I am not allowed to share even sanitized version of my script. If there’s an interest, I can come up with some unrelated to my work scripts, but that will take some time.
Problems with Fortigate API
The biggest issue by far is that it isn’t very consistent. For example, path for the interface configuration is cmdb/system/interface, which is quite logical. The path for syslog settings is, however, cmdb/log.syslogd/setting. This extra dot in the path is annoying and often times not very logical. I did have to download schema and search through it quite a few times to find the right URL for the API call.
Another inconsistency that I have discovered – not all the fields are being edited in the same way. For example, you have to edit firewall rules one by one, you can’t just send the device a whole list of them (which would be awesome). You can, however, send a whole list of rules for prefix list in one command, which makes it much easier to edit them.
But probably by far the biggest issue is that you can not pre-stage the config. Once you push the command, it is active and saved in the config. CLI does have a couple of tricks to avoid it (run time only config mode and batch mode config utility), but API does not. You can do a config backup via the API call, so at least you can make sure that you have good config before the changes and then you can revert back if needed, but it would be much easier to manage the device if you could do bulk configuration.
Was it worth it?
Yes, absolutely. Having 100 devices that are configured with the same template and are guaranteed to not have any fat-fingered values in them is worth a lot. The values in IPAM could have been fat-fingered, but fixing them would be very easy with just writing the provisioning script again.
Did I have to use REST API for this? Most likely not. But I’m not sure if using already built-in library for SSH communication would be any easier or faster, as I still needed to generate JSON files from my other systems and feeding JSON into REST API is as easy as it gets.
Being able to push commands via API is great, but it’s useful mostly for the initial deploy. It would be nice to be able to manage the device in the live environment, using tools like Ansible. Yes, there’s a module that can manage Fortigates, but it is using SSH again. So, next step is probably to write a module for Ansible. How hard could that be?