Welcome to pyFG’s documentation!¶
This library does not pretend to hide or abstract FortiOS configuration. Its sole purpose is to give you a a programmatic way to read it and modify it. To be able to use you will still need to understand how the CLI works.
There are two main libraries:
FortiOS
– This is the main library you will use. It allows you to connect to a device,- read its configuration, it provides an interface to the running and the candidate config and provides diff and commit operations amongst others.
FortiConfig
– This library represents a block of configuration. You will usually- deal with this just to create new objects (like a new firewall policy) or just via the attribures running_config and candidate_config in a
FortiOS
object.
Tutorials¶
First Steps¶
Connecting to the device¶
Let’s start connecting to the vdom test_vdom
for a particular device:
>>> from pyFG import FortiOS
>>> d = FortiOS('192.168.76.50', vdom='test_vdom')
>>> d.open()
Loading configuration¶
Now you can easily load a block of configuration and do some read operations:
>>> d.load_config('router bgp')
We can verify easily that we got the configuration doing the following:
>>> print d.running_config.to_text()
config router bgp
config redistribute isis
end
config redistribute6 connected
end
config redistribute6 isis
end
config redistribute static
end
config redistribute6 rip
end
config redistribute connected
end
config redistribute ospf
end
config redistribute6 static
end
config neighbor
edit 172.20.213.23
set remote-as 65555
set route-map-in "test4"
next
edit 2.2.2.2
set remote-as 123
set shutdown enable
next
end
config redistribute rip
end
config redistribute6 ospf
end
end
>>>
Or we can get the AS for each neighbor in a programmatic way:
>>> for neigh, param in d.running_config['router bgp']['neighbor'].iterblocks():
... print neigh, param.get_param('remote-as')
...
172.20.213.23 65555
2.2.2.2 123
>>>
We can also iterate over all the parameters for a specific block. Let’s get all the parameters for the neighbor 2.2.2.2:
>>> for param, value in d.running_config['router bgp']['neighbor']['2.2.2.2'].iterparams():
... print param, value
...
remote-as 123
shutdown enable
Adding a new sub block of configuration¶
Now, let’s add a new bgp neighbor. First we have to create the configuration block and set the parameters:
>>> from pyFG import FortiConfig
>>> nn = FortiConfig(config_type='edit', name='3.3.3.3')
>>> nn.set_param('remote-as', 12346)
>>> nn.set_param('shutdown', 'enable')
Now we assign it to the candidate configuration and print it to see it looks like we want:
>>> d.candidate_config['router bgp']['neighbor']['3.3.3.3'] = nn
>>> print d.candidate_config.to_text()
config router bgp
config redistribute isis
end
config redistribute6 connected
end
config redistribute6 isis
end
config redistribute static
end
config redistribute6 rip
end
config redistribute connected
end
config redistribute ospf
end
config redistribute6 static
end
config neighbor
edit 172.20.213.23
set remote-as 65555
set route-map-in "test4"
next
edit 2.2.2.2
set remote-as 123
set shutdown enable
next
edit 3.3.3.3
set remote-as 12346
set shutdown enable
next
end
config redistribute rip
end
config redistribute6 ospf
end
end
>>>
Deleting a sub_block of configuration¶
Now, let’s delete neighbor 2.2.2.2:
>>> d.candidate_config['router bgp']['neighbor'].del_block('2.2.2.2')
Checking configuration changes¶
After all this changes let’s see what has changed:
>>> print d.compare_configuration()
conf vdom
edit test_vdom
config router bgp
config neighbor
delete 2.2.2.2
edit 3.3.3.3
set remote-as 12346
set shutdown enable
next
end
end
end
>>>
As you can see, that method returns all the necessary commands to reach the candidate configuration from the running configuration.
Setting a parameter¶
Let’s set a route-map outbound to neighbor 172.20.213.23:
>>> d.candidate_config['router bgp']['neighbor']['172.20.213.23'].set_param('route-map-out', 'non-existant-routemap')
Committing changes¶
Now that we have done several changes let’s commit the changes:
>>> d.commit()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pyFG/fortios.py", line 242, in commit
self._commit(config_text, force)
File "pyFG/fortios.py", line 297, in _commit
raise exceptions.FailedCommit(wrong_commands)
pyFG.exceptions.FailedCommit: [('-3', 'set route-map-out non-existant-routemap')]
>>>
The route map we tried to assign does not exist so the commit failed returning a FailedCommit exception. By default, if one single command fails during the commit operation the entire commit will be rolled back. At this point you have three options:
- Create the route map
- Delete that parameter or assign an existing route-map
- Force the changes.
We are going to try forcing the changes:
>>> d.commit(force=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pyFG/fortios.py", line 242, in commit
self._commit(config_text, force)
File "pyFG/fortios.py", line 299, in _commit
raise exceptions.ForcedCommit(wrong_commands)
pyFG.exceptions.ForcedCommit: [('-3', 'set route-map-out non-existant-routemap')]
>>> print d.compare_configuration()
conf vdom
edit test_vdom
config router bgp
config neighbor
edit 172.20.213.23
set route-map-out non-existant-routemap
next
end
end
end
>>>
We still got an exception although this time a different one; ForcedCommit. As you can see from the compare_configuration method the rest of the changes went through.
Rolling back changes¶
Now, let’s assume we regret the changes we just did and we want to rollback:
>>> d.rollback()
>>> print d.running_config.to_text()
config router bgp
config redistribute isis
end
config redistribute6 connected
end
config redistribute6 isis
end
config redistribute static
end
config redistribute6 rip
end
config redistribute connected
end
config redistribute ospf
end
config redistribute6 static
end
config neighbor
edit 172.20.213.23
set remote-as 65555
set route-map-in "test4"
next
edit 2.2.2.2
set remote-as 123
set shutdown enable
next
end
config redistribute rip
end
config redistribute6 ospf
end
end
>>>
Voilà, we are back to our original configuration. BGP neighbor 3.3.3.3 is gone, 2.2.2.2 is back and that broken parameter map is not in there anymore.
Loading configuration from a file¶
The following example is very interesting if you plan to manage your devices using a configuration management system based in templates like ansible. Let’s assume we have a configuration file that we have generated somehow with the following content:
config router bgp
config neighbor
edit "172.20.213.32"
set remote-as 333
set route-map-out "test4"
next
end
config redistribute "connected"
end
config redistribute "rip"
end
config redistribute "ospf"
end
config redistribute "static"
end
config redistribute "isis"
end
config redistribute6 "connected"
end
config redistribute6 "rip"
end
config redistribute6 "ospf"
end
config redistribute6 "static"
end
config redistribute6 "isis"
end
end
We want to load that configuration into a device, replacing its current configuration. First we have to connect to the device and load the running configuration we want to replace:
>>> from pyFG import FortiOS
>>> d = FortiOS('192.168.76.50', vdom='test_vdom')
>>> d.open()
>>> d.load_configuration('router bgp', empty_candidate=True)
The parameter empty_candidate
will load only the running config. Now, we load the configuration file into the
candidate config:
>>> with open ("bgp_config.txt", "r") as my_file:
... data=my_file.read()
...
>>> d.load_configuration(config_text=data, in_candidate=True)
>>> print d.candidate_config.to_text()
config router bgp
config redistribute isis
end
config redistribute6 connected
end
config redistribute6 isis
end
config redistribute static
end
config redistribute6 rip
end
config redistribute connected
end
config redistribute ospf
end
config redistribute6 static
end
config neighbor
edit 172.20.213.32
set remote-as 333
set route-map-out "test4"
next
end
config redistribute rip
end
config redistribute6 ospf
end
end
Now you can check the differences like this:
>>> print d.compare_configuration()
conf vdom
edit test_vdom
config router bgp
config neighbor
delete 172.20.213.23
delete 2.2.2.2
edit 172.20.213.32
set remote-as 333
set route-map-out "test4"
next
end
end
end
And commit the changes:
>>> d.commit()
>>> print d.compare_configuration()
>>>
>>> d.close()
A final compare_configuration returning an empty string will prove us that our changes were applied correctly.
Options¶
SSH Config¶
You can now store some of the access details for your FortiGate appliacnes inside the ~/.ssh/config of the user. Currently supported:
- Username
- Hostname
- Proxy Command
- IdentityFile
Example 1¶
Simple “alias” whereas you would use the ‘hostname’ “fortigate” inside your scripts instead of the IP address. Handy if you do not have a DNS server:
Host fortigate
Hostname 192.168.1.1
User admin
Example 2¶
A bit more complex for when you need go via a SSH proxy server to reach the appliance:
Host fortigate
Hostname 192.168.1.1
User admin
ProxyCommand ssh user@10.10.10.1 nc %h %p