Import Azure Subscription Not Created with Terraform

Goal: Importing 2 Azure subscriptions not created with IaC/Terraform

Issue: When importing, the error

Error: failed reading Subscription Alias: subscriptions.SubscriptionsClient#AliasGet: Failure responding to request: StatusCode=401 – Original Error: autorest/azure: Service returned an error. Status=401 Code=“UserNotAuthorized” Message=“User does not have access Microsoft.Subscription/aliases/write over scope providers/Microsoft.Subscription/aliases/sub1”

Error: failed reading Subscription Alias: subscriptions.SubscriptionsClient#AliasGet: Failure responding to request: StatusCode=401 – Original Error: autorest/azure: Service returned an error. Status=401 Code=“UserNotAuthorized” Message=“User does not have access Microsoft.Subscription/aliases/write over scope providers/Microsoft.Subscription/aliases/sub2”

Actions:

terraform import -var-file=“app.tfvars” module.azure_subscription.azurerm_subscription.subscription_app “/providers/Microsoft.Subscription/aliases/sub1”

terraform import -var-file=“app.tfvars” module.azure_subscription.azurerm_subscription.subscription_data “/providers/Microsoft.Subscription/aliases/sub2”

Code:

module “azure_subscription” {
providers = {
azurerm = azurerm.subshared
}
source = “…/…/modules/azure_subscription/”
application = var.application
environment = var.environment#
region_short = var.region_short
tags = local.tags
workload = “DevTest” #DevTest or Production
}

#Create Azure Subscriptions
#Terraform Registry

#Providers

terraform {
required_providers {
azurerm = {
source = “hashicorp/azurerm”
version = “~> 3.108.0”
}
}
required_version = “>= 1.1.0”
}

#Data

data “azurerm_billing_mca_account_scope” “billing_scope” {
billing_account_name = “xxx”
billing_profile_name = “xxx”
invoice_section_name = “xxx”
}

#Variables

variable “application” {
description = “application and/or Product name from tfvars”
}
variable “environment” {
description = “environment, Ex: sbx, from tfvars”
}
variable “region_short” {
description = “region short name, eus, from tfvars”
}
variable “tags” {
description = “tags from TF local”
}
variable “workload” {
description = “DevTest or Production Azure Subscription Offer Type”
}

#Resources
resource “azurerm_subscription” “subscription_app” {
alias = “prefix-{var.region_short}-app-{var.application}-{var.environment}" subscription_name = "***prefix***-{var.region_short}-app-{var.application}-{var.environment}”
billing_scope_id = data.azurerm_billing_mca_account_scope.billing_scope.id
workload = var.workload
tags = var.tags
}

resource “azurerm_subscription” “subscription_data” {
alias = “prefix-{var.region_short}-data-{var.application}-{var.environment}" subscription_name = "***prefix***-{var.region_short}-data-{var.application}-{var.environment}”
billing_scope_id = data.azurerm_billing_mca_account_scope.billing_scope.id
workload = var.workload
tags = var.tags
}

Outputs

##App
output “azurerm_subscription_app_id” {
value = azurerm_subscription.subscription_app.id
}

output “azurerm_subscription_app_subscription_name” {
value = azurerm_subscription.subscription_app.subscription_name
}

output “azurerm_subscription_app_billing_scope_id” {
value = azurerm_subscription.subscription_app.billing_scope_id
}

Data

output “azurerm_subscription_data_id” {
value = azurerm_subscription.subscription_data.id
}

output “azurerm_subscription_data_subscription_name” {
value = azurerm_subscription.subscription_data.subscription_name
}

output “azurerm_subscription_data_billing_scope_id” {
value = azurerm_subscription.subscription_data.billing_scope_id
}

Attempted:

Followed, Terraform Registry, adding alias

#Resources
resource “azurerm_subscription” “sub1” {
alias = “sub1”
subscription_name = “sub1”
subscription_id = “xxx”
}

resource “azurerm_subscription” “sub2” {
alias = "sub2
subscription_name = “sub2”
subscription_id = “xxx”
}

Alias is added per, az account alias list

{
  "id": "/providers/Microsoft.Subscription/aliases/sub1",
  "name": "sub1",
  "properties": {
    "acceptOwnershipState": null,
    "acceptOwnershipUrl": null,
    "billingScope": null,
    "createdTime": null,
    "displayName": null,
    "managementGroupId": null,
    "provisioningState": "Succeeded",
    "resellerId": null,
    "subscriptionId": "xxx",
    "subscriptionOwnerId": null,
    "tags": null,
    "workload": null
  },
  "systemData": null,
  "type": "Microsoft.Subscription/aliases"
},


{
  "id": "/providers/Microsoft.Subscription/aliases/sub2",
  "name": "sub2",
  "properties": {
    "acceptOwnershipState": null,
    "acceptOwnershipUrl": null,
    "billingScope": null,
    "createdTime": null,
    "displayName": null,
    "managementGroupId": null,
    "provisioningState": "Succeeded",
    "resellerId": null,
    "subscriptionId": "xxx",
    "subscriptionOwnerId": null,
    "tags": null,
    "workload": null
  },
  "systemData": null,
  "type": "Microsoft.Subscription/aliases"
},

Tried adding role assignment per,

az role assignment create --assignee 12345 --role owner --scope “providers/Microsoft.Subscription/aliases/sub1”
az role assignment create --assignee 12345 --role owner --scope “providers/Microsoft.Subscription/aliases/sub2”

Error;

The command failed with an unexpected error. Here is the traceback:
list index out of range
Traceback (most recent call last):
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/command_modules/role/custom.py”, line 180, in create_role_assignment
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/command_modules/role/custom.py”, line 204, in _create_role_assignment
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/command_modules/role/_multi_api_adaptor.py”, line 57, in create_role_assignment
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/core/tracing/decorator.py”, line 76, in wrapper_use_tracer
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/mgmt/authorization/v2022_04_01/operations/_role_assignments_operations.py”, line 902, in create
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/core/exceptions.py”, line 112, in map_error
azure.core.exceptions.ResourceExistsError: (RoleAssignmentExists) The role assignment already exists.
Code: RoleAssignmentExists
Message: The role assignment already exists.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\knack/cli.py”, line 233, in invoke
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/init.py”, line 664, in execute
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/init.py”, line 731, in _run_jobs_serially
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/init.py”, line 701, in _run_job
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/init.py”, line 334, in call
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/command_operation.py”, line 121, in handler
File “D:\a_work\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/command_modules/role/custom.py”, line 186, in create_role_assignment
IndexError: list index out of range

Azure RBAC;

I was attempting to run as an app registration, tried my global administrator account, both produce the same import error and error with RBAC permissions on “/”. Both are owner on either subscription, however it’s assigned at Management Group.

Links Reviewed;
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subscription

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subscription#example-usage---adding-an-alias-to-an-existing-subscription

Comments;

This is an issue for us as we are implementing terraform as an enterprise and have many existing subscriptions.

@nilocoly

Azure Subscriptions are a bit of a special case. You can only ‘import’ subscriptions that have been programmatically created (e.g… via a Terraform module). In reality the requirement is to have an alias (which Terraform will create when provisioning a subscription) for which you can then use the import command.

However, if you have subscriptions that are already in existence then, rather than being imported into Terraform, they must be adopted.

The documentation hints at this but is not 100% clear. But all you need is the following in your module:

resource "azurerm_subscription" "example_existing_subscription" {
  subscription_name = "My Example Existing Subscription"
  subscription_id   = "12345678-12234-5678-9012-123456789012" # <-- Existing Subscription ID
}

This will cause Terraform to set a new GUID-based alias and adopt the subscription into the state. No separate import step is required. It will say it is adding the resource but it does not add a new subscription.

In order to do this the subscription should not have an existing alias. But this can be checked and managed by the az account alias .... az cli commands.

Also note that providing subscription_id can be specified only for adopting control of an existing Subscription, it cannot be used to provide a custom Subscription ID.

And that Either billing_scope_id or subscription_id has to be specified, and by providing a billing_scope instead of the subscription_id this indicates that you want a new subscription to be created.

You will need to ensure you (or the identity that you are running terraform under) has the appropriate RBAC set in order to create the alias as part of the adoption process.

I suggest you try on a testing subscription until you are happy with the process. You can always remove the alias using the az cli and delete the state (not a terraform destroy) to repeat the process

Hope that helps

Happy Terraforming