Getting Started - FortiManager

In this article we will cover how to add a model device and install a policy package on FortiManager using Ansible.

Overview

In this article, quite similar to the previous (Getting started with Ansible and FortiGate Automation) where we covered how to use Ansible with FortiOS we will also use a docker container to run the control node, if you followed the first article you can even use the same container but this time we will improve upon the manual tasks to create the container and leverage some useful features of docker to automate that process, specifically by defining the image and which packages to install using a Dockerfile and define mount points with a docker-compose file.

After we have an automated container build we will explore how to interact with the FortiManager modules, create a new model device, associate a policy package and install it.

Versions & Source

Software

Version

FortiManager-VM

6.2.3

Ansible

2.9.11

Python

3.7.5

fortinet.fortimanager collection

1.0.3

Last updated: 2020-08-12

Recipe

1. Define a Dockerfile and docker-compose.yml files

Dockerfile is a file with instructions for building the base image used on your containers, so instead of pulling a base image, starting a container with it and manually installing packages we will define which packages we need in this file.

Dockerfile
FROM ubuntu:19.10

RUN set -x && \
    \
    echo "==> Installing python3 and dependencies..."  && \
    apt update && \
    apt install python3-pip --yes && \
    \
    echo "==> Installing Ansible..."  && \
    pip3 install ansible && \
    \
    echo "==> Installing Ansible collections..."  && \
    ansible-galaxy collection install fortinet.fortimanager

WORKDIR /ansible

In this Dockerfile we defined :

  • Base Image is ubuntu:19.10

  • Installation of python3, Ansible and dependencies

  • Installation of Ansible galaxy module fortinet.fortimanager

  • Finally, the working directory will be /ansible/playbooks

Now create a new file named docker-compose.yml, this will contain the container definitions that we usually pass a command line arguments, having it defined on the docker-compose file will guarantee that we don’t forget to pass any relevant arguments to the container.

docker-compose.yml
version: '3'
services:
  ansible:
    build: .
    image: ansible:1.0
    volumes:
    - ./ansible:/ansible

In this docker-compose YAML file we defined:

  • To start the container using a build definition (Dockerfile) that is on the same folder as the docker-compose file

  • Name that build image as ansible:1.0

  • Mount the local folder ./ansible to the container folder /ansible. This will come in handy as we edit files locally on our device and those files are transparently available to the container.

After you have those two files created execute the following command:

$ docker-compose run ansible bash
Building ansible
Step 1/3 : FROM ubuntu:19.10
19.10: Pulling from library/ubuntu

...

Successfully built 3e3ad52d93fe
Successfully tagged ansible:1.0
WARNING: Image for service ansible was built because it did not
already exist. To rebuild this image you must use
`docker-compose build` or `docker-compose up --build`.
root@69bdab627c69:/ansible#

This will trigger the base image download, custom image creation (with the packages defined on Dockerfile, this image will be named using the convention of repository:tag so if you list your images it will appear as ansible:1.0) and will spin up a new container mounting the folder as instructed on docker-compose.yml

After the build process is finished you should be at the container shell, you can verify that the fortinet.fortimanager Ansible module was correctly installed by checking the documentation:

$ ansible-doc fortinet.fortimanager.fmgr_dvm_cmd_add_device

2. Create an inventory and playbook file for adding a new model device

The inventory file now will be slightly different from our previous one, we will optimize it a little and put common variables directly on the inventory so we don’t have to repeat them on all playbooks. This is a common principle called DRY.

inventory.ini
[fortimanagers]
fmg01 ansible_host=10.20.10.12 ansible_user="ansible_user" ansible_password="ansible_pass" 

[fortimanagers:vars]
ansible_network_os             = fortinet.fortimanager.fortimanager
ansible_httpapi_port           = 10406
ansible_httpapi_use_ssl        = True
ansible_httpapi_validate_certs = False
ansible_connection             = httpapi

We will also define a minimal ansible.cfg file, this will allow us to run our playbooks without specifying the inventory file on the command line:

ansible.cfg
[defaults]
inventory = ./inventory.ini
gathering = False

Finally let’s dig into the playbook itself.

My first tip is that during the playbook creation you need to pay attention to indentation, I used a online YAML to JSON converter to make sure my spaces were correctly aligned which sometimes is not obvious from a visual inspection.

Here’s the playbook, create it on a new /ansible/playbook subfolder:

01_add_model_device.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 01_add_model_device.yml
---
- name: Add model device to FMG and install Policy Package
  hosts: fmg01
  collections:
    - fortinet.fortimanager

  tasks:
    - name: Add model device
      fmgr_dvm_cmd_add_device:
        loose_validation: true
        method: exec
        params:
          - data:
              adom: root
              device:
                device action: add_model
                mgmt_mode: fmg
                os_ver: 6
                mr: 2
                sn: FGVM020000000000

Line 1 is just a comment which in this case is the same as the filename, line 2 is a YAML directive that states the start of the document.

Lines 3-8 should be familiar, except line 5. Disabling the gather_facts is useful to speed up the playbook execution.

This task is to add a new device model (a template of a device, this is useful for Zero Touch Provisioning use cases), we can read the module documentation and based on those parameters we create the playbook.

Line 13 is required because we’re not bypassing the module constraints for os_ver and mr (OS Version and Major Release). All other lines are relevant to the action we’re executing, adding a new device model with SN FGVM020000000000, FortiOS Version 6.2 on the FMG ADOM root.

This is the file/dir structure so far:

.
├── Dockerfile
├── ansible
│   ├── ansible.cfg
│   ├── inventory.ini
│   └── playbooks
│       └── 01_add_model_device.yml
└── docker-compose.yml

2 directories, 5 files

At this point you’re ready to run your first playbook against FortiManager:

root@c02f4307c0d2:/ansible# ansible-playbook playbooks/01_add_model_device.yml

PLAY [Add model device to FMG and install Policy Package] ********************************************************

TASK [Add model device] ******************************************************************************************
changed: [fmg01]

PLAY RECAP *******************************************************************************************************
fmg01                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

If you inspect your FMG at this point you should see a new managed device. Yay!

But it’s not sufficient to only have the device without being able to associate and install policies on it right? So let’s create a new playbook to execute those tasks.

3. Associate and install a Policy Package to a device

02_install_policy_package_to_device.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 02_install_policy_package_to_device.yml
---
- name: Add model device to FMG and install Policy Package
  hosts: fmg01
  gather_facts: no
  collections:
    - fortinet.fortimanager

  tasks:
    - name: Add policy package to model device
      fmgr_pm_pkg_adom_obj:
        loose_validation: true
        method: set
        url_params:
          adom: root
        params:
          - data:
              name: default
              scope member:
                - name: FGVM020000000000
                  vdom: root
              type: pkg

    - name: Install policy package to model device
      fmgr_securityconsole_install_package:
        loose_validation: true
        method: exec
        params:
          - data:
              adom: root
              pkg: default
              scope:
                - name: FGVM020000000000
                  vdom: root

Notice that each task has it’s specific module, understand the API structure of FMG will make your job easier when you’re trying to figure it out which module correspond to which task. Another details that may have captured your attention is that we’re repeating ourselves a lot. We will optimize that in a bit, before that let’s run this playbook and see the results.

root@c02f4307c0d2:/ansible# ansible-playbook playbooks/02_install_policy_package_to_device.yml

PLAY [Add model device to FMG and install Policy Package] ********************************************************

TASK [Add policy package to model device] ************************************************************************
changed: [fmg01]

TASK [Install policy package to model device] ********************************************************************
changed: [fmg01]

PLAY RECAP *******************************************************************************************************
fmg01                      : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Policy Package default associated to device model.

Policy Package correctly installed on device.

4. Refactor the playbooks

Finally we have a working set of files that achieve our initial goal: Add a new model device and install a policy package on it, however if we need to add another device we would have to change those files on several places so let’s optimize that code by using some variables and isolating those variables to it’s own file, this way anytime we need to add a new device model we only need to change the variables, not the task itself.

Create this new file:

03_add_model_dev_with_PP_and_vars.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 03_add_model_dev_with_PP_and_vars.yml
---
- name: Add model device to FMG and install Policy Package - Optimized with vars
  hosts: fmg01
  gather_facts: no
  collections:
    - fortinet.fortimanager
  vars_files:
    - 03_variables.yml

  tasks:
    - name: Add model device
      fmgr_dvm_cmd_add_device:
        loose_validation: true
        method: exec
        params:
          - data:
              adom: '{{ adom }}'
              device:
                device action: add_model
                mgmt_mode: fmg
                os_ver: 6
                mr: 2
                sn: '{{ device_sn }}'
                name: '{{ device_name }}'

    - name: Add policy package to model device
      fmgr_pm_pkg_adom_obj:
        loose_validation: true
        method: set
        url_params:
          adom: '{{ adom }}'
        params:
          - data:
              name: '{{ policy_package_name }}'
              scope member:
                - name: '{{ device_name }}'
                  vdom: '{{ vdom }}'
              type: pkg

    - name: Install policy package to model device
      fmgr_securityconsole_install_package:
        loose_validation: true
        method: exec
        params:
          - data:
              adom: '{{ adom }}'
              pkg: '{{ policy_package_name }}'
              scope:
                - name: '{{ device_name }}'
                  vdom: '{{ vdom }}'

In this new file we are consolidating the add device and install policy package steps, notice the syntax for using variables: '{{ variable_name }}' and on lines 9-10 where we import the variables file.

Create the variables file:

03_variables.yml
1
2
3
4
5
6
7
# 03_variables.yml
---
adom: root
device_sn: FGVM020000000000
device_name: FG01
vdom: root
policy_package_name: default

Delete the existing device on FMG and run the new playbook:

root@c02f4307c0d2:/ansible# ansible-playbook playbooks/03_add_model_dev_with_PP_and_vars.yml

PLAY [Add model device to FMG and install Policy Package - Optimized with vars] **********************************

TASK [Add model device] ******************************************************************************************
changed: [fmg01]

TASK [Add policy package to model device] ************************************************************************
changed: [fmg01]

TASK [Install policy package to model device] ********************************************************************
changed: [fmg01]

PLAY RECAP *******************************************************************************************************
fmg01                      : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

And the final result on FMG:

Feel free to explore other FortiManager modules, you just created an automated build process for the ansible control node and optimized ansible config, inventory and playbooks for interacting with FortiManager.