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
size
s anddiskimage
s 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 firewallid
later, the syntax will becivo_firewall.www.id
.
- Set the firewall name to
- We are creating a new firewall using
- 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 the6443
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, settingaction = "deny"
will deny the traffic.
- Link it to the
- We are creating a new firewall rule using
- 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 tocivo_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 port6443
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.