How To Deploy Secure Linodes using Cloud Firewalls and Terraform
Traducciones al EspañolEstamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
Terraform modules allow you to better organize your configuration code and to distribute and reuse it. You can host your Terraform modules on remote version control services, like GitHub, for others to use. The Terraform Module Registry hosts community modules that you can reuse for your own Terraform configurations, or you can publish your own modules for consumption by the Terraform community.
In this guide, you will create a Linode Firewalls module which declares commonly used Cloud Firewall configurations. You will then use the module to create a Linode instance and assign the Linode to the Cloud Firewall. You can adopt the example configurations in this guide to create your own reusable Cloud Firewall configurations.
Linode Cloud Firewalls is a free service used to create, configure, and add stateful network-based firewalls to Linode services. A Cloud Firewall is independent of the service it is attached to, so you can apply a single Firewall to multiple Linode services. Linode Cloud Firewalls analyze traffic against a set of predefined rules at the network layer and determine if the traffic will be permitted to communicate with the Linode Service it secures. Cloud Firewalls work as an allowlist with an implicit deny rule– it will block all traffic by default and only pass through network traffic that meets the parameters of the configured rules.
A Cloud Firewall can be configured with Inbound and Outbound rules. Inbound rules limit incoming network connections to a Linode service based on the port(s) and sources you configure. Outbound rules limit the outgoing network connections coming from a Linode service based on the port(s) and destinations you configure.
Linode Cloud Firewalls is a free service used to create, configure, and add stateful network-based …
Before You Begin
If you are new to Terraform, read through our A Beginner’s Guide to Terraform guide to familiarize yourself with key concepts.
See Create a Terraform Module for a deeper dive into Terraform’s standard module structure and other helpful details.
You need a Linode API v4 personal access token to use with Terraform. This token will allow you to create, update, and destroy Linode resources. Follow the Getting Started with the Linode API guide for steps to create a token.
Note
When you create a personal access token ensure that you set Read/Write access permissions for Linode instances and Cloud Firewalls.Install Terraform on your local computer.
Note
This guide was written using Terraform version 0.13.0.Install Git on your computer and complete the steps in the Configure Git section of the Getting Started with Git guide.
Create Your Cloud Firewalls Module
The following steps will create the Cloud Firewalls module, which includes several child modules that split up the required resources between the root module, an inbound_ssh
module, a mysql
module, and a web-server
module. The root module is the directory that holds the Terraform configuration files that are applied to build your desired infrastructure. These files provide an entry point into any child modules. Each child module uses the linode_firewall
resource to create reusable Cloud Firewall rules for specific use cases.
NoteYou can apply up to three Cloud Firewalls per Linode instance.
NoteYou can view the files created throughout this tutorial in the author’s GitHub repository. You can clone the repository and use it as a foundation to create your own custom Cloud Firewalls module.
Create Your Module’s Directory Structure
In this section, you will create the directory structure outlined below, which will contain the module and child module configuration files that you will create in later steps.
main_firewalls/
├── main.tf
├── outputs.tf
├── secrets.tfvars
├── terraform
├── terraform.tfvars
├── variables.tf
└── modules/
    ├── inbound_ssh/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── mysql/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── web_server/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
Move into your
terraform
directory.cd ~/terraform
From your
terraform
directory, create the directory structure outlined above.mkdir -p main_firewalls/{inbound_ssh,mysql,web_server}
Note
If you followed our install Terraform steps, then your Terraform executable will be located in theterraform
directory. If this is not the case, ensure that you can execute Terraform commands from themain_firewalls
directory.
Create the Inbound SSH Child Module
When applied to a Terraform configuration, the inbound_ssh
module will create a Cloud Firewall with inbound rules to allow TCP
connections to port 22
from all sources. Port 22
is typically used for secure shell (SSH) connections, secure logins, file transfers (scp, sftp), and port forwarding.
Using your preferred text editor, create the
inbound_ssh
module’smain.tf
file. Copy and save the contents of the example below.- File: ~/main_firewalls/inbound_ssh/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
terraform { required_providers { linode = { source = "linode/linode" version = "1.16.0" } } } resource "linode_firewall" "ssh_inbound" { label = var.firewall_label tags = var.tags inbound { protocol = "TCP" ports = ["22"] addresses = ["0.0.0.0/0"] } linodes = var.linodes }
- This file uses the Terraform Linode Provider’s
linode_firewall
resource to create a Cloud Firewall with the inbound rules described above. - The
linodes
argument expects a list of Linode IDs. When a Linode ID is passed to thelinodes
argument, theinbound_ssh
firewall will be assigned to it. - The arguments
label
,tags
, andlinodes
make use of input variables, which allow these values to be customized when using the module for your resource configurations.
Create the
variables.tf
file to declare theinbound_ssh
module’s input variables. Copy and save the contents of the example below.- File: ~/main_firewalls/inbound_ssh/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
variable "linodes" { description = "List of Linode ids to which the rule sets will be applied" type = list(string) default = [] } variable "firewall_label" { description = "This firewall's human-readable firewall_label" type = string default = "my-firewall" } variable "tags" { description = "List of tags to apply to this Firewall" type = list(string) default = [] }
The input variables declared in this file correspond to the
linode_firewalls
resource arguments that theinbound_ssh
module exposes for customization. In a similar way, you can expose different arguments for your Cloud Firewall child modules as needed.
Create the MySQL Child Module
The mysql
child module creates a Cloud Firewall with an inbound rule commonly suited for client connections to a MySQL database server. The inbound rule allows TCP
connections to port 3306
. The addressses
argument accepts an input variable so that it can be customized to restrict access to a specific IP address(es) or CIDR block.
Using your preferred text editor, create the
inbound_ssh
module’smain.tf
file. Copy and save the contents of the example below.- File: ~/main_firewalls/mysql/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
terraform { required_providers { linode = { source = "linode/linode" version = "1.16.0" } } } resource "linode_firewall" "mysql" { label = var.firewall_label tags = var.tags inbound { protocol = "TCP" ports = ["3306"] addresses = var.addresses } linodes = var.linodes }
- This file uses the Terraform Linode Provider’s
linode_firewall
resource to create a Cloud Firewall with the inbound rules described above. - The
linodes
argument expects a list of Linode IDs. When a Linode ID is passed to thelinodes
argument, themysql
firewall will be assigned to it. - The arguments
label
,tags
,linodes
, andaddresses
make use of input variables, which allow these values to be customized when using the module for your resource configurations.
Create the
variables.tf
file to declare theinbound_ssh
module’s input variables. Copy and save the contents of the example below.- File: ~/main_firewalls/mysql/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
variable "linodes" { description = "List of Linode ids to which the rule sets will be applied" type = list(string) default = [] } variable "firewall_label" { description = "This firewall's human-readable firewall_label" type = string default = "my-firewall" } variable "tags" { description = "List of tags to apply to this Firewall" type = list(string) default = [] } variable "addresses" { description = "A list of IP addresses, CIDR blocks, or 0.0.0.0/0 (to allow all) this rule applies to." type = list(string) default = ["0.0.0.0/0"] }
The input variables declared in this file correspond to the
linode_firewalls
resource arguments that themysql
module exposes for customization.
Create the Web Server Child Module
The web_server
child module, when applied, creates a Cloud Firewall with inbound and outbound rules allowing incoming and outgoing connections from all sources and destinations to ports 80
and 443
over TCP
. These ports are commonly associated with
HTTP and
HTTPS, respectively.
Using your preferred text editor, create the
web_server
module’smain.tf
file. Copy and save the contents of the example below.- File: ~/main_firewalls/web_server/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
terraform { required_providers { linode = { source = "linode/linode" version = "1.16.0" } } } resource "linode_firewall" "web_server" { label = var.firewall_label tags = var.tags inbound { protocol = "TCP" ports = ["80"] addresses = ["0.0.0.0/0"] } outbound { protocol = "TCP" ports = ["80"] addresses = ["0.0.0.0/0"] } inbound { protocol = "TCP" ports = ["443"] addresses = ["0.0.0.0/0"] } outbound { protocol = "TCP" ports = ["443"] addresses = ["0.0.0.0/0"] } linodes = var.linodes }
- This file uses the Terraform Linode Provider’s
linode_firewall
resource to create a Cloud Firewall with the inbound and outbound rules described above. - The
linodes
argument expects a list of Linode IDs. When a Linode ID is passed to thelinodes
argument, theweb_server
firewall will be assigned to it. - The arguments
label
,tags
, andlinodes
make use of input variables, which allow these values to be customized when using the module for your resource configurations.
Create the
variables.tf
file to declare theweb_server
module’s input variables. Copy and save the contents of the example below.- File: ~/main_firewalls/web_server/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
variable "linodes" { description = "List of Linode ids to which the rule sets will be applied" type = list(string) default = [] } variable "firewall_label" { description = "This firewall's human-readable firewall_label" type = string default = "my-firewall" } variable "tags" { description = "List of tags to apply to this Firewall" type = list(string) default = [] }
The input variables declared in this file correspond to the
linode_firewalls
resource arguments that theweb_server
module exposes for customization.
Create the Root Module
Now that all the Cloud Firewalls child modules have been created, you can create your root module. The root module is in charge of defining the infrastructure to be built by Terraform. The root module has access to all the child modules and can make use of all or none of them. In this section, you will create a root module that can create a Cloud Firewall using the rules defined in the web_server
child module. It also creates two Linode instances and assigns the Cloud Firewall to both Linode instances.
Using your preferred text editor, create the root module’s
main.tf
file. Copy and save the contents of the example below.- File: ~/main_firewalls/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
terraform { required_providers { linode = { source = "linode/linode" version = "1.16.0" } } } provider "linode" { api_version = "v4beta" token = var.token } locals { key = var.key linode_ids = linode_instance.linode_base[*].id } module "firewalls_web" { source = "./modules/web_server" firewall_label = var.firewall_label_map["web"] tags = var.tags linodes = local.linode_ids } resource "linode_sshkey" "main_key" { label = var.key_label ssh_key = chomp(file(local.key)) } resource "linode_instance" "linode_base" { count = var.linode_count image = var.image label = "${var.label}_${count.index}" region = var.region type = var.type authorized_keys = [ linode_sshkey.main_key.ssh_key ] root_pass = var.root_pass }
The
provider
block is a requirement to use the Linode provider. Since Cloud Firewalls is currently in an open beta, you must use theapi_version
argument to tell Terraform to use Linode’s beta API v4 endpoints.The
locals
block declares a local variablekey
whose value will be provided by an input variable. Thelinode_ids
local variable is used by theweb_server
module instance in the next block to retrieve the Linode ids for the Linodes to be assigned to the Cloud Firewall that will be created.The
module "firewalls_web"
block creates an instance of theweb_server
child module, which when applied will create a new Cloud Firewall with the configurations provided by the child module and input variable values you will provide in a later step.The
source
argument provides the location of the child module’s source code and is required whenever you create an instance of a module.All other arguments are determined by the child module. Since the
web_server
child module exposes thefirewall_label
,tags
, andlinodes
, values must be provided for them. Input variables are used in the root module to make it reusable. Depending on the child module that you are using, and the label you’d like to assign to the Cloud Firewall, you should replace the key value for thevar.firewall_label_map["web"]
. Refer to thevariables.tf
file for details.The
linodes
argument retrieves its value from the local variable defined in the previous block.The
linode_sshkey
resource will create Linode SSH Keys tied to your Linode account. These keys can be reused for future Linode deployments once the resource has been created.ssh_key = chomp(file(local.key))
uses Terraform’s built-in functionfile()
to provide a local file path to your public SSH key’s location. The location of the file path is the value of the local variablekey
. Thechomp()
built-in function removes trailing new lines from the SSH key.The
linode_instance
resource creates two Linode instances with configurations provided by its arguments.The
count
argument controls how many Linode instances will be created with the configurations provided in the resource block’s arguments.Since Linode labels must be unique, the
label
argument will create a label based on a value provided to thevar.label
input variable and the index number representing the Linode instance that is created.The
authorized_keys
argument uses the SSH public key provided by thelinode_sshkey
resource in the previous resource block.
Create the
variables.tf
file to declare the root module’s input variables. These input variables are a combination of the all the values required by the various resources used in themain.tf
file. You can update the default values to your own preferences.- File: ~/main_firewalls/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
variable "token" { description = " Linode API token" } variable "key" { description = "Public SSH Key's path." } variable "key_label" { description = "New SSH key label." } variable "linode_count" { description = "The number of Linode instances to deploy." type = number default = 1 } variable "image" { description = "Image to use for Linode instance." default = "linode/ubuntu18.04" } variable "label" { description = "The Linode's label is for display purposes only, but must be unique." default = "default-linode" } variable "region" { description = "The region where your Linode will be located." default = "us-east" } variable "type" { description = "Your Linode's plan type." default = "g6-standard-1" } variable "root_pass" { description = "Your Linode's root user's password." } variable "linodes" { description = "List of Linode ids to which the rule sets will be applied" type = list(string) default = [] } variable "firewall_label_map" { type = "map" default = { "web" = "firewall_web_server" "mysql" = "firewall_mysql" "ssh" = "firewall_ssh" } } variable "tags" { description = "List of tags to apply to this Firewall" type = list(string) default = [] }
The variable declaration for
firewall_label_map
, by default, creates a map with default keysweb
,mysql
, andssh
. You can use these keys to provide the map’s default values to thefirewall_label
argument. Alternatively, you can override the default values in theterraform.tfvars
file that you will create in a later step.Create the
outputs.tf
file. This file exposes the IDs of the Linode instances that are created by thelinode_instance
resource block and will be printed to your console when the root module’s configurations are applied.- File: ~/main_firewalls/output.tf
1 2 3 4
output "linode_id" { value = linode_instance.linode_base[*].id }
Create the
terraform.tfvars
file to provide values for all input variables defined in thevariables.tf
file. This file will exclude any values that provide sensitive data, like passwords and API tokens. A file containing sensitive values will be created in the next step. You can replace any of these values with your own.- File: ~/main_firewalls/terraform.tfvars
1 2 3 4 5 6 7 8 9
key = "~/.ssh/id_rsa.pub" linode_count = 3 key_label = "my-ssh-key" label = "linode" tags = ["my-example-tag"] firewall_label_map = { "web" = "firewall_webserver_http_https" }
Create a file named
secrets.tfvars
to store any sensitive values. Replace the example values with your own.- File: ~/main_firewalls/secrets.tfvars
1 2 3
token = "my-api-v4-token" root_pass = "my-super-strong-root-password"
Note
This file should never be tracked in version control software and should be listed in your.gitignore
file if using GitHub.
You are now ready to apply your main_firewalls
module’s Terraform configuration. These steps will be completed in the next section.
Initialize, Plan and Apply the Terraform Configuration
Whenever a new provider is used in a Terraform configuration, it must first be initialized. The initialization process downloads and installs the provider’s plugin and performs any other steps needed for its use. Before applying your configuration, it is also useful to view your configuration’s execution plan before making any actual changes to your infrastructure. In this section, you will complete all these steps.
Initialize the Linode provider. Ensure you are in the
linode_stackscripts
directory before running this command:terraform init
You will see a message that confirms that the provider plugins have been successfully initialized.
Run the Terraform plan command:
terraform plan -var-file="secrets.tfvars" -var-file="terraform.tfvars"
Terraform plan won’t take any action or make any changes on your Linode account. Instead, an analysis is done to determine which actions (i.e. Linode instance creations, deletions, or modifications) are required to achieve the state described in your configuration.
You are now ready to create the infrastructure defined in your root module’s
main.tf
configuration file:terraform apply -var-file="secrets.tfvars" -var-file="terraform.tfvars"
Since you are using multiple variable value files, you must call each file individually using the
var-file
argument. You will be prompted to confirm theapply
action. Type yes and hit enter. Terraform will begin to create the resources you’ve defined throughout this guide. This process will take a couple of minutes to complete. Once the infrastructure has been successfully built you will see a similar output:Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
You can verify that your Cloud Firewalls have been created and applied to your new Linode instances by logging into the Linode Cloud Manager and navigating to the Firewalls section of the manager.
Next Steps
To learn how to
version control the main-firewalls
module that you created in this guide, see the
Create a Terraform Module guide.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
This page was originally published on