Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add fortigate node #1917

Merged
merged 13 commits into from
Mar 13, 2024
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"]
Loading