Skip to content

Commit

Permalink
add fortigate node (#1917)
Browse files Browse the repository at this point in the history
* add fortigate node

* rename to fortios and use fortinet_fortios kind name

* add fortinet_fortios to register.go and register the new type

* modify fortinet example lab

* streamline launch command

* rename to fortigate

* added link to download portal

* added kind references

* adapt lab example

* added fortigate test

* try running on hosted

* use ghcr image

* added login step

---------

Co-authored-by: Roman Dodin <dodin.roman@gmail.com>
  • Loading branch information
robotwalk and hellt committed Mar 13, 2024
1 parent 1042e22 commit 756d51a
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,14 @@ jobs:
with:
py_ver: ${{ needs.file-changes.outputs.py_ver }}

fortigate-tests:
uses: ./.github/workflows/fortigate-tests.yml
needs:
- file-changes
- build-containerlab
with:
py_ver: ${{ needs.file-changes.outputs.py_ver }}

# a job that downloads coverage artifact and uses codecov to upload it
coverage:
runs-on: ubuntu-22.04
Expand All @@ -335,6 +343,7 @@ jobs:
- vxlan-tests
- kind-tests
- sros-tests
- fortigate-tests

steps:
- name: Checkout
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/fortigate-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: fortigate-test

"on":
workflow_call:
inputs:
py_ver:
required: true
type: string

jobs:
fortigate-tests:
runs-on: ubuntu-22.04
strategy:
matrix:
runtime:
- "docker"
test-suite:
- "01*.robot"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/download-artifact@v4
with:
name: containerlab

- name: Move containerlab to usr/bin
run: sudo mv ./containerlab /usr/bin/containerlab && sudo chmod a+x /usr/bin/containerlab

- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.py_ver }}
cache: pip
cache-dependency-path: "tests/requirements.txt"

- name: Install robotframework
run: |
pip install -r tests/requirements.txt
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Sanitize test-suite name
run: echo "TEST_SUITE=$(echo ${{ matrix.test-suite }} | tr -d '*')" >> $GITHUB_ENV

- name: Run tests
run: |
bash ./tests/rf-run.sh ${{ matrix.runtime }} ./tests/09-fortigate/${{ matrix.test-suite }}
# upload test reports as a zip file
- uses: actions/upload-artifact@v4
if: always()
with:
name: 09-fortigate-log-${{ env.TEST_SUITE }}-${{ matrix.runtime }}
path: ./tests/out/*.html

# upload coverage report from unit tests, as they are then
# merged with e2e tests coverage
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-fortigate-tests-${{ env.TEST_SUITE }}-${{ matrix.runtime }}
path: /tmp/clab-tests/coverage/*
retention-days: 7
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ In addition to native containerized NOSes, containerlab can launch traditional v
* [Palo Alto PAN](https://containerlab.dev/manual/kinds/vr-pan)
* [IPInfusion OcNOS](https://containerlab.dev/manual/kinds/ipinfusion-ocnos)
* [Check Point Cloudguard](https://containerlab.dev/manual/kinds/checkpoint_cloudguard/)
* [Fortinet Fortigate](https://containerlab.dev/manual/kinds/fortinet_fortigate/)
* [Aruba AOS-CX](https://containerlab.dev/manual/kinds/vr-aoscx)
* [OpenBSD](https://containerlab.dev/manual/kinds/openbsd)

Expand Down
2 changes: 2 additions & 0 deletions clab/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
crpd "github.com/srl-labs/containerlab/nodes/crpd"
cvx "github.com/srl-labs/containerlab/nodes/cvx"
ext_container "github.com/srl-labs/containerlab/nodes/ext_container"
fortinet_fortigate "github.com/srl-labs/containerlab/nodes/fortinet_fortigate"
host "github.com/srl-labs/containerlab/nodes/host"
ipinfusion_ocnos "github.com/srl-labs/containerlab/nodes/ipinfusion_ocnos"
k8s_kind "github.com/srl-labs/containerlab/nodes/k8s_kind"
Expand Down Expand Up @@ -50,6 +51,7 @@ func (c *CLab) RegisterNodes() {
crpd.Register(c.Reg)
cvx.Register(c.Reg)
ext_container.Register(c.Reg)
fortinet_fortigate.Register(c.Reg)
host.Register(c.Reg)
ipinfusion_ocnos.Register(c.Reg)
keysight_ixiacone.Register(c.Reg)
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ In addition to native containerized NOSes, containerlab can launch traditional v
* [Palo Alto PAN](manual/kinds/vr-pan.md)
* [IPInfusion OcNOS](manual/kinds/ipinfusion-ocnos.md)
* [Check Point Cloudguard](manual/kinds/checkpoint_cloudguard.md)
* [Fortinet Fortigate](manual/kinds/fortinet_fortigate.md)
* [Aruba AOS-CX](manual/kinds/vr-aoscx.md)
* [OpenBSD](manual/kinds/openbsd.md)

Expand Down
63 changes: 63 additions & 0 deletions docs/manual/kinds/fortinet_fortigate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
search:
boost: 4
---
# Fortinet Fortigate

Fortinet Fortigate virtualized security appliance is identified with the `fortinet_fortigate` kind in the [topology file](../topo-def-file.md). It is built using the [hellt/vrnetlab](../vrnetlab.md) project and essentially is a Qemu VM packaged in a docker container format.

## Getting Fortinet Fortigate disk image

Users can obtain the qcow2 disk image for Fortinet Fortigate VM from the [official support site](https://support.fortinet.com/Download/VMImages.aspx); a free account required. Download the "New deployment" variant of the FGVM64 VM for the KVM platform.

Extract the downloaded zip file and rename the `fortios.qcow2` to `fortios-vX.Y.Z.qcow2` where `X.Y.Z` is the version of the Fortigate VM. Put the renamed file in the `fortigate` directory of the cloned [hellt/vrnetlab](https://github.com/hellt/vrnetlab) project and run `make` to build the container image.

## Managing Fortinet Fortigate nodes

/// note
Containers with Fortinet Fortigate VM inside will take ~2min to fully boot.
You can monitor the progress with the `docker logs -f <container-name>` command.
///

Fortinet Fortigate node launched with containerlab can be managed via the following interfaces:

/// tab | bash
to connect to a `bash` shell of a running fortigate container:

```bash
docker exec -it <container-name/id> bash
```

///
/// tab | CLI
to connect to the Fortigate CLI

```bash
ssh admin@<container-name/id/IP-addr>
```

///
/// tab | Web UI (HTTP)
Fortigate VM comes with HTTP(S) server with a GUI manager app. You can access the Web UI using http schema.

```bash
http://<container-name/id/IP-addr>
```

You can expose container's port 80 with the [`ports`](../nodes.md#ports) setting in containerlab and get access to the Web UI using your containerlab host IP.
///
/// note
Default login credentials: `admin:admin`
///

## Interfaces mapping

Fortinet Fortigate interfaces are named as follows in the topology file:

* `eth0` - management interface connected to the containerlab management network (rendered as `port1` in the CLI)
* `eth1` - first data interface, mapped to the first data port of the VM (rendered as `port2`)
* `eth2+` - second and subsequent data interface

When containerlab launches Fortigate node the `port1` interface of the VM gets assigned `10.0.0.15/24` address from the QEMU DHCP server. This interface is transparently stitched with container's `eth0` interface such that users can reach the management plane of the Fortigate using containerlab's assigned IP.

Data interfaces `eth1+` need to be configured with IP addressing manually using CLI or other available management interfaces.
1 change: 1 addition & 0 deletions docs/manual/kinds/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Within each predefined kind, we store the necessary information that is used to
| **OpenBSD** | [`openbsd`](openbsd.md) | supported | VM |
| **Keysight ixia-c-one** | [`keysight_ixia-c-one`](keysight_ixia-c-one.md) | supported | container |
| **Check Point Cloudguard** | [`checkpoint_cloudguard`](checkpoint_cloudguard.md) | supported | VM |
| **Fortinet Fortigate** | [`fortinet_fortigate`](fortinet_fortigate.md) | supported | VM |
| **Palo Alto PAN** | [`vr-panos/vr-paloalto_panos`](vr-pan.md) | supported | VM |
| **Linux bridge** | [`bridge`](bridge.md) | supported | N/A |
| **Linux container** | [`linux`](linux.md) | supported | container |
Expand Down
1 change: 1 addition & 0 deletions docs/manual/vrnetlab.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The images that work with containerlab will appear in the supported list as we i
| Aruba AOS-CX | [aruba_aoscx](kinds/vr-aoscx.md) | | |
| IPInfusion OcNOS | [ipinfusion_ocnos](kinds/ipinfusion-ocnos.md) | | |
| Checkpoint Cloudguard | [checkpoint_cloudguard](kinds/checkpoint_cloudguard.md) | | |
| Fortinet Fortigate | [fortinet_fortigate](kinds/fortinet_fortigate.md) | | |

### Connection modes

Expand Down
11 changes: 11 additions & 0 deletions lab-examples/fortigate/fortigate.clab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: fortigate
topology:
nodes:
forti1:
kind: fortinet_fortigate
image: vrnetlab/vr-fortigate:7.0.14
forti2:
kind: fortinet_fortigate
image: vrnetlab/vr-fortigate:7.0.14
links:
- endpoints: ["forti1:eth1", "forti2:eth1"]
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ nav:
- OpenBSD: manual/kinds/openbsd.md
- Keysight IXIA-C One: manual/kinds/keysight_ixia-c-one.md
- Check Point Cloudguard: manual/kinds/checkpoint_cloudguard.md
- Fortinet Fortigate: manual/kinds/fortinet_fortigate.md
- Palo Alto PAN: manual/kinds/vr-pan.md
- KinD: manual/kinds/k8s-kind.md
- Linux bridge: manual/kinds/bridge.md
Expand Down
90 changes: 90 additions & 0 deletions nodes/fortinet_fortigate/fortigate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2020 Nokia
// Licensed under the BSD 3-Clause License.
// SPDX-License-Identifier: BSD-3-Clause

package fortinet_fortigate

import (
"context"
"fmt"
"path"

"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/types"
"github.com/srl-labs/containerlab/utils"
)

var (
kindnames = []string{"fortinet_fortigate"}
defaultCredentials = nodes.NewCredentials("admin", "admin")
)

const (
// scrapligo doesn't have a driver for fortigate, can be copied from scrapli community
// scrapliPlatformName = "fortinet_fortigate"
configDirName = "config"
startupCfgFName = "startup-config.cfg"
)

// Register registers the node in the NodeRegistry.
func Register(r *nodes.NodeRegistry) {
r.Register(kindnames, func() nodes.Node {
return new(fortigate)
}, defaultCredentials)
}

type fortigate struct {
nodes.DefaultNode
}

func (n *fortigate) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error {
// Init DefaultNode
n.DefaultNode = *nodes.NewDefaultNode(n)
// set virtualization requirement
n.HostRequirements.VirtRequired = true

n.Cfg = cfg
for _, o := range opts {
o(n)
}
// env vars are used to set launch.py arguments in vrnetlab container
defEnv := map[string]string{
"USERNAME": defaultCredentials.GetUsername(),
"PASSWORD": defaultCredentials.GetPassword(),
"CONNECTION_MODE": nodes.VrDefConnMode,
"VCPU": "2",
"RAM": "2048",
"DOCKER_NET_V4_ADDR": n.Mgmt.IPv4Subnet,
"DOCKER_NET_V6_ADDR": n.Mgmt.IPv6Subnet,
}

n.Cfg.Env = utils.MergeStringMaps(defEnv, n.Cfg.Env)

// mount config dir to support startup-config functionality
n.Cfg.Binds = append(n.Cfg.Binds, fmt.Sprint(path.Join(n.Cfg.LabDir, configDirName), ":/config"))

if n.Cfg.Env["CONNECTION_MODE"] == "macvtap" {
// mount dev dir to enable macvtap
n.Cfg.Binds = append(n.Cfg.Binds, "/dev:/dev")
}

n.Cfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace",
defaultCredentials.GetUsername(), defaultCredentials.GetPassword(), n.Cfg.ShortName, n.Cfg.Env["CONNECTION_MODE"])

return nil
}

func (n *fortigate) PreDeploy(_ context.Context, params *nodes.PreDeployParams) error {
utils.CreateDirectory(n.Cfg.LabDir, 0777)
_, err := n.LoadOrGenerateCertificate(params.Cert, params.TopologyName)
if err != nil {
return nil
}

return nodes.LoadStartupConfigFileVr(n, configDirName, startupCfgFName)
}

// CheckInterfaceName checks if a name of the interface referenced in the topology file correct.
func (n *fortigate) CheckInterfaceName() error {
return nodes.GenericVMInterfaceCheck(n.Cfg.ShortName, n.Endpoints)
}
3 changes: 2 additions & 1 deletion schemas/clab.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
"cisco_c8000",
"cvx",
"cumulus_cvx",
"openbsd"
"openbsd",
"fortinet_fortigate"
]
},
"license": {
Expand Down
50 changes: 50 additions & 0 deletions tests/09-fortigate/01-two-fortigates.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
*** Settings ***
Library OperatingSystem
Resource ../common.robot

Suite Teardown Run Keyword Cleanup


*** Variables ***
${lab-name} forti
${lab-file-name} fortigates.clab.yml
${runtime} docker


*** Test Cases ***
Deploy ${lab-name} lab
Log ${CURDIR}
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name}
Log ${output}
Should Be Equal As Integers ${rc} 0
IF ${rc} != 0 Fatal Error Failed to deploy ${lab-name} lab

Wait for VM to reach running state
Sleep 30s
Wait Until Keyword Succeeds 120 5s VM is healthy


*** Keywords ***
VM is healthy
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${runtime} exec clab-${lab-name}-forti1 cat /health
Log ${output}

Should Be Equal As Integers ${rc} 0
Should Contain ${output} 0 running

${rc2} ${output2} = Run And Return Rc And Output
... sudo -E ${runtime} exec clab-${lab-name}-forti2 cat /health
Log ${output2}

Should Be Equal As Integers ${rc2} 0
Should Contain ${output2} 0 running

Cleanup
# dump logs from VM
Run sudo -E ${runtime} logs clab-${lab-name}-forti1 &> /tmp/${lab-name}-forti1.log
${contents} = OperatingSystem.Get File /tmp/${lab-name}-forti1.log
Log ${contents}

Run sudo -E ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup
13 changes: 13 additions & 0 deletions tests/09-fortigate/fortigates.clab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: forti
topology:
nodes:
forti1:
kind: fortinet_fortigate
image: ghcr.io/srl-labs/fortigate:7.0.14
# image: registry.srlinux.dev/pub/fortinet_fortigate:7.0.14
forti2:
kind: fortinet_fortigate
image: ghcr.io/srl-labs/fortigate:7.0.14
# image: registry.srlinux.dev/pub/fortinet_fortigate:7.0.14
links:
- endpoints: ["forti1:eth1", "forti2:eth1"]

0 comments on commit 756d51a

Please sign in to comment.