FortiManager Automation with Terraform

This is a short follow-up to the blog post I wrote about using Terraform with Fortigate. My customer wanted to leverage the Fortimanager and not the Fortigate directly to manage their Fortinet estate through Terraform (or rather, with Consul-Terraform-Sync).

The provider for Fortimanager is actually pretty recent and was only released a few months ago.

Just like in the previous post, I used the AWS Marketplace to deploy a Fortimanager. I used the AMI with a free 15-day license to get started and log onto the box with admin as the username and with the EC2 instance ID as the password.

Unlike the Fortios, the Fortimanager does not require an API token but your admin credentials (either provided as environmental variables or directly into your code).

To start with, you will need these couple of blocks. Update the parameters accordingly.

provider "fortimanager" {
  hostname     = "A.B.C.D"
  username     = "admin"
  password     = "SecurePassw0rd"
  insecure     = "true"

  scopetype    = "adom"
  adom         = "root"
}

terraform {
  required_providers {
    fortimanager = {
      source  = "fortinetdev/fortimanager"
    }
  }
}

If you try to go and build resources without enabling “rpc-permit” on your account, you will run into an issue:

Error: Error creating ObjectFirewallAddress resource: 
│ err -11: No permission for the resource

I know – I should have RTFM.

If I understand correctly, by default, your admin account only works for GUI / SSH but not for API calls. You need to enable “rpc-permit” privileges on your Fortimanager account to enable APIs, and therefore Terraform to work.

FMG-VM64-AWSOnDemand # config system admin user
(user)# edit admin
(admin)# set rpc-permit read-write
(admin)# end

Once you’ve done that, you can go ahead and create your resources:

Plan: 3 to add, 0 to change, 0 to destroy.
fortimanager_object_firewall_address.objectAddressB: Creating...
fortimanager_object_firewall_address.objectAddressA: Creating...
fortimanager_object_firewall_address.objectAddressA: Creation complete after 0s [id=terraformObjectAddressA]
fortimanager_object_firewall_addrgrp.trname: Creating...
fortimanager_object_firewall_address.objectAddressB: Creation complete after 0s [id=terraformObjectAddressB]
fortimanager_object_firewall_addrgrp.trname: Creation complete after 0s [id=terraform-addrgrp4]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

As mentioned, the provider was only released a month ago and it’s still got some limitations.

Yes, I can create object firewall addresses and an address group (which, I suspect, is what 99% of Fortinet users would like to do with Terraform):

% tfaa

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # fortimanager_object_firewall_address.objectAddressA will be created
  + resource "fortimanager_object_firewall_address" "objectAddressA" {
      + _image_base64         = (known after apply)
      + allow_routing         = "disable"
      + associated_interface  = "any"
      + cache_ttl             = (known after apply)
      + clearpass_spt         = (known after apply)
      + color                 = (known after apply)
      + comment               = (known after apply)
      + country               = (known after apply)
      + dynamic_sort_subtable = "false"
      + end_ip                = (known after apply)
      + end_mac               = (known after apply)
      + epg_name              = (known after apply)
      + fabric_object         = (known after apply)
      + filter                = (known after apply)
      + fqdn                  = (known after apply)
      + fsso_group            = (known after apply)
      + global_object         = (known after apply)
      + id                    = (known after apply)
      + interface             = (known after apply)
      + macaddr               = (known after apply)
      + name                  = "terraformObjectAddressA"
      + node_ip_only          = (known after apply)
      + obj_id                = (known after apply)
      + obj_tag               = (known after apply)
      + obj_type              = (known after apply)
      + organization          = (known after apply)
      + policy_group          = (known after apply)
      + scopetype             = "inherit"
      + sdn                   = (known after apply)
      + sdn_addr_type         = (known after apply)
      + sdn_tag               = (known after apply)
      + start_ip              = (known after apply)
      + start_mac             = (known after apply)
      + sub_type              = (known after apply)
      + subnet                = [
          + "192.168.10.0",
          + "255.255.255.255",
        ]
      + subnet_name           = (known after apply)
      + tenant                = (known after apply)
      + type                  = (known after apply)
      + uuid                  = (known after apply)
      + visibility            = (known after apply)
      + wildcard              = (known after apply)
      + wildcard_fqdn         = (known after apply)
    }

  # fortimanager_object_firewall_address.objectAddressB will be created
  + resource "fortimanager_object_firewall_address" "objectAddressB" {
      + _image_base64         = (known after apply)
      + allow_routing         = "disable"
      + associated_interface  = "any"
      + cache_ttl             = (known after apply)
      + clearpass_spt         = (known after apply)
      + color                 = (known after apply)
      + comment               = (known after apply)
      + country               = (known after apply)
      + dynamic_sort_subtable = "false"
      + end_ip                = (known after apply)
      + end_mac               = (known after apply)
      + epg_name              = (known after apply)
      + fabric_object         = (known after apply)
      + filter                = (known after apply)
      + fqdn                  = (known after apply)
      + fsso_group            = (known after apply)
      + global_object         = (known after apply)
      + id                    = (known after apply)
      + interface             = (known after apply)
      + macaddr               = (known after apply)
      + name                  = "terraformObjectAddressB"
      + node_ip_only          = (known after apply)
      + obj_id                = (known after apply)
      + obj_tag               = (known after apply)
      + obj_type              = (known after apply)
      + organization          = (known after apply)
      + policy_group          = (known after apply)
      + scopetype             = "inherit"
      + sdn                   = (known after apply)
      + sdn_addr_type         = (known after apply)
      + sdn_tag               = (known after apply)
      + start_ip              = (known after apply)
      + start_mac             = (known after apply)
      + sub_type              = (known after apply)
      + subnet                = [
          + "192.168.20.0",
          + "255.255.255.255",
        ]
      + subnet_name           = (known after apply)
      + tenant                = (known after apply)
      + type                  = (known after apply)
      + uuid                  = (known after apply)
      + visibility            = (known after apply)
      + wildcard              = (known after apply)
      + wildcard_fqdn         = (known after apply)
    }

  # fortimanager_object_firewall_addrgrp.trname will be created
  + resource "fortimanager_object_firewall_addrgrp" "trname" {
      + _image_base64         = (known after apply)
      + allow_routing         = "disable"
      + category              = (known after apply)
      + color                 = (known after apply)
      + comment               = (known after apply)
      + dynamic_sort_subtable = "false"
      + exclude               = (known after apply)
      + exclude_member        = (known after apply)
      + fabric_object         = (known after apply)
      + global_object         = (known after apply)
      + id                    = (known after apply)
      + member                = "terraformObjectAddressA"
      + name                  = "terraform-addrgrp4"
      + scopetype             = "inherit"
      + type                  = (known after apply)
      + uuid                  = (known after apply)
      + visibility            = (known after apply)
    }

Plan: 3 to add, 0 to change, 0 to destroy.
fortimanager_object_firewall_address.objectAddressB: Creating...
fortimanager_object_firewall_address.objectAddressA: Creating...
fortimanager_object_firewall_address.objectAddressB: Creation complete after 1s [id=terraformObjectAddressB]
fortimanager_object_firewall_address.objectAddressA: Creation complete after 1s [id=terraformObjectAddressA]
fortimanager_object_firewall_addrgrp.trname: Creating...
fortimanager_object_firewall_addrgrp.trname: Creation complete after 0s [id=terraform-addrgrp4]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

They’ve been created as illustrated in the GUI:

What we cannot do yet is create an address group with multiple addresses, which is kinda of defeating the object.

This works fine:

resource "fortimanager_object_firewall_address" "objectAddressA" {
  allow_routing        = "disable"
  associated_interface = "any"
  name                 = "terraformObjectAddressA"
  subnet = [
    "192.168.10.0",
    "255.255.255.255",
  ]
}

This works fine too:

resource "fortimanager_object_firewall_addrgrp" "trname" {
  allow_routing = "disable"
  member        = "terraformObjectAddressA"
  //type          = "array"
  name          = "terraform-addrgrp4"
  depends_on = [
    fortimanager_object_firewall_address.objectAddressA
  ]
}

But there’s no way -as far as I can see today – to create a list of addresses within an address group. You might be able to do it using the generic API group but that’s the only way today.

Hopefully this GitHub issue will be resolved soon and I will update this blog accordingly.


Update: it’s been fixed! Thanks for the Fortinet team for resolving this so quickly. You can now create a list of members:

resource "fortimanager_object_firewall_addrgrp" "trname" {
  allow_routing = "disable"
  member        = ["terraformObjectAddressA","terraformObjectAddressB"]
  name          = "terraform-addrgrp4"
  depends_on = [
    fortimanager_object_firewall_address.objectAddressA
  ]
}

Thanks for reading.

One thought on “FortiManager Automation with Terraform

  1. hi Nico,

    Hope you are doing great. i read your article and its quite helpful. I am trying to deploy Forti configurations via fortimanger using Terraform but facing some initial issues, although i am able to create some resources. Are you still active on this Blog and can please respond.

    Thanks

    Like

Leave a comment