fortinetdev / terraform-provider-fortios

Terraform Fortios provider
https://www.terraform.io/docs/providers/fortios/
Mozilla Public License 2.0
69 stars 50 forks source link

Adding members to a redundant interface #163

Closed cliftonvella closed 3 years ago

cliftonvella commented 3 years ago

Terraform Version: 0.15.4 FortiOS Provider Version: 1.11.0

Hi,

I am trying to find a way to member interfaces to a redundant interface on a Fortigate using Terraform.

I am using the “fortios_system_interface” resource and I manage to create the redundant interface itself:

resource “fortios_system_interface” “redundant_internal” {
name = “red_int”
role = “lan”
ip = “0.0.0.0/0.0.0.0”
type = “redundant”
vdom = “root”
status = “up”
description = “Managed with Terraform”
}

When I try to add the “member” attribute, I get an error saying that the resource does not expect that attribute, when it is in fact listed in the documentation. I took a look at the code for the “fortios_system_interface” resource and it seems that “member” is expected to be a list of maps, so I tried to define as a local variable like so:

locals {
member = [
{
interface_name = “port3”
},
{
interface_name = “port4”
},
]
}

Then, I tried adding the “interface” attribute to the resource, with “${local.member}” as its value, but I get an error saying that the “interface” attribute expects a value of type string. So out of frustration I tried defining the “interface” attribute as “port3,port4”, and while this time I got no errors, the Fortigate simply ignored that and no members were added to the redundant interface.

I’m honestly at a loss on how to do this so if someone with experience on this can help it would be greatly appreciated.

Thanks and Regards,

Clifton

frankshen01 commented 3 years ago

Before configuring terraform and FortiGate redundant interface, some basic knowledge needs to be mastered:

FortiGate Redundancy: https://docs.fortinet.com/document/fortigate/6.4.0/administration-guide/567758/aggregation-and-redundancy

Terraform basic syntax: Locals: https://www.terraform.io/docs/language/values/locals.html, https://learn.hashicorp.com/tutorials/terraform/locals Dynamic Blocks: https://www.terraform.io/docs/language/expressions/dynamic-blocks.html Import: https://www.terraform.io/docs/cli/import/index.html

Detailed Steps and Validation:

Step1 Prepare the prerequisites

Make sure that the interface to be added to member is not referenced by any other place, as described in https://docs.fortinet.com/document/fortigate/6.4.0/administration-guide/567758/aggregation-and-redundancy:

Redundancy In a redundant interface, traffic only goes over one interface at any time. This differs from an aggregated interface where traffic goes over all interfaces for increased bandwidth. This difference means redundant interfaces can have more robust configurations with fewer possible points of failure. This is important in a fully-meshed HA configuration.

An interface is available to be in a redundant interface if:

It is a physical interface and not a VLAN interface. It is not already part of an aggregated or redundant interface. It is in the same VDOM as the redundant interface. It does not have an IP address and is not configured for DHCP or PPPoE. It has no DHCP server or relay configured on it. It does not have any VLAN subinterfaces. It is not referenced in any security policy, VIP, or multicast policy. It is not monitored by HA. It is not one of the FortiGate-5000 series backplane interfaces. When an interface is included in a redundant interface, it is not listed on the Network > Interfaces page. You cannot configure the interface individually and it is not available for inclusion in security policies, VIPs, or routing.

Step2 Show TF Configuration:


provider "fortios" {
  hostname = "192.168.52.177"
  insecure = "true"
  token    = "GNH7r40H65GNb46kd4rG8rtrmn0fr1"
}

locals  {
  redunantmeb = [
    {
        interface_name = "port3"
    },
    {
        interface_name = "port4"
    },
  ]
}

resource "fortios_system_interface" "trname" {
    algorithm                                  = "L4"
    bfd_desired_min_tx                         = 250
    bfd_detect_mult                            = 3
    bfd_required_min_rx                        = 250
    defaultgw                                  = "enable"
    ip                                         = "0.0.0.0 0.0.0.0"
    mtu                                        = 1500
    mtu_override                               = "disable"
    name                                       = "testri"
    status                                     = "up"
    type                                       = "redundant"
    vdom                                       = "root"
    dynamic_sort_subtable                      = true

    dynamic "member" {
        for_each = local.redunantmeb

        content {
            interface_name = member.value.interface_name
        }
    }
}

Step3 Terraform apply and plan:


# terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # fortios_system_interface.trname will be created
  + resource "fortios_system_interface" "trname" {
      + ac_name                                    = (known after apply)
      + aggregate                                  = (known after apply)
      + algorithm                                  = "L4"
      + alias                                      = (known after apply)
      + allowaccess                                = (known after apply)
      + ap_discover                                = (known after apply)
      + arpforward                                 = (known after apply)
      + auth_type                                  = (known after apply)
      + auto_auth_extension_device                 = (known after apply)
      + autogenerated                              = (known after apply)
      + bandwidth_measure_time                     = (known after apply)
      + bfd                                        = (known after apply)
      + bfd_desired_min_tx                         = 250

 .................

      + username                                   = (known after apply)
      + vdom                                       = "root"
      + vindex                                     = (known after apply)
      + vlan_protocol                              = (known after apply)
      + vlanforward                                = (known after apply)
      + vlanid                                     = (known after apply)
      + vrf                                        = (known after apply)
      + vrrp_virtual_mac                           = (known after apply)
      + wccp                                       = (known after apply)
      + weight                                     = (known after apply)
      + wins_ip                                    = (known after apply)

      + member {
          + interface_name = "port3"
        }
      + member {
          + interface_name = "port4"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

fortios_system_interface.trname: Creating...
fortios_system_interface.trname: Creation complete after 1s [id=testri]

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

# terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

fortios_system_interface.trname: Refreshing state... [id=testri]

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

Step4 Check result:

labfirewall # config system interface

labfirewall (interface) # show
config system interface
    edit "testri"
        set vdom "root"
        set type redundant
        set member "port3" "port4"
        set snmp-index 7
    next
end

1

We can find that it has been configured successfully.

BTW: When you come across a parameter that you don’t know how to configure, then you can try to configure a simple template on the FortiGate GUI/CLI, and use the terraform import function to import the configuration on the FGT into Terraform, and then use terraform show to see the contents of the exported terraform hcl format. This can also provide some help.

cliftonvella commented 3 years ago

@frankshen01 Thanks a lot for the very detailed explanation. You’re a life saver!

At one point I did try defining the interface members using a dynamic block, but I was trying to assign the dynamic block as a value to the interface attribute, which obviously did not work. I got a bit confused because I have already done this on Ansible and over there the fortios interface module actually accepts a member attribute, while the Terraform module does things differently.

May I ask if what you wrote in your reply is documented somewhere? Because the official provider documentation on the Terraform website does not show any of this.

frankshen01 commented 3 years ago

@cliftonvella You're welcome!

> At one point I did try defining the interface members using a dynamic block, but I was trying to assign the dynamic block as a value to the interface attribute, which obviously did not work. I got a bit confused because I have already done this on Ansible and over there the fortios interface module actually accepts a member attribute, while the Terraform module does things differently.

As described in the aforementioned https://www.terraform.io/docs/language/expressions/dynamic-blocks.html:

  dynamic "origin_group" {
    for_each = var.load_balancer_origin_groups
    content {
      name = origin_group.key

      dynamic "origin" {
        for_each = origin_group.value.origins
        content {
          hostname = origin.value.hostname
        }
      }
    }
  }

Here is a clear way to use dynamic. As for the Ansible you mentioned, Terraform's syntax itself has many differences with Ansible. Also, there is no concept of module you mentioned in Terraform. And terraform provides the concept of resource with state maintenance as the core.

> May I ask if what you wrote in your reply is documented somewhere? Because the official provider documentation on the Terraform website does not show any of this.

The reason why I spent 2 hours and described and answered your question in the most detailed way possible as I can is mainly to help you solve the problem quickly. I sincerely recommend to you a book "terraform up and running" (plus terraform official documents), from which you can master a lot of terraform software's own advantages, skills and usage methods.

Thanks!

cliftonvella commented 3 years ago

Hello @frankshen01, thanks a lot for your detailed answer once again. I will definitely check out that book you recommended.