Lately I have been working a lot with Fortigate firewalls and unlike many firewalls that I have worked before, they have pretty good REST API. I have already shared some of my early experiments with API, but I wanted to do more with it. Due to some unexpected turn of events, this summer I had a luxury of having an intern working with me, who didn’t know much of networking, but was very proficient with python. The result of our work – fortios_api module for Ansible – is now helping me manage my environment.
Why write a module for Ansible?
I chose Ansible simply because I know it. I have worked with NX-OS Ansible modules before, I liked how they were done and wanted something similar for my firewalls. And since there aren’t many modules for Fortigates available in Ansible, I had to create them. But before writing the modules, I needed to figure out how I am going to use them.
Just pushing a config change with Ansible is nice, but it isn’t all that exciting. There isn’t that much value in automation for me if I end up editing yaml files for every change instead of typing commands in CLI. I want something better. Here’s the way I picture a better system for my use case:
I have a central database (can be excel spreadsheet, can be IPAM) that holds the records of all the servers connected to all of the firewalls. This database also holds some information about functions of those servers (be that web server, mail server, etc) and their connectivity requirements. The only place where people interact with the system is via this database – they can add or delete entries as needed. Then scripts will parse the database and generate Ansible playbooks and variables for each firewall, which then can be pushed to the them. While I still may need to edit some files in order to change firewall rules, it is a lot simpler. And because I can do some error-checking and other intelligence in the script processing database, it is now possible to let others define the connectivity requirements for themselves and then I (or security team) just need to approve the change for it to go live.
When I was working with NX-OS modules for Ansible, I found that it was very easy to programmatically add things to the configuration. But it was a lot harder to delete things or to ensure that nobody logged in and changed some parts of the config manually. For example, if I configured Vlan1, Vlan2 and Vlan3 on the switch and then I wanted to get rid of Vlan2, I’d have to run a playbook where I call out that Vlan2 has state: absent. But what if I just wanted to run a playbook that would ensure that I only have Vlan1 and Vlan3 configured on the switch, regardless of which other VLANs are configured on it? From what I know, it wasn’t that easy. You would have to first get the config, then parse it and figure out which VLANs are present and then add missing ones and remove extra ones. And you will have to take care of that all.
I wanted to avoid that in the new module, so that’s why the main design requirement was – this module must take desired end-state config, rather than just set of actions to perform.
Also when working with Fortios API I have realized that most of the API endpoints behave in exactly the same way. So I thought that it would be neat if the module could have the core part that does vast majority of the work and then it has some small auxiliary modules that just provide necessary information for that core module to do the work (like API endpoint, list of available variables, etc). And since Fortios can return API schema on request, it would be even neater if it could figure some things dynamically (this later turned out to be a bad idea).
Issues due to the design decisions
Probably the biggest issue with the end state model is that it’s hard to onboard device already in production. You can’t simply go and add a new firewall policy with the module, you MUST add them all at once and make sure that they are correct. I have tried to simplify this process with adding of print_current_config method, which will write a file with full config from a given API endpoint. It has a lot of extra data (all the default values are there, etc), but at least it’s a start.
Another issue that I ran into due to the end state model – this module requires a lot more intelligence. For example, I want to make sure that if playbook doesn’t specify the interface, then the interface is either deleted (if it’s a virtual interface) or it’s reset to defaults. But how do I determine which interfaces are virtual and which are not? I actually don’t have the answer, right now I ask user to give me a list of permanent interfaces (it would be terrible for environment with lots of different models, luckily I have only 1 model in production). The same goes for configured address groups and services – you have to manually specify the built-in ones to be ignored, otherwise module will try to delete or reset them (which may break lots of things).
Very unexpected issue came from me trying to dynamically figure out which values API can take. Ansible calls it argument spec and I am building one dynamically as the module runs (and I cache it in the local folder). Well, as it turns out, Ansible doesn’t allow modules that don’t have fixed argument spec, so they won’t take it into the project.
Does it work?
In short: yes. I am still in process of rolling this out for all of the functions on all of the firewalls, but I am already managing less critical functions like snmp via it on all of the firewalls in production. And I have a small environment that is 100% managed by the module, I have logged in to device’s CLI only once to set the management IP.
Can I use it?
Absolutely! Just in case you missed it, you can find module here: https://github.com/eoprede/ansible_fortios_api
I have tried to give some general documentation and examples there, also each module has (or at least should have) examples within it.
If you have questions/comments, you can find me on reddit /r/networking or in networktocode fortinet slack channel.
I’m going to keep working with this module internally, but I don’t expect doing any more development work outside of bug fixes or adding another module or two for common tasks. I may consider fixing it to Ansible standards so that it can be included in the project at some later time, but I’ve heard Fortinet is working on their own module – so this work may become obsolete once that module gets released.