Skip to main content
  1. Posts/

pyfg - Compare Fortigate Configurations

Introduction #

I recently created a fork of an unmaintained Python library that can be used to interact with a Fortigate and its configuration. The library is called pyfg and the fork can be accessed through the following Github page: pyfg.

In this blog post I want to introduce the library shortly and explain why I created this fork and how it can be used.

Initial situation #

For a specific use case at work I had to compare two distinct configurations of a Fortigate firewall and determine the commands that need to be executed to transform one configuration into the other.

Of course creating a diff between the two configuration backups is a possibility but this procedure has one disadvantage. While the commands for sections that have to be added can be taken as is from the diff, sections that should be removed can’t be used directly. Instead one has to manually create a delete statement to erase the relevant part of the config. Depending on the amount of differences between the two configurations this can be quite cumbersome.

During my research for a better solution I stumbled upon the pyfg python library originally developed by Spotify for this scenario. However the library has some issues such as a very basic prompt detection that led to multiple problems in my use case. Additionally it isn’t maintained anymore as the last commit was in 2017 and the repository is archived since 2022. Because of that I decided to create my own fork.

PyFG library #

Just like the original project my fork of the pyfg library provides an API that allows you to interact with the configuration of a Fortigate and edit it. The general capabilities of the library include:

  • Connect to a device via SSH, retrieve the running configuration (either the entire config or only some blocks) and build a model
    • Alternatively build a model by reading a configuration from a file
  • Create a local candidate configuration by doing changes to the model
    • Alternatively read a candidate configuration from a file
  • Create a diff between the running and the local candidate configuration
  • Generate the commands that are necessary to transform the running into the candidate configuration

While I left most of the general functionality unchanged thus far, I made several internal adjustments that should improve how a configuration is read and processed. The most notable changes are:

  • Dropped support for Python versions older than 3.8 and removed Python 2 compatibility
  • Added support for custom SSH ports
  • Improved prompt detection when retrieving configurations from a device
  • Improved behavior how data is read from the SSH channel, allowing it to read large configurations without running into timeouts
  • Added support for multi line values, such as policy comments or certificates

To install the library simply clone the repository and install it via pip:

$ git clone https://github.com/xXKnightRiderXx/pyfg.git
$ cd pyfg
$ pip3 install .

Example #

In this section I want to show a short code example that solves my initial problem when I started the fork: comparing two configurations and generating the commands that I need to execute to transform one config into the other.

First have a look at the following two files which show model router bgp configurations:

For this example we assume that the current configuration is our starting point while the target configuration is the one that we want to reach. Using the library we can compare the two configurations with a few lines of Python code:

from pyFG import FortiOS, FortiConfig

with open("current.conf") as f, open("target.conf") as g:
    current = f.read()
    target = g.read()

d = FortiOS("")
# Load current config and don't set it as candidate one because we load the candidate from a file
d.load_config(config_text=current, empty_candidate=True)
# Load candidate config
d.load_config(config_text=target, in_candidate=True)
# Print the commands
print(d.compare_config())

Running this small script provides the following output, which are the commands that you need to execute in order to transform the current configuration into the target configuration.

config router bgp
  set as "2222"
  config neighbor
    edit 10.240.4.1
      set bfd "disable"
    next
    delete 10.240.4.11
    edit 10.240.4.26
      set remote-as "65443"
      set route-map-out "announce_networks_npu2"
      set update-source "ASH2-LON3_2"
      set bfd "enable"
    next
  end
end

As already said before there are additional use cases where the library can be used. For further examples have a look at the examples folder of the repository. Please be aware that so far I only adjusted them in such a way that they work with Python 3. In the future I want to expand them, provide additional documentation and maybe even write another blog post to present them.

Closing Words #

Thanks for reading this blog post and I hope that the library is helpful to you. If you find a bug or have any other kind of feedback feel free to open an issue in the repository.

Until next time 👋