Terraform is a great orchestrator for infrastructure provisioning. It has a tight integration with Azure and you can provision just about anything with it.
However, quite often security is overlooked in the provisioning process:
- Credentials used to connect to Azure are not kept securely.
- Virtual machines are created with weak passwords.
- Passwords are kept in the terraform configuration file in clear text.
If you ever thought to yourself “There’s gotta be a better way to do this…” then read on to gain insight on security basics when using Terraform and Azure.
We will leverage Azure Key Vault, a managed service to store sensitive data (such as secretes, keys and certificates) to help secure our infrastructure provisioning process.
If you want to follow along, you will need:
- Azure Subscription and an administrative account (You can start a free trial here)
- Terraform binary installed on your machine
- Azure CLI tools and basic knowledge of using the az commands (az login and such)
If you’ve been living under a rock for the last couple of years, let me give you a short background into the different components we will use:
Terraform is a software that enables you provision infrastructure using code. It does that by leveraging providers such as Azure, AWS, GCP and others and provisions the infrastructure (virtual machines, managed DBs, networks, blob storage, etc.) on top of them. Terraform uses its own language called HCL (Hashicorp Configuration Language) to define the set of infrastructure to provision.
Azure is the cloud infrastructure offering from Microsoft. It allows you to consume infrastructure and other dev-related services as a service, on a pay-as-you-go model.
One of those services is called Key Vault, which is essentially a secure vault in the cloud. You can safely store sensitive information into a given Key Vault (such as passwords, certificates and such) and use them with other resources
Connect Terraform Securely to Azure
Let’s start with the very basic – connecting Terraform and Azure. This is a one time process that will allow us to then connect securely to Azure from within Terraform every time we want to provision or modify Azure Infrastructure with Terraform
Setup Terraform Service Principle Name (SPN) in Azure
Terraform recommends authenticating using a Service Principle when using a shared environment.
(Note: although you can use the Azure CLI as well when you’re running Terraform locally, I found that using a Service Principle in both use cases is a better approach and helps streamline the overall provisioning process).
Terraform needs the following information to authenticate with Azure:
In the link I shared you can read how to retrieve those values. Once you have them, come back here.
We will create secrets for all the above values inside Azure Key Vault, and then use those secrets to authenticate Terraform with Azure.
Setup Azure Key Vault
Azure Key Vault is a managed service from Microsoft that allows you to store and access sensitive data in a secure way. Full documentation can be found here.
We need to create a Key Vault and grant our SPN permissions to the Key Vault (Note: although you can create the Key Vault itself with Terraform and grant the Terraform Service Principle access to the Key Vault, there is a bug in the process and access isn’t actually granted.)
Once created, go to your Key Vault and create an Access Policy granting access to the Terraform SPN. See the image below for an example on how to accomplish that (I gave the Terraform SPN full permissions to the Key Vault. In a production scenario, you might want to limit that)
Store Terraform login information in Azure Key Vault
In the beginning, we created the SPN for Terraform and got the following data:
To access it securely we should:
- Store the data in Azure Key Vault.
- Configure environment variables on our machine to use the secrets from Azure.
- createsecrets.sh will create the secrets in Azure. Replace the data next to the –value parameter with the data you got in step I (You need to authenticate to Azure with a user that has permissions to run az keyvault secret set, to create the secrets). This is a one-time process.
- mycreds.sh is a local file on your computer that you source (e.g. source ./mycreds.sh) whenever you need to make the connection to Azure. The data will stay in you shell environment as long as the shell is open. Again, to run this script you need to authenticate to azure with a user that has permissions to run az keyvault secret show). This process is done once pre shell session (normally when you start your workday)
- Note that once sourced, the credentials are stored in plain text in your shell (output.sh shows that).
Once sourced, Terraform will pick it up and use it to authenticate to Azure when you run Terraform init (which is the first command you run when you want to start configuring infrastructure with Terraform). Read on to see a live example of the process.
This way, no sensitive information is stored in your .tf configuration files! (and you can now save and share them securely with team members using source control, for example)
Optionally, you can source any other Terraform variable using the same technique. You don’t have to do it for our example, but it’s important that you will be familiar with this functionality.
For example, you might need to work with Azure AD and for that you need the tenant_id. In such a case, just make sure to prefix it with TF_VAR:
And then you can declare a variable without a value in your variables.tf file and terraform will pick the value from your shell:
Connect to Azure and Provision Resources
At this point you should be able to authenticate from Terraform to Azure using the data that is stored in your shell (We will test it together in a moment).
This gives us the ability to start provisioning resources in Azure using Terraform.
Let’s think of the following simple scenario:
- You want to provision a virtual machine
- You want to generate a login password that will be complex
- You want to store and retrieve that password in a secure way
We can achieve this type of scenario rather easily by utilizing Azure Key Vault.
Create Key Vault Secrets using Terraform
This time, since we’re already connected to Azure, we will create a secret and store it in Key Vault using Terraform:
Save the below file in a folder on your computer and make sure to change the default value for the vault_uri and resource_group_name variable to the key vault that you created earlier:
Terraform “random” Function
If you look closely at the Terraform code, you will see a “random string” resource.
This is a terraform resource to create a random string. This is a terraform-specific resource, unrelated to Azure in particular.
You can use that to auto-generate the secret data and achieve 2 goals at once:
- Once again, you don’t put your secret data in clear text inside your configuration, allowing you to check it in to source control safely and share it among team members
- You can create a complex string that can adhere to security requirements
Open your shell and navigate to the folder where you saved the above file and run terraform init (this will utilize the connection to Azure and download any provider-specific information that might be needed to run the terraform plan). The output should look similar to this:
$ terraform init Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "azurerm" (1.19.0)... - Downloading plugin for provider "random" (2.0.0)... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.azurerm: version = "~> 1.19" * provider.random: version = "~> 2.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
Run terraform plan to see what terraform will provision on Azure:
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + azurerm_key_vault_secret.vm_secret id: <computed> name: "vm-secret" tags.%: "1" tags.environment: "my-resource-group" value: <sensitive> vault_uri: "https://jungo-test-keyvault.vault.azure.net/" version: <computed> + random_string.vm_password id: <computed> length: "14" lower: "true" min_lower: "2" min_numeric: "2" min_special: "2" min_upper: "2" number: "true" result: <computed> special: "true" upper: "true" Plan: 2 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.
If everything looks good, you can go ahead and run terraform apply to create the resources in azure:
$ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + azurerm_key_vault_secret.vm_secret id: <computed> name: "vm-secret" tags.%: "1" tags.environment: "my-resource-group" value: <sensitive> vault_uri: "https://jungo-test-keyvault.vault.azure.net/" version: <computed> + random_string.vm_password id: <computed> length: "14" lower: "true" min_lower: "2" min_numeric: "2" min_special: "2" min_upper: "2" number: "true" result: <computed> special: "true" upper: "true" Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes random_string.vm_password: Creating... length: "" => "14" lower: "" => "true" min_lower: "" => "2" min_numeric: "" => "2" min_special: "" => "2" min_upper: "" => "2" number: "" => "true" result: "" => "<computed>" special: "" => "true" upper: "" => "true" random_string.vm_password: Creation complete after 0s (ID: none) azurerm_key_vault_secret.vm_secret: Creating... name: "" => "vm-secret" tags.%: "" => "1" tags.environment: "" => "my-resource-group" value: "<sensitive>" => "<sensitive>" vault_uri: "" => "https://jungo-test-keyvault.vault.azure.net/" version: "" => "<computed>" azurerm_key_vault_secret.vm_secret: Creation complete after 3s (ID: https://jungo-test-keyvault.vault.azure...ecret/ee95bebcac8c46b2a2cbd6383bb1b906) Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: vm_password_result = 4>RLN@F6#Vsub9
The Outputs section will output the generated data to the console so we can store and use it in a safe place (In addition, you can query it later using Terraform or Azure CLI).
Use the created secret as VM login
Let’s take our previous example of creating a secret and add a simple vm config to it. For the sake of readability, note that this isn’t a complete configuration to setup a VM in Azure using Terraform. I just added the specific part that refers to the secret.
A full example on how to provision a VM in Azure using Terraform can be found here.
The important part here is the last few lines, starting with os_profile. Take a close look and see that the value for admin_password is a query that gets the value of the created secret
To wrap it all up…
Let’s recap what we did:
- Setup Azure Terraform SPN (Service Principle Name) to enable Terraform access to Azure.
- Created Azure Key Vault to store the SPN login information and granted the Terraform SPN access to the Key Vault.
- Authenticated to Azure using credentials that are safely stored in Azure.
- Created other secrets in Azure Key Vault.
- Used the created secret as the login password to a VM that we provisioned using Terraform.
So there you have it – simple and secure operation of Terraform and Azure, using Azure Key Vault.
What did you think? Do you have a suggestion to further secure the flow? Ping me back @omerbarel on Twitter and share your feedback
Oh, and one last thing –
Make sure to check back soon as I will take us to the next level – securely provision AKS (Azure Kubernetes Service) using Terraform and connect it to Azure Key Vault to further secure our environments