Firewall is a network security system that is able to block or allow inbound/outbound network traffic to a device in the network. At Civo, we also have firewall product for our customers.

This firewall can be launched using Civo.com, Civo API, Civo CLI, as well as Civo Terraform provider.

In this guide, we will explore how to create firewall and firewall rules — and link it to Civo compute instance using Civo Terraform provider.

Requirements

This guide assumes that you:

  • Have a Civo account
  • Have a Terraform project initialized (refer this doc for more details)
  • Using Civo Terraform provider version 1.0.16 or later
  • Have gone through the Launch a Civo compute instance using Terraform guide
  • Have basic understanding of Linux commands and how to use them in terminal

Step #1 - Add region to provider

Since the region field is optional in most of Civo Terraform provider's resources and data sources (if no region is provided in the configuration or provider.tf file, the system will choose one for you), it's a good idea to just declare it once at the provider level.

The benefits of declaring region at provider level are:

  • We don't have to repeat it all over the place in our configuration file(s)
  • Terraform will ensure all API calls for data sources and resources communicate with a consistent region

To do so, simply update your provider.tf to include region field. Example:

// code omitted for brevity

provider "civo" {
  token = "<YOUR_CIVO_API_KEY>"
    region = "LON1"
}

Step #2 - Prepare configuration file

Let's first create a file named main.tf:

# Change directory to Terraform project
$ cd ~/civo

# Create the file
$ touch main.tf

Using your favourite editor (e.g. nano or VS Code), add the following code inside that file and save it:

# Query small instance size
data "civo_size" "small" {
    filter {
        key = "name"
        values = ["g3.small"]
        match_by = "re"
    }

    filter {
        key = "type"
        values = ["instance"]
    }

}

# Query instance disk image
data "civo_disk_image" "debian" {
   filter {
        key = "name"
        values = ["debian-10"]
   }
}

# Create a firewall
resource "civo_firewall" "www" {
    name = "www"
}

# Create a firewall rule
resource "civo_firewall_rule" "kubernetes" {
    firewall_id = civo_firewall.www.id
    protocol = "tcp"
    start_port = "6443"
    end_port = "6443"
    cidr = ["0.0.0.0/0"]
    direction = "ingress"
    label = "kubernetes-api-server"
    action = "allow"
}

# Create a compute instance
resource "civo_instance" "foo" {
    hostname = "foo.com"
    firewall_id = civo_firewall.www.id
    size = element(data.civo_size.small.sizes, 0).name
    disk_image = element(data.civo_disk_image.debian.diskimages, 0).id
}

So what happen when we apply the configuration above?

  • In the Query small instance size and Query instance disk image block, we are querying for list of instance sizes and diskimages to be used when creating Civo compute instance later. If you need more details about these blocks, checkout this doc.
  • In the Create a firewall block:
    • We are creating a new firewall using civo_firewall resource and
      • Set the firewall name to www
      • We then can refer to this firewall as civo_firewall.www. For example, if we need to use this firewall id later, the syntax will be civo_firewall.www.id.
  • In the Create a firewall rule block:
    • We are creating a new firewall rule using civo_firewall_rule resource and
      • Link it to the www firewall above
      • Set the protocol to tcp
      • Set both start and end port to 6443 (a common port for HTTP)
      • Set CIDR notation to 0.0.0.0/0 to open the 6443 port to everyone
      • Set the direction to ingress so this rule will effective for incoming traffic (from outside world to our compute instance)
      • Set the label to web-server for our reference
      • We then can refer to this firewall rule as civo_firewall_rule.http later (if we need to)
      • When we set the action = "allow", this is going to add a rule to allow traffic. Similarly, setting action = "deny" will deny the traffic.
  • In the Create a compute instance block, it's pretty much like what we have done in this doc. The only new field here is firewall_id, which is pointed to civo_firewall.www.id. Meaning, when the compute instance is created, it will not use default firewall. Instead, it will use the firewall we created from this Terraform configuration file.

The civo_firewall and civo_firewall_rule resources support other fields too. Checkout the docs for more details.

Step #3 - Plan

Now, you can run terraform plan command to see what's going to be created.

$ terraform plan
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:

  # civo_firewall.www will be created
  + resource "civo_firewall" "www" {
      + id         = (known after apply)
      + name       = "www"
      + network_id = (known after apply)
    }

  # civo_firewall_rule.http will be created
  + resource "civo_firewall_rule" "http" {
      + cidr        = [
          + "0.0.0.0/0",
        ]
      + direction   = "ingress"
      + end_port    = "6443"
      + firewall_id = (known after apply)
      + id          = (known after apply)
      + label       = "web-server"
      + protocol    = "tcp"
      + region      = (known after apply)
      + start_port  = "6443"
    }

  # civo_instance.foo will be created
  + resource "civo_instance" "foo" {
      + cpu_cores          = (known after apply)
      + created_at         = (known after apply)
      + disk_gb            = (known after apply)
      + disk_image         = "4204229c-510c-4ba4-ab07-522e2aaa2cf8"
      + firewall_id        = (known after apply)
      + hostname           = "foo.com"
      + id                 = (known after apply)
      + initial_password   = (sensitive value)
      + initial_user       = "civo"
      + network_id         = (known after apply)
      + private_ip         = (known after apply)
      + pseudo_ip          = (known after apply)
      + public_ip          = (known after apply)
      + public_ip_required = "create"
      + ram_mb             = (known after apply)
      + size               = "g3.small"
      + source_id          = (known after apply)
      + source_type        = (known after apply)
      + status             = (known after apply)
      + template           = (known after apply)
    }

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

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

As you can see from the output above, what will happen later when we apply our configuration file is:

  • It will create a firewall named www
  • Inside that www firewall, it will create a firewall rule to open port 6443 to outside world
  • It will create a compute instance and set its firewall to the www firewall

Step #4 - Apply

It's now time to create the actual firewall, firewall rule and compute instance. Let's run terraform apply command. When it asks for confirmation, type yes and hit Enter key.

$ terraform apply
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:

  # civo_firewall.www will be created
  + resource "civo_firewall" "www" {
      + id         = (known after apply)
      + name       = "www"
      + network_id = (known after apply)
    }

  # civo_firewall_rule.http will be created
  + resource "civo_firewall_rule" "http" {
      + cidr        = [
          + "0.0.0.0/0",
        ]
      + direction   = "ingress"
      + end_port    = "6443"
      + firewall_id = (known after apply)
      + id          = (known after apply)
      + label       = "web-server"
      + protocol    = "tcp"
      + region      = (known after apply)
      + start_port  = "6443"
    }

  # civo_instance.foo will be created
  + resource "civo_instance" "foo" {
      + cpu_cores          = (known after apply)
      + created_at         = (known after apply)
      + disk_gb            = (known after apply)
      + disk_image         = "a4204155-a876-43fa-b4d6-ea2af8774560"
      + firewall_id        = (known after apply)
      + hostname           = "foo.com"
      + id                 = (known after apply)
      + initial_password   = (sensitive value)
      + initial_user       = "civo"
      + network_id         = (known after apply)
      + private_ip         = (known after apply)
      + pseudo_ip          = (known after apply)
      + public_ip          = (known after apply)
      + public_ip_required = "create"
      + ram_mb             = (known after apply)
      + size               = "g3.small"
      + source_id          = (known after apply)
      + source_type        = (known after apply)
      + status             = (known after apply)
      + template           = (known after apply)
    }

Plan: 3 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

civo_firewall.www: Creating...
civo_firewall.www: Creation complete after 3s [id=d58c8430-c4ee-469b-a390-0c8cd446de5e]
civo_firewall_rule.http: Creating...
civo_instance.foo: Creating...
civo_firewall_rule.http: Creation complete after 2s [id=922679f3-2044-44e9-af9d-d7a0866e076d]
civo_instance.foo: Still creating... [10s elapsed]
civo_instance.foo: Still creating... [20s elapsed]
civo_instance.foo: Still creating... [30s elapsed]
civo_instance.foo: Still creating... [40s elapsed]
civo_instance.foo: Still creating... [50s elapsed]
civo_instance.foo: Creation complete after 55s [id=e1bad596-be02-4ad4-b07a-108ed4b8f57e]

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

When the creation completes, refresh your Civo web UI and you will see there's new compute instance just get created. Click it to see more details.

If you refresh Firewalls page, you will see the new www firewall:

And if you click Actions > Rules, you will see the new rule for port 6443:

Now, if you change the action = "deny" under resource "civo_firewall_rule" "kubernetes" {, it is going to reflect it in the UI as well after you apply your changes:

If you notice, there will be a new file named terraform.tfstate get created for you in your local project directory. And, if you print its content, it will look like:

$ cat terraform.tfstate
{
  "version": 4,
  "terraform_version": "1.0.6",
  "serial": 5,
  "lineage": "9846ee7a-c5b2-cf16-a705-3f57ffc38fc9",
  "outputs": {},
  "resources": [
    {
      "mode": "data",
      "type": "civo_disk_image",
      "name": "debian",
      "provider": "provider[\"registry.terraform.io/civo/civo\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "diskimages": [
              {
                "id": "a4204155-a876-43fa-b4d6-ea2af8774560",
                "label": "",
                "name": "debian-10",
                "version": "10"
              }
            ],
            "filter": [
              {
                "all": false,
                "key": "name",
                "match_by": "exact",
                "values": [
                  "debian-10"
                ]
              }
            ],
            "id": "terraform-20210920044137658700000002",
            "region": null,
            "sort": null
          },
          "sensitive_attributes": []
        }
      ]
    },
    {
      "mode": "data",
      "type": "civo_instances_size",
      "name": "small",
      "provider": "provider[\"registry.terraform.io/civo/civo\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "filter": [
              {
                "all": false,
                "key": "name",
                "match_by": "re",
                "values": [
                  "g3.small"
                ]
              },
              {
                "all": false,
                "key": "type",
                "match_by": "exact",
                "values": [
                  "instance"
                ]
              }
            ],
            "id": "terraform-20210920044137612100000001",
            "sizes": [
              {
                "cpu": 1,
                "description": "Small",
                "disk": 25,
                "name": "g3.small",
                "ram": 2048,
                "selectable": true,
                "type": "instance"
              }
            ],
            "sort": null
          },
          "sensitive_attributes": []
        }
      ]
    },
    {
      "mode": "managed",
      "type": "civo_firewall",
      "name": "www",
      "provider": "provider[\"registry.terraform.io/civo/civo\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "d58c8430-c4ee-469b-a390-0c8cd446de5e",
            "name": "www",
            "network_id": "5c16ab17-933a-46ed-96c6-8a093a0179e1",
            "region": null
          },
          "sensitive_attributes": [],
          "private": "bnVsbA=="
        }
      ]
    },
    {
      "mode": "managed",
      "type": "civo_firewall_rule",
      "name": "http",
      "provider": "provider[\"registry.terraform.io/civo/civo\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "cidr": [
              "0.0.0.0/0"
            ],
            "direction": "ingress",
            "end_port": "6443",
            "firewall_id": "d58c8430-c4ee-469b-a390-0c8cd446de5e",
            "id": "922679f3-2044-44e9-af9d-d7a0866e076d",
            "label": "web-server",
            "protocol": "tcp",
            "region": null,
            "start_port": "6443"
          },
          "sensitive_attributes": [],
          "private": "bnVsbA==",
          "dependencies": [
            "civo_firewall.www"
          ]
        }
      ]
    },
    {
      "mode": "managed",
      "type": "civo_instance",
      "name": "foo",
      "provider": "provider[\"registry.terraform.io/civo/civo\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "cpu_cores": 1,
            "created_at": "0001-01-01 00:00:00 +0000 UTC",
            "disk_gb": 25,
            "disk_image": "a4204155-a876-43fa-b4d6-ea2af8774560",
            "firewall_id": "d58c8430-c4ee-469b-a390-0c8cd446de5e",
            "hostname": "foo.com",
            "id": "e1bad596-be02-4ad4-b07a-108ed4b8f57e",
            "initial_password": "1eRj0Bn5w7uP",
            "initial_user": "civo",
            "network_id": "5c16ab17-933a-46ed-96c6-8a093a0179e1",
            "notes": "",
            "private_ip": "192.168.1.6",
            "pseudo_ip": null,
            "public_ip": "74.220.17.173",
            "public_ip_required": "create",
            "ram_mb": 2048,
            "region": null,
            "reverse_dns": "",
            "script": "",
            "size": "g3.small",
            "source_id": "debian-10",
            "source_type": "diskimage",
            "sshkey_id": "",
            "status": "ACTIVE",
            "tags": null,
            "template": null
          },
          "sensitive_attributes": [],
          "private": "bnVsbA==",
          "dependencies": [
            "civo_firewall.www",
            "data.civo_disk_image.debian",
            "data.civo_instances_size.small"
          ]
        }
      ]
    }
  ]
}

That's the Terraform state file that was created after you created the resources just now.

When you update your main.tf file and run terraform apply again, Terraform will refresh the state file, try to understand what you want to update and update your resources.

If there's no change in your main.tf file and you rerun terraform apply, it will output No changes. Your infrastructure matches the configuration. message back to you.

***

So now you know how to create Civo firewall using Terraform. Good job! Let's move on to the next guide to see what else can be done using Civo Terraform provider.