In most Terraform projects you’ll probably be deploying and managing Azure resources within a single Azure Subscription. However, it’s common to structure things across multiple Azure Subscriptions in certain cases. One of these cases in when deploying a Hub and Spoke networking model and separating DEV and PROD environments into separate Azure Subscriptions. This is a case where you may end up having a single Terraform project manage some of the Resource Groups, VNets, and other resources within a single Terraform project managing the Governance or Networking for your environments. There are of course other cases where you may need to manage Azure resources across multiple Azure Subscriptions.
Table of Contents
Define Multiple Azure Providers for Multiple Azure Subscriptions
At these times, you will need to support multiple instances of the azurerm
Terraform Provider each targeting a different Azure Subscription. To do this, you can utilize the alias
attribute on the provider
block in Terraform to have multiple azurerm
providers configured for different Azure Subscription and/or different Azure Service Principal credentials (client id / secret).
Deploy to Multiple Azure Subscriptions
When deploying to multiple Azure Subscriptions while using the same credentials, you will define the azurerm
provider in the Terraform code by specifying the Azure Subscription ID using the subscription_id
argument. This can be done to setup multiple Azure provider instances in the same Terraform project that each target different Azure Subscription. This also requires that the same credentials (either Use or Service Principal) used by multiple providers in the Terraform project has permissions to manage resources in each Azure Subscription as needed.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.0.0"
}
}
}
provider "azurerm" {
alias = "dev"
subscription_id = var.dev_sub_id
features {}
}
provider "azurerm" {
alias = "prod"
subscription_id = var.prod_sub_id
features {}
}
If you’re using a single Terraform project to manage Azure resources across multiple Azure Subscriptions, but need to set different credentials for each azurerm
provider to use, then you’ll need to configure those credentials appropriately. The next section shows how to do this.
Deploy using Different Service Principals
When each Azure provider is required to use different credentials, you will need a separate Azure Service Principal (client id / secret) for each provider to use, with permissions to manage Azure resources each in only a single Azure Subscription. This helps increase the security of the Service Principals you use to setup your Infrastructure as Code (IaC) deployments.
Below is an example of 2 different provider
blocks for the azurerm
Terraform provider with the alias
attribute set to differentiate them, as they each have different Azure Service Principals configured:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.0.0"
}
}
}
provider "azurerm" {
alias = "dev"
subscription_id = var.dev_sub_id
tenant_id = var.dev_tenant_id
client_id = var.dev_client_id
client_secret = var.dev_client_secret
features {}
}
provider "azurerm" {
alias = "prod"
subscription_id = var.prod_sub_id
tenant_id = var.prod_tenant_id
client_id = var.prod_client_id
client_secret = var.prod_client_secret
features {}
}
Keep in mind that the above example uses Input Variables passed to the Terraform Project to define the values that are used to configure the Azure Service Principals to use for each of the azurerm
Provider instances authenticate with different Azure Subscriptions.
Deploy to Multiple Azure Subscriptions with Terraform
Once you have multiple azurerm
Terraform Providers configured in your Terraform project with the alias
attribute set for each, you can then use the provider
attribute on resource
blocks to tell that resource block to explicitly use a specific azurerm
Terraform provider with the alias
specified.
Below is an example of a single Terraform project with some code that manages a couple Azure resources across multiple Azure Subscriptions utilizing the azurerm
Terraform providers defined in the code example above:
resource azurerm_resource_group "prod_rg" {
provider = azurerm.prod
name = "E1-PROD-RG"
location = "eastus"
}
resource azurerm_virtual_network "prod_vnet" {
provider = azurerm.prod
name = "E1-PROD-VNET"
location = azurerm_resource_group.prod_rg.location
location = azurerm_resource_group.prod_rg.name
# Other attributes defined here...
}
resource azurerm_resource_group "dev_rg" {
provider = azurerm.dev
name = "C1-DEV-RG"
location = "centralus"
}
resource azurerm_virtual_network "dev_vnet" {
provider = azurerm.dev
name = "C1-DEV-VNET"
location = azurerm_resource_group.prod_rg.location
location = azurerm_resource_group.prod_rg.name
# Other attributes defined here...
}
With the resource.provider
attribute you’re telling your Terraform code which specific Terraform Provider to use for that resource. By defining multiple azurerm
providers with the alias
attribute, you are able to setup multiple instances of the azurerm
provider to be used within a single Terraform project. This can be done with the azurerm
provider or any other Terraform provider as well.
Passing Terraform Provider Alias to Modules
When you write modular and reusable Terraform code with Terraform Modules, the code within the module will likely just be referencing the azurerm
Terraform Provider without the use of the provider.alias
specifed. Be default, this will cause the Terraform Module to utilize the single default azurerm
provider defined. If you have multiple azurerm
providers defined using the alias
attribute, you can use the providers
attribute to configure the Terraform Module being used to target a specific provider instance.
Using the providers
attribute on a module
block can enable you to still utilize multiple azurerm
Terraform Providers in a project that is developed using Terraform Modules.
Below is an example of defining multiple module
blocks, each with a different azurerm
provider instance specified for it to use:
module "sample-dev" {
source = "./sample-module"
providers = {
azurerm = azurerm.dev
}
}
module "sample-prod" {
source = "./sample-module"
providers = {
azurerm = azurerm.prod
}
}
When using the module.providers
attribute to tell the Module which Terraform Provider instance to use, you define it using an object with syntax that allows for multiple providers to be specified for the module
. The reason for this is that a single Terraform module could have code in it that manages multiple resources that are a part of multiple different Terraform providers. This enables you to explicitly specify all the different Terraform providers for that module to use in case you have multiple instances of multiple different types of Terraform Providers used in your single Terraform project.
Deploy to Multiple Azure Subscriptions with a “Default” azurerm
Provider
The previous examples shows how to define multiple azurerm
Terraform Providers using the alias
attribute and then explicitly tell a resource
or module
which Terraform Provider instance to use. However, what if you need to define a “Default” azurerm
Terraform Provider you want Terraform to automatically use first, unless you explicitly tell it which provider to use for specific resources
or modules
? Well, you can easily do this by defining a azurerm
Terraform Provider without the alias
attribute set and this will be the “Default” provider.
Below is an example of this used:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.0.0"
}
}
}
# This is the "Default" provider
provider "azurerm" {
subscription_id = var.dev_sub_id
tenant_id = var.dev_tenant_id
client_id = var.dev_client_id
client_secret = var.dev_client_secret
features {}
}
# This provider uses the "prod" alias
provider "azurerm" {
alias = "prod"
subscription_id = var.prod_sub_id
tenant_id = var.prod_tenant_id
client_id = var.prod_client_id
client_secret = var.prod_client_secret
features {}
}
# Use the "Default" azurerm provider
resource azurerm_resource_group "dev_rg" {
name = "C1-DEV-RG"
location = "centralus"
}
module "sample-dev" {
source = "./sample-module"
}
# Use the azurerm provider with the "prod" alias
resource azurerm_resource_group "prod_rg" {
provider = azurerm.prod
name = "E1-PROD-RG"
location = "eastus"
}
module "sample-prod" {
source = "./sample-module"
providers = {
azurerm = azurerm.prod
}
}
Securely Pass Credentials to Terraform
All the Terraform examples in this article use variables (via var
usage) to pass the Azure credentials to the multiple configured Azure providers. This is the best practice when setting up Terraform deployments when it comes to security. Hard coding the client_id
and client_secret
for the Azure Service Principals in the Terraform code will mean those credentials will get committed to source control and become vulnerable to malicious abuse.
NEVER store Azure Service Principal credentials as plain text in the Terraform code or source control repository!
If you have questions about using input variables in Terraform, please refer to my article titled “Use Terraform Input Variables to Parameterize Infrastructure Deployments“. That article covers what you need to start parameterizing your Terraform code, so you can pass these sensitive Azure Service Principal credentials to the Terraform deployments in a secure fashion.
Happy deploying!
Hi
My module needs to get data from 2 subscriptions. how do you pass 2 azure providers to module?
Use the `providers = { }` method shown in the article to pass the AzureRM provider named `azurerm` to the `module` and add another one with a different name for the second Azure Subscription.
Hello Chris,
Have you tested it? I got the following error:
There is no explicit declaration for local provider name “azurerm” in module…..then Terraform will not apply the plan
Ensure you have the ‘provider’ block configured correctly. And, yes, I have code running in production using this.
resource “azurerm_app_service_virtual_network_swift_connection” “vnet-integration” {
app_service_id = data.azurerm_windows_web_app.app.id
subnet_id = data.azurerm_subnet.vnet-integration.id
}
If I use the default provider it can see the app, but not the subnet. If I use provider with the subnet subscription it can see the subnet, but not the app. The two resources are in seperate subscriptions.
The data calls are using the appropriate providers.
How to make this work?
You need to use the “provider” property on the ‘data’ block for the individual resource to tell it the alias of the Terraform ‘azurerm’ provider instance to use for that specific resource. I hope this helps.
Hello Chris, what would be the right way to use multiple subscriptions with shared external modules where I can add the “provider” variable to the resource.
You would need to build the module to accept and expect multiple providers and their alias when used.