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. For more information on Cloud Firewalls, see the Cloud Firewalls documentation.
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 personal access token to use with Terraform. This token will allow you to create, update, and destroy Linode resources. See the Manage Personal Access Tokens 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.
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 - terraformdirectory.- cd ~/terraform
- From your - terraformdirectory, create the directory structure outlined above.- mkdir -p main_firewalls/modules/{inbound_ssh,mysql,web_server}- Note - If you followed our [install Terraform](/docs/guides/how-to-build-your-infrastructure-using-terraform-and-linode/#install-terraform) steps, then your Terraform executable will be located in the `terraform` directory. If this is not the case, ensure that you can execute Terraform commands from the `main_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_sshmodule’s- main.tffile. 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- 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_firewallresource to create a Cloud Firewall with the inbound rules described above.
- The linodesargument expects a list of Linode IDs. When a Linode ID is passed to thelinodesargument, theinbound_sshfirewall will be assigned to it.
- The arguments label,tags, andlinodesmake use of input variables, which allow these values to be customized when using the module for your resource configurations.
 
- Create the - variables.tffile to declare the- inbound_sshmodule’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- 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_firewallsresource arguments that the- inbound_sshmodule 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_sshmodule’s- main.tffile. 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- 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_firewallresource to create a Cloud Firewall with the inbound rules described above.
- The linodesargument expects a list of Linode IDs. When a Linode ID is passed to thelinodesargument, themysqlfirewall will be assigned to it.
- The arguments label,tags,linodes, andaddressesmake use of input variables, which allow these values to be customized when using the module for your resource configurations.
 
- Create the - variables.tffile to declare the- inbound_sshmodule’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- 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_firewallsresource arguments that the- mysqlmodule 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_servermodule’s- main.tffile. 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- 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_firewallresource to create a Cloud Firewall with the inbound and outbound rules described above.
- The - linodesargument expects a list of Linode IDs. When a Linode ID is passed to the- linodesargument, the- web_serverfirewall will be assigned to it.
- The arguments - label,- tags, and- linodesmake use of input variables, which allow these values to be customized when using the module for your resource configurations.
 
- Create the - variables.tffile to declare the- web_servermodule’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- 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_firewallsresource arguments that the- web_servermodule 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.tffile. 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- 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 - providerblock is a requirement to use the Linode provider. Since Cloud Firewalls is currently in an open beta, you must use the- api_versionargument to tell Terraform to use Linode’s beta API v4 endpoints.
- The - localsblock declares a local variable- keywhose value will be provided by an input variable. The- linode_idslocal variable is used by the- web_servermodule 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 the- web_serverchild 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 - sourceargument 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_serverchild module exposes the- firewall_label,- tags, and- linodes, 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 the- var.firewall_label_map["web"]. Refer to the- variables.tffile for details.
- The - linodesargument retrieves its value from the local variable defined in the previous block.
- The - linode_sshkeyresource 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 function- file()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 variable- key. The- chomp()built-in function removes trailing new lines from the SSH key.
- The - linode_instanceresource creates two Linode instances with configurations provided by its arguments.
- The - countargument 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 - labelargument will create a label based on a value provided to the- var.labelinput variable and the index number representing the Linode instance that is created.
- The - authorized_keysargument uses the SSH public key provided by the- linode_sshkeyresource in the previous resource block.
 
- Create the - variables.tffile 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 the- main.tffile. 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- 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 keys- web,- mysql, and- ssh. You can use these keys to provide the map’s default values to the- firewall_labelargument. Alternatively, you can override the default values in the- terraform.tfvarsfile that you will create in a later step.
- Create the - outputs.tffile. This file exposes the IDs of the Linode instances that are created by the- linode_instanceresource block and will be printed to your console when the root module’s configurations are applied.- File: ~/main_firewalls/output.tf
- 1 2 3- output "linode_id" { value = linode_instance.linode_base[*].id }
 
- Create the - terraform.tfvarsfile to provide values for all input variables defined in the- variables.tffile. 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- 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.tfvarsto store any sensitive values. Replace the example values with your own.- File: ~/main_firewalls/secrets.tfvars
- 1 2- 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- .gitignorefile 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_stackscriptsdirectory 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.tfconfiguration 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-fileargument. You will be prompted to confirm the- applyaction. 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