Skip to main content

Managing Terraform Secrets when using Equinix Metal

Learn how to carefully manage your Terraform secrets when using the Equinix Metal provider.

Managing Terraform Secrets when using Equinix Metal

This guide will walkthrough how to manage secrets in Terraform scripts while using the Equinix Metal provider. Terraform authenticates with Equinix to deploy resources and perform the planned actions. But to run those scripts Terraform will need some sensitive information or secrets to gain access to the services. For example, an API key would be considered a secret because that is sensitive information.

Prerequisites

Secrets in plain text are a bad idea

Before using environmental variables, you could hardcode your sensitive information into your script file in plain text, but this is problematic because anyone who gains access to this file will be able to see your sensitive information, like your API keys! Below is an example of a script with a hardcoded API key.

Getting rid of that auth_token = "someEquinixMetalApiKey" line would be a great idea!

terraform {
  required_providers {
    equinix = {
      source  = "equinix/equinix"
      version = "1.13.0"
    }
  }
}

provider "equinix" {
  auth_token = "someEquinixMetalApiKey"
}

resource "equinix_metal_device" "web1" {
  hostname = "example"
  plan = "c3.small.x86"
  metro = "da"
  operating_system = "ubuntu_20_04"
  billing_cycle = "hourly"
  project_id = "someProjectId"
}

This is not ideal for a variety of reasons. We wouldn't want someone to take that API key of ours and start spinning up that drive up cost or deleting servers that we're using to host applications.

We need a way to handle this sensitive information carefully!

Use Enviormental Variables

One way to handle this sensitive information carefully would be to leverage Environment Variables. It's very common for Terraform providers (and other SDKs and Clients) to allow users to set API keys and other sensitive information by leveraging environment variables. Let's see what the Equinix Terraform Providers docs say:

variable description
client_id API Consumer Key available under "My Apps" in developer portal. This argument can also be specified with the EQUINIX_API_CLIENTID shell environment variable.
client_secret API Consumer secret available under "My Apps" in developer portal. This argument can also be specified with the EQUINIX_API_CLIENTSECRET shell environment variable.
token API tokens are generated from API Consumer clients using the OAuth2 API. This argument can also be specified with the EQUINIX_API_TOKEN shell environment variable.
auth_token This is your Equinix Metal API Auth token. This can also be specified with the METAL_AUTH_TOKEN environment variable.

Perfect! We can set the auth_token information in an METAL_AUTH_TOKEN environment variable and remove auth_token = "someEquinixMetalApiKey" from the provider block. It should now looks like this:

provider "equinix" {
}

To create the environmental variable by run the export command in a terminal and replace someEquinixMetalToken with your API key.

export METAL_AUTH_TOKEN=someEquinixMetalToken

We no longer have to owrry about our API key being stored in plain text!

Use a variables.tf file

Now, let's say you also want to remove your project ID from plain text as well. Another way to reduce sensitive information showing up would be to define input variables in a file. First create a new file named variables.tf then create a new input variable block named project_id and give it some values. Below is an early example I created:

variable "project_id" {
  description = "Your project UUID inside the Metal Console or from the Metal CLI"
  type        = string
  sensitive   = true
  # default     = ''
}

Let's see what the sensitive flag does, according to the documentation.

Because you flagged the new variables as sensitive, Terraform redacts their values from its output when you run a plan, apply, or destroy command. ... If you were to run terraform apply now, Terraform would prompt you for values for these new variables since you haven't assigned defaults to them.

After creating your new variable.tf file and adding the variables you would like to use. Go back into your Terraform script file and locate project_id = "someProjectId" inside your metal device resource block and replace it with project_id = var.project_id as so.

(Tip: whenever you create a new variable block in variables.tf to reference it in another place it will be var.nameofvariable)

resource "equinix_metal_device" "web1" {
  hostname = "example"
  plan = "c3.small.x86"
  metro = "da"
  operating_system = "ubuntu_20_04"
  billing_cycle = "hourly"
  project_id = var.project_id
}

As expected, when you run terraform apply it will prompt you to give the variable an input in the terminal before running the script. One way to work around this would be to create another environment variable.

From a terminal set the environment variable TF_VAR_project_id and replace someProjectId with your actual project ID. This will give the new sensitive variable you created a value to reference and no longer prompt you to input the value.

export TF_VAR_project_id=someProjectId

Use an inputs.tfvars file

But, what if you have a lot of input variable and you don't want to export each one individually or wait for the terminal to prompt you to add them when you run terraform apply. Another option is to create a file known as a .tfvars file.

In this example we will name it inputs.tfvars and inside this file it will hold the values for the variables created earlier in the variables.tf file. We'll also be using all variables for a metal device resource block in main.tf replacing all previous hardcoded information. It would look like this:

terraform {
  required_providers {
    equinix = {
      source  = "equinix/equinix"
      version = "1.13.0"
    }
  }
}

provider "equinix" {
}

resource "equinix_metal_device" "web1" {
  hostname = var.hostname
  plan  = var.plan
  metro = var.metro
  operating_system = var.os
  billing_cycle = var.billing
  project_id = var.project_id
}

In variables.tf it would looke like this:

variable "hostname" {
  description = "The name you want to give the host/metal device "
  type        = string
  #default    = ''
}

variable "plan" {
  description = "The type of plan you want to deploy "
  type        = string
  #default    = ''
}

variable "metro" {
  description = "The metro you want to deploy in"
  type        = string
  #default    = ''
}

variable "os" {
  description = "The operating system you want to be put on the machine"
  type        = string
  #default    = ''
}

variable "billing" {
  description = "The billing cycle you want for the machine"
  type        = string
  #default    = ''
}

variable "project_id" {
  description = "Your project UUID inside the Metal Console or from the Metal CLI"
  type        = string
  sensitive   = true
  #default    = ''
}

Then in our inputs.tfvars file we could give those variables a value. It would look like this:

hostname = "test"
plan = "c3.small.x86"
metro = "da"
os = "ubuntu_20_04"
billing = "hourly"

Notice we don't list project_id because we already exported it as an environment variable earlier. Terraform will automatically look for variables in this order:

  • Environment variables
  • The terraform.tfvars file, if present.
  • The terraform.tfvars.json file, if present.
  • Any *.auto.tfvars or *.auto.tfvars.json files, processed in lexical order of their filenames.
  • Any -var and -var-file options on the command line, in the order they are provided. (This includes variables set by - a Terraform Cloud workspace.)

After creating these new files and variables you should have a folder structure that looks like this:

❯ terraform-project-folder
.
└── main.tf
└── variables.tf
└── inputs.tfvars

You can now apply the changes to your plan by running terraform plan -var-file="inputs.tfvars" and it will show you the resources in your plan with the sensitive variables being shown as redacted and the other ones as normal and if everything looks correct you can run terraform apply -var-file="inputs.tfvars" then you will be prompted to input yes if you would like for the plan to be executed and to begin deployment.

terraform plan -var-file="inputs.tfvars"
terraform apply -var-file="inputs.tfvars"

Now to destroy the plan you will run terraform destroy -var-file="inputs.tfvars" and say yes to running the command when prompted.

terraform destroy -var-file="inputs.tfvars"

You can now hide secrets in environmental variables and store less sensitive information as input variables by using variables.tf and .tfvars files.

Configure a Remote Backend

Although the secrets are hidden in your files and show up as redacted when you run terraform apply they can still potentially be stored in plain text in Terraform's state file. That is because whenever you run terraform apply it creates a state file that keeps track of resources created by your configuration and maps them to real-world resources. By default, the file will be stored locally inside the directory of the Terraform project.

To secure this file and your secrets you can configure a remote backend. That is done by creating a new block inside your main.tf with the credentials needed so that the state file will be stored somewhere else and or encrypted. Now if you are working alone or just testing something this may not be necessary, but if you are in a team or working on a big project it is highly recommended. Here is Terraform's Official Developer Docs on how to do that, it will not only show examples on how to setup a remote backend but also list all the available options for backends on Terraform.

Conclusion

Thanks for following along with this guide! This guide showcased how to carefully use secrets when utilizing Terraform to deploy Equinix Metal servers and things to think about when it comes to managing secrets. Specifically, we covered:

  • Creating enviormental Variables as a way to encrypt sensitive information in your files.
  • How to utilize input variables and .tfvars files to store variable values instead of hardcoding them.
  • Mentioned the option of using a remote backend to store state files in different locations with better security.

Last updated

13 September, 2024

Category

Tagged

Technical
Subscribe to our newsletter

A monthly digest of the latest news, articles, and resources.