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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s