fbpx

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.


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!

Microsoft MVP

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
HashiCorp Ambassador Microsoft Certified Trainer (MCT) Microsoft Certified: Azure Solutions Architect