'Azure ARM deployment access a resource from a different subscription when deploying a new resource

I am using an Azure service principal and C# Azure SDK to deploy this ARM template. The publicIPPrefixResourceId I am passing as a parameter is the ARM Id of the resource that is in a different subscription from where I am currently deploying to. However, the service principal has access to both subscriptions. But I get an error saying it was not able to find the publicIPPrefixResourceId in the current subscription.

I am wondering is there a way to specify that also includes other subscriptions while looking for a resource? I don't want to use a custom-managed service identity because my service principle has access to both subscriptions.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for all resources."
      }
    },
    "publicIpName": {
      "type": "string"
    },
    "publicIpSku": {
      "type": "string",
      "defaultValue": "Standard"
    },
    "publicIPPrefixResourceId": {
      "type": "string",
      "metadata": {
        "description": "Resource Id of the PublicIpPrefix to create VM VIP"
      }
    }
  },
  "resources": [
    {
      "apiVersion": "2019-02-01",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[parameters('publicIpName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('publicIpSku')]"
      },
      "properties": {
        "publicIPAllocationMethod": "Static",
        "publicIPPrefix": {
          "Id": "[parameters('publicIPPrefixResourceId')]"
        }
      }
    }
  ]
}

C# Azure SDK that is deploying above ARM template:

        var deploymentTask = azure.Deployments.Define(parameters.DeploymentName)
            .WithExistingResourceGroup(resourceGroupName)
            .WithTemplate(vmTemplate)
            .WithParameters(deploymentParameters)
            .WithMode(DeploymentMode.Incremental)
            .Create();


Solution 1:[1]

I don't think this is doable, while looking for resource group, you are looking under a specific subscription. The SDK you are using is our old Azure SDK for .net, I'll suggest that you try our new SDK Azure.ResourceManager and Azure.ResourceManager.Resource, the new SDK integrates with the latest Azure.Identity, you can directly use azure resource identifier to look for resources

var client = new ArmClient(new DefaultAzureCredential());
var resourceGroup = client.GetResourceGroupResource(new Azure.Core.ResourceIdentifier("/subscriptions/<Subscriptionid>/resourceGroups/<resourcegroupname>"));
ArmDeploymentCollection ArmDeploymentCollection = resourceGroup.GetArmDeployments();
        string deploymentName = "myDeployment";
        var input = new ArmDeploymentContent(new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
        {
            TemplateLink = new ArmDeploymentTemplateLink()
            {
                Uri = new Uri("https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.storage/storage-account-create/azuredeploy.json")
            },
            Parameters = BinaryData.FromObjectAsJson(new JsonObject()
                {
                    {"storageAccountType", new JsonObject()
                        {
                            {"value", "Standard_GRS" }
                        }
                    }
                })
        });
        ArmOperation<ArmDeploymentResource> lro = await ArmDeploymentCollection.CreateOrUpdateAsync(WaitUntil.Completed, deploymentName, input);
        ArmDeploymentResource deployment = lro.Value;

Or hierarchy look for the resources like below

var client = new ArmClient(new DefaultAzureCredential());
var subscriptions =client.GetSubscriptions();
foreach(var sub in subscriptions)
{
    Console.WriteLine("SubID:"+sub.Data.DisplayName);
    var rgs = sub.GetResourceGroups();
    foreach(var rg in rgs)
    {
        Console.WriteLine("RgName:"+rg.Data.Name);
        ArmDeploymentCollection ArmDeploymentCollection = rg.GetArmDeployments();
        string deploymentName = "myDeployment";
        var input = new ArmDeploymentContent(new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
        {
            TemplateLink = new ArmDeploymentTemplateLink()
            {
                Uri = new Uri("https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.storage/storage-account-create/azuredeploy.json")
            },
            Parameters = BinaryData.FromObjectAsJson(new JsonObject()
                {
                    {"storageAccountType", new JsonObject()
                        {
                            {"value", "Standard_GRS" }
                        }
                    }
                })
        });
        ArmOperation<ArmDeploymentResource> lro = await ArmDeploymentCollection.CreateOrUpdateAsync(WaitUntil.Completed, deploymentName, input);
        ArmDeploymentResource deployment = lro.Value;

    }
}
Console.ReadLine();

The new version SDK is going to cover more Azure services very soon

Solution 2:[2]

It is possible to reference a resource the way you are trying to do. There are caveats however:

  1. You need to provide the resourceId correctly, for example: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tmp/providers/Microsoft.Network/publicIPPrefixes/prefixtemp" - see also resourceId function
  2. You cannot reference a public IP prefix in another subscription. This is not a limitation of ARM but rather of the specific service.

all public IP addresses created from the prefix must exist in the same Azure region and subscription as the prefix.

Full "working" example:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "location": {
        "type": "string",
        "defaultValue": "[resourceGroup().location]",
        "metadata": {
          "description": "Location for all resources."
        }
      },
      "publicIpName": {
        "type": "string"
      },
      "publicIpSku": {
        "type": "string",
        "defaultValue": "Standard"
      },
      "publicIPPrefixResourceId": {
        "type": "string",
        "metadata": {
          "description": "Resource Id of the PublicIpPrefix to create VM VIP"
        },
        "defaultValue": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tmp/providers/Microsoft.Network/publicIPPrefixes/prefixtemp"
      }
    },
    "resources": [
    ],
    "outputs": {
        "ipPrefixInOtherSubscription": {
          "type": "string",
          "value": "[reference(parameters('publicIPPrefixResourceId'), '2021-08-01').ipPrefix]"
        }
      }
  }

If you try to use it like you intended, you get this error:

{
    "code": "ResourceReferenceUsesWrongSubscriptionId",
    "message": "Property publicIPPrefix of resource /subscriptions/11111111-0000-0000-0000-000000000000/resourceGroups/rg-temp/providers/Microsoft.Network/publicIPAddresses/test can only reference resources in subscription(s) 11111111-0000-0000-0000-000000000000. Value /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tmp/providers/Microsoft.Network/publicIPPrefixes/prefixtemp references wrong subscription.",
    "details": []
}

And there is no way around that.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Elendil Zheng-MSFT
Solution 2 Alex AIT