Overview
This tutorial uses examples from the following GitHub project: Azure Terraform Deployments.
When creating terraform configurations, especially when using CI/CD tooling such as Azure DevOps or GitHub it is very easy to overlook what exactly is being output as part of a Terraform configuration plan, especially if the configuration contains sensitive data. This could lead to sensitive data and settings to be leaked.
In todays tutorial we will look at examples on how we can protect and hide sensitive data in terraform output using masking.
Sensitive Variable Type
In the following demo configuration we will use Terraform to create the following resources in Azure:
- Resource Group
- App Service Plan
- App Insights
- App Service
NOTE: All the code samples used in this tutorial are updated to use the the latest version of the AzureRM provider 3.0.
Let's take a closer look at the App service configuration:
## appservices.tf ##
resource "azurerm_linux_web_app" "APPSVC" {
name = var.appsvc_name
location = azurerm_resource_group.RG.location
resource_group_name = azurerm_resource_group.RG.name
service_plan_id = azurerm_service_plan.ASP.id
https_only = true
identity {
type = "SystemAssigned"
}
site_config {
container_registry_use_managed_identity = true
ftps_state = "FtpsOnly"
application_stack {
docker_image = "${var.acr_name}.azurecr.io/${var.appsvc_name}"
docker_image_tag = "latest"
}
vnet_route_all_enabled = var.vnet_route_all_enabled
}
app_settings = var.appsvc_settings
}
Notice the setting called app_settings = var.appsvc_settings
. The variable for this setting is defined as a map:
## variables.tf ##
variable "appsvc_settings" {
type = map(any)
description = "Specifies the app service settings to be created."
default = null
}
If we pass the following variable into the terraform config:
appsvc_settings = {
APPINSIGHTS_INSTRUMENTATIONKEY = "!!sensitive_Key!!"
sensitive_key1 = "P@ssw0rd01"
sensitive_key2 = "P@ssw0rd02"
}
Notice that when the terraform plan is being run, terraform will actually output the variable into the terraform plan and log to the CI/CD tooling as output:
This can be an issue because the data will be leaked to anyone who has access to the CI/CD logs/output. Especially dangerous if the repository or project is public.
So what we can do to mask the setting from output is to mark the variable as sensitive. We can do that by adding sensitive = true
to the variable:
## variables.tf ##
variable "appsvc_settings" {
type = map(any)
description = "Specifies the app service settings to be created."
default = null
sensitive = true
}
Notice now that when the terraform plan is being run, terraform will mask the output of the variable:
Sensitive Output Type
Similarly to variables, outputs can also be marked as sensitive. For example say we want to create a sensitive output we can mark the output
as sensitive = true
as shown the the below example:
## appservices.tf ##
resource "azurerm_application_insights" "INSIGHTS" {
name = var.app_insights_name
location = azurerm_resource_group.RG.location
resource_group_name = azurerm_resource_group.RG.name
application_type = "web"
workspace_id = var.workspace_id != null ? var.workspace_id : null
}
output "insights_key" {
value = azurerm_application_insights.INSIGHTS.instrumentation_key
sensitive = true
}
Sensitive Function
Another way to mark output as sensitive is by using the sensitive()
function. in the demo configuration let's change the way we send app_settings
to the app service configuration by creating a dynamic locals
config instead of a variable:
## local.tf ##
locals {
#Appsvc Settings
app_settings = {
default_settings = {
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.INSIGHTS.instrumentation_key}"
DOKCER_REGISTRY_SERVER_URL = "${var.acr_name}.azurecr.io"
},
linux_app_settings = {
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.INSIGHTS.instrumentation_key}"
DOKCER_REGISTRY_SERVER_URL = "${var.acr_name}.azurecr.io"
WEBSITE_PULL_IMAGE_OVER_VNET = "true"
LINUX_SENSITIVE_VALUE = "!!sensitive_value!!"
}
}
}
As you can see the locals configuration has two configurations, one called default_settings
and another called linux_app_settings
, we can send the relevant config by using the terraform lookup()
function as shown below app_settings = lookup(local.app_settings, "linux_app_settings", null)
:
## appservices.tf ##
resource "azurerm_linux_web_app" "APPSVC" {
name = var.appsvc_name
location = azurerm_resource_group.RG.location
resource_group_name = azurerm_resource_group.RG.name
service_plan_id = azurerm_service_plan.ASP.id
https_only = true
identity {
type = "SystemAssigned"
}
site_config {
container_registry_use_managed_identity = true
ftps_state = "FtpsOnly"
application_stack {
docker_image = "${var.acr_name}.azurecr.io/${var.appsvc_name}"
docker_image_tag = "latest"
}
vnet_route_all_enabled = var.vnet_route_all_enabled
}
app_settings = lookup(local.app_settings, "linux_app_settings", null)
}
Since our app insights instrumentation key output is already marked as a sensitive output, it is all good and well for that value to be hidden from the output:
But what about if we want the entire app_settings
config block to be hidden?
This is where the sensitive()
function comes in, as you can see by just wrapping the relevant locals variables in the sensitive()
function will instruct terraform to hide the entire block from output:
## local.tf ##
locals {
#Appsvc Settings
app_settings = {
default_settings = sensitive({
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.INSIGHTS.instrumentation_key}"
DOKCER_REGISTRY_SERVER_URL = "${var.acr_name}.azurecr.io"
}),
linux_app_settings = sensitive({
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.INSIGHTS.instrumentation_key}"
DOKCER_REGISTRY_SERVER_URL = "${var.acr_name}.azurecr.io"
WEBSITE_PULL_IMAGE_OVER_VNET = "true"
LINUX_SENSITIVE_VALUE = "!!sensitive_value!!"
})
}
}
I hope you have enjoyed this post and have learned something new. You can also find the code samples used in this blog post on my GitHub page. ❤️
Author
Like, share, follow me on: 🐙 GitHub | 🐧 X/Twitter | 👾 LinkedIn
Top comments (1)
But store sensetive data in TFVARS file is the best practice?