Elevating IaC Workflows with Spacelift Stacks and Dependencies 🛠️

Register for the July 23 demo →

Terraform

12 Terraform Security Best Practices (& 7 Common Risks)

terraform security best practices

In this article, we look at some security best practices you should implement in your Terraform projects. We will describe why Terraform security is an issue with Terraform configurations and the risks you face when using it before recommending some Terraform security best practices you should follow to mitigate these potential security risks.

What we will cover:

  1. What is Terraform security?
  2. Terraform security risks 
  3. Terraform security best practices

What is Terraform security?

Terraform security is a set of practices and rules used to ensure the security of your infrastructure defined with Terraform. Since Terraform automates infrastructure provisioning and management, security becomes especially crucial to prevent misconfigurations or unauthorized deployments. Proper management of state files, secure storage of secrets, role-based access controls, and code scanning are some key aspects of maintaining a secure Terraform environment.

Terraform security risks

Terraform is a powerful tool for infrastructure automation. However, if not used cautiously, it introduces its own set of security risks.

Below are the seven common security risks associated with deploying your infrastructure with Terraform.

1. Misconfigurations

As Terraform automates infrastructure provisioning based on your code, any errors or misconfigurations in your Terraform files can lead to security risks and vulnerabilities in your deployed cloud infrastructure. This could involve accidentally exposing sensitive resources to the public internet, granting excessive permissions to users or processes, or deploying insecure configurations.

2. Credential/secret exposure

Terraform configurations often require sensitive information like API keys, passwords, or access tokens to interact with cloud providers or other services. Storing these secrets directly within your Terraform code files is a security risk. If these files are compromised, attackers could gain access to your infrastructure.

3. Insecure backend storage

By default, Terraform uses local state files (terraform.tfstate) to store information about your infrastructure and provisioned resources. However, these local files are less secure and can be accidentally accessed by unauthorized users on your machine.

4. Insufficient access management

Terraform configurations and state files need to be secured with appropriate access controls. Without proper access management, unauthorized users could potentially modify or delete your infrastructure or steal sensitive information from the state file. 

In a multi-user environment, if Terraform is executed with elevated privileges (e.g., using root access), there is a risk of privilege escalation. Malicious actors could abuse these elevated privileges to make unauthorized changes to the infrastructure or access sensitive data.

5. Insider threats

Malicious actors with access to Terraform configurations or the Terraform state file could cause significant damage. This could involve deploying unauthorized infrastructure, modifying existing infrastructure for malicious purposes, or stealing sensitive information. 

For example, attackers could create providers with names similar to legitimate ones, tricking developers into using the malicious provider. In addition, if Terraform provider versions are not pinned, a malicious actor with access to the provider release process could override a legitimate version with a malicious one without being noticed.

6. Third-party risks

Terraform allows the use of third-party Terraform modules from sources like the Terraform Registry or GitHub. While convenient, using third-party modules introduces the risk of including insecure or malicious code, which could be triggered during the provisioning or management of infrastructure resources, affecting the security of the infrastructure.

7. Dependency vulnerabilities

Terraform relies on various dependencies, including Terraform itself, providers, and plugins. These dependencies may have vulnerabilities that could be exploited by attackers to compromise the integrity and security of the infrastructure.

Terraform security best practices

1. Secure state management

The first step to a secure Terraform configuration is to store Terraform state files securely in a remote backend with proper access controls and encryption enabled. Do not hold them on your local machine. Popular backends include AWS S3, Azure Blob Storage, or HashiCorp Consul.

The example below shows how to configure the backend using AWS S3 with optional flags to enable encryption. For further security, be sure to Enable Bucket Versioning so the Terraform state file versions can be tracked and restored if they are accidentally deleted.

# Configure the S3 Backend with Access Controls and Encryption

backend "s3" {
  bucket = "your-s3-bucket-name"
  key    = "terraform.tfstate"

  # Enable Server-Side Encryption with AWS Key Management Service (KMS)
  kms_key_id = "arn:aws:kms:<region>:<account-id>:alias/alias/terraform-state-key"

  # Optional: Configure Encrypted Transfer (HTTPS)
  encrypt = true
}

# Configure AWS Provider
provider "aws" {
  region = "your-aws-region"
}

# Your Terraform resources here...

You can also use platforms like Spacelift to manage the Terraform state for you. Spacelift offers a backend synchronized with the rest of the platform to maximize convenience and security. You can also import your state during stack creation, which is very useful for engineers who are migrating their old configurations and states to Spacelift.

terraform secure backend spacelift

Your Terraform state would also be protected against accidental or malicious access, as Spacelift can map state access and changes to legitimate Spacelift runs, automatically blocking all unauthorized traffic.

2. Detect vulnerabilities early

Utilize tools for static code analysis to detect security vulnerabilities in your Terraform code. These tools can help detect security issues like unintended resource exposure or misconfigurations early in the development process. Popular security tools include tfsec, Checkov, and Terrascan. These tools should also be regularly updated and configured to help you maintain a robust security posture.

Let’s see an example of using Terrascan on a Terraform code. The configuration below creates an Azure resource group:

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "example" {
  name     = "example-resources"
  location = "East US"
}

Run terrascan scan in the directory with the file above.

The Terrascan scan summary shows one policy violation and suggests that resource locks should be enabled on the resource group with a LOW severity.

3. Store secrets securely

Parameterize your Terraform modules to avoid hardcoding sensitive information such as credentials or IP addresses directly into your configuration files. Instead, pass them as variables or retrieve them from secure storage solutions like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault. You could also set sensitive values as environment variables on your system or CI/CD pipeline. In general, avoid storing secrets directly within your Terraform code.

If you are using a platform to manage your deployments, such as Terraform Cloud workspaces, Terraform Enterprise, or Spacelift, utilize features offered by them managing secrets within those platforms. 

You should also consider how you will implement credential rotation policies to regularly rotate access keys, passwords, and other sensitive credentials used by Terraform scripts. To mitigate the risk of credential compromise, automate the credential rotation process and avoid using long-lived credentials. That’s why Spacelift integrates with identity management systems from major cloud providers to dynamically generate short-lived access tokens that can be used to configure their corresponding Terraform providers.

The example below shows how you could reference a secret in an already existing Azure Key Vault and apply it to the creation of an Azure VM rather than hardcoding the password into the configuration file.

# Configure Azure Provider
provider "azurerm" {
 features {}
}

# Reference the Key Vault (assuming it already exists)
data "azurerm_key_vault" "example" {
 name = "mykeyvault"
 resource_group_name = "myresourcegroup"
}

# Retrieve the password secret from Key Vault
data "azurerm_key_vault_secret" "vm_password" {
 name         = "vm-password"
 key_vault_id = data.azurerm_key_vault.example.id
}

# Create the Virtual Machine
resource "azurerm_linux_virtual_machine" "example" {
 name                 = "myvm"
 location             = azurerm_resource_group.example.location
 resource_group_name  = azurerm_resource_group.example.name
 admin_username       = "username"
 admin_password       = data.azurerm_key_vault_secret.vm_password.value

# ... other VM configuration options ...
}

4. Use secure communication protocols

To avoid exposing sensitive information over unencrypted channels, ensure that Terraform communicates securely with cloud providers and other services by using HTTPS endpoints and TLS encryption. With HTTPS, data transmitted between Terraform and the cloud provider is encrypted, protecting it from eavesdropping and man-in-the-middle attacks.

5. Enforce code reviews

Implement a code review process for your Terraform configurations. This helps identify potential security vulnerabilities, misconfigurations, or logic errors before applying them to your infrastructure. Implement unit and integration tests (for example, with Terratest) to validate the Terraform code and catch any issues. 

Below is an example setup for an integration test using Terratest. The created function tests the creation of an Azure storage account in a resource group and a container in the storage account. It then verifies that the storage account and container exist. After the test is completed the resources will be cleaned up.

package test

import (
 "context"
 "fmt"
 "testing"
 "time"

 "github.com/Azure/azure-sdk-for-go/storage"
 "github.com/gruntwork-io/terratest/modules/azure"
 "github.com/gruntwork-io/terratest/modules/random"
 "github.com/gruntwork-io/terratest/modules/testing"
)

func TestAzureStorageAccount(t *testing.T) {
 t.Parallel()

 // Set the Azure subscription ID and resource group where the Storage Account will be created
 uniqueID := random.UniqueId()
 subscriptionID := "YOUR_AZURE_SUBSCRIPTION_ID"
 resourceGroupName := fmt.Sprintf("test-rg-%s", uniqueID)

 // Create the resource group
 azure.CreateResourceGroup(t, resourceGroupName, "uksouth")

 // Set the Storage Account name and create the account
 accountName := fmt.Sprintf("teststorage%s", uniqueID)
 accountType := storage.StandardZRS
 location := "uksouth"
 account := azure.CreateStorageAccount(t, subscriptionID, resourceGroupName, accountName, accountType, location)

 // Create a container in the Storage Account
 ctx := context.Background()
 client, err := storage.NewBasicClient(accountName, azure.GenerateAccountKey(t, accountName))
 if err != nil {
  t.Fatalf("Failed to create Storage Account client: %v", err)
 }
 service := client.GetBlobService()
 containerName := fmt.Sprintf("testcontainer%s", uniqueID)
 if _, err := service.CreateContainer(ctx, containerName, nil); err != nil {
  t.Fatalf("Failed to create container: %v", err)
 }

 // Check if the container exists
 testing.WaitForTerraform(ctx, terraformOptions, nil)

 // Cleanup resources
 defer azure.DeleteResourceGroup(t, resourceGroupName)
}

As a best practice, you should also always run terraform plan to preview changes before applying to ensure no unintended modifications. 

6. Enable audit logging

Enable audit logging for Terraform operations to track changes, detect unauthorized modifications, and investigate security incidents. Use logging solutions like AWS CloudTrail, Azure Monitor, or centralized logging platforms to monitor Terraform activities that can hook into your CI/CD platforms and pipelines. 

Changes to infrastructure resources managed by Terraform should be directly monitored using configuration management tools, change-tracking solutions, or cloud provider monitoring services.

7. Implement infrastructure access control

Restrict access to the infrastructure resources provisioned by Terraform. This involves proper access control mechanisms within your cloud provider or platform (e.g., IAM policies in AWS, Azure RBAC). Follow the principle of least privilege by granting Terraform scripts only the permissions required to perform their tasks. Use dedicated service accounts or IAM roles with minimal permissions tailored to the specific needs of your infrastructure. 

RBAC and IAM can be controlled using Terraform configurations. (Read more: How to create AWS IAM role with Terraform). In the example below, we assign an RBAC role of ‘Storage Blob Data Contributor Role’ to an already existing Azure storage account.

# Configure Azure Provider
provider "azurerm" {
 features {}
}

# Reference the Storage Account
data "azurerm_storage_account" "example" {
 name                = "mystorageaccount"
 resource_group_name = "myresourcegroup"
}

# Assign the Storage Blob Data Contributor Role
resource "azurerm_role_assignment" "blob_contributor" {
 scope              = data.azurerm_storage_account.example.id
 principal_id       = "your-principal-object-id"
 role_definition_id = "[subscription_id].providers/Microsoft.Authorization/roleDefinitions/b9197268-82f7-4ce7-99fa-4d1f42671a07"  # Storage Blob Data Contributor role
}

You should also enable two-factor authentication for your users accessing Terraform-related tools, platforms, and services.

8. Use a secure Version Control System (VCS)

To keep your Terraform configurations safe, store them in a secure version control system like Git. These types of VCS provide robust mechanisms to ensure the integrity and security of your infrastructure code. Version control systems enable you to track changes over time, offering a detailed history of who made changes, what those changes were, and why they were made.

9. Regularly update dependencies

Keep in mind monitoring vulnerability databases and security advisories for potential security issues affecting your dependencies. To benefit from the latest security fixes and features, keep Terraform, provider plugins, and any third-party modules up to date with the latest security patches and updates. You can use the terraform init -upgrade command to upgrade the provider plugins installed for your Terraform configuration to the latest compatible versions.

Defining specific versions of Terraform or the required provider can help prevent unexpected behavior. In the example below, the configuration will only work with Terraform versions greater than or equal to 1.2.0 but strictly less than 2.0.

terraform {
 required_version = ">= 1.2.0, < 2.0"
}

Here, a version greater than 1.2.0 must be used:

terraform {
 required_version = "> 1.2.0"
}

You can use various operators to define version ranges as required.

=: Exact version match (e.g., = 1.3.5)
>: Greater than (e.g., > 0.11.0)
<: Less than (e.g., < 1.4.0)
>=: Greater than or equal to
<=: Less than or equal to
~>: Compatible patch versions within a major version (e.g., ~> 1.1: allows 1.1.1, 1.1.2, etc.)

Whenever possible, source modules from trusted registries like the official Terraform Registry or your organization’s private registry. These sources typically have more stringent vetting processes and security controls in place.

10. Automate security compliance

Implement automated security compliance checks using tools like Terraform Compliance, Open Policy Agent (OPA), or Sentinel Policy as Code to enforce security policies, standards, and best practices across your Terraform configurations. 

For example, when defining policies for your infrastructure with OPA, it can be used to decide whether a plan is safe to apply or not:

You can leverage Spacelift policies, which provide a way to express rules as code. These rules manage your IaC environment and help make common decisions such as login, access, and execution. Policies are based on the Open Policy Agent project and can be defined using Rego. If you don’t want to write Rego from scratch to create your policies, you can use the Policy Library, which is a collection of templates that you can import as regular policies and modify to accommodate your needs.

Using Spacelift, you can implement policies that can decide, for example:

  • Access: who gets to access individual Stacks and with what level of access;
  • Approval: who can approve or reject a run and how a run can be approved;
  • Initialization: which Runs and Tasks can be started;
  • Plan: which changes can be applied;
  • And many more.

You should also consider the infrastructure created by Terraform as part of your wider regular security assessments, penetration testing, and vulnerability scans of your infrastructure to identify security weaknesses, assess risks, and prioritize remediation efforts.

11. Implement backup and disaster recovery

Implement backup and disaster recovery strategies for Terraform state files, configuration files, and critical infrastructure components. Maintain off-site backups, versioned backups, and recovery procedures to mitigate the impact of data loss or corruption.

12. Educate and train users

Provide security awareness training to users interacting with Terraform. This can help them understand potential security risks and best practices for secure infrastructure management. 

The awareness training should cover more than just Terraform security topics. Here are some recommended resources for further reading:

Key points

Terraform introduces its own set of security risks that you should be aware of. Terraform security means putting in place remediating controls to ensure safe and secure provisioning of cloud infrastructure. Terraform security practices include secure state management, least privilege access, and proper secrets management. By following the points above, you will establish a good security posture when deploying your infrastructure with Terraform!

If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Secure Terraform Management Made Easy

Spacelift effectively manages Terraform state, more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and includes many more features.

Start free trial

How can Spacelift stacks & dependencies elevate your IaC workflows?

Don’t miss our July 23 webinar.

Register for the webinar