Configuring Azure Arc Managed Identity with Terraform

It’s that time of the year when I remember that Azure Arc is surprisingly cool, and I should say something about it. Well, an interesting story happened to me just a few weeks ago, so let’s talk about that!

The Problem

We have an old remote on-premise VM, which has access to some local privileged resources, and we’d want to allow it to do a thing or two in Azure as well. How would you do that?

Traditional Service Principal (SP) obviously does the trick. You’d create it in Azure, throw in some permissions, and then give SP’s TenantID/ClientID/ClientSecret values to VM admins. Having them, the admins would az login --service-principal in whatever piece of automation they require access for, and the problem would’ve been solved. However…

Client Secrets have a nasty habit of expiring exactly when everybody who’s involved has already forgotten about their existence. Then, the secret is basically an explicit password, and passwords are bad. Especially when given to people I know nothing about. On the other hand, if I could assign that VM a managed identity (often referred as MSI), like we do for normal Azure compute resources, and then grant permissions to MSI directly, there would be no passwords, no expiration, and no necessity to remember anything ever again. The perfect solution of a problem. It’s trivial to connect the machine to Azure Arc, so if Azure Arc somehow supports MSIs…

And it actually does!

Azure Arc and System-assigned Managed Identity

Talking about MSI, we usually mean one of two things – system-assigned or user-assigned managed identity. System identity is fully baked into the resource, it gets created with it, and it dies with it as well. In contrast, user-assigned identity is a fully separate resource, which can be assigned on demand and reused later. In Terraform, we explicitly enable system identity or explicitly assign the other one.

The problem is that we don’t create Azure Arc resource with Terraform, but rather onboard it with the script, so where exactly is the place for MSI settings?

As it happens, Azure grants Arc machine system-assigned identity by default, so we don’t have to enable anything. Current UI doesn’t advertise this fact, but you could see Arc’s MSI details through resource JSON view.

Hit “JSON View” link and..
Voila!

And yes, I probably should’ve mentioned earlier, but today’s example and associated Terraform will be based on my previous post about Azure Arc.

So, we already have the MSI, what would be a Terraform-friendly way to assign it some permissions? We won’t be manually copy-pasting identity GUIDs after all. Not for one machine, and especially not for a hundred.

Discovering Arc Machine MSIs Dynamically with Terraform

Here’s another trick I learned just recently – Terraform has azurerm_resources data source, which can discover and filter out arbitrary Azure resources by their type and a resource group name. I happen to know both, so in addition to main.tf, arc.tf and digitalocean.tf files from the previous post, let’s create msi.tf and do some extra magic:

I collected Arc machine name(s) in a local variable, so it’s

  • easier to debug it
  • can be used later.

For instance, exposing the variable with Terraform output shows that indeed, existing Arc machine was successfully discovered:

Isn’t that beautiful?

Having machine names (I also could’ve extracted their IDs), I can pass that data to another data source – azurerm_arc_machine and pipe it to the permission assignments:

And believe it or not, this thing works. I run it myself, the outputs were fine, Microsoft data centres didn’t shut down. The remaining piece of the puzzle is how would one use this identity and its permissions on a target machine?

Logging in as MSI on Azure Arc Machine

Usually, logging in as MSI is trivial – az login --identity, and the trick just happens. However, when I did it on Azure Arc machine for the first time, this thing happened instead:

Can’t MSI login on older versions of AZ CLI. Can request oauth2 token manually, though

az login seemed to timeout and then failed with an error. And here starts a small theoretic detour.

When run inside of Azure network, the VM has access to a special metadata service (http://168.63.129.16 or something), which az login --identity uses to receive its oauth2 token and feel authenticated.

Azure Arc machine doesn’t run in Azure network. Instead, the binary, which was installed by Arc onboarding script and which turns the VM into an Azure Arc VM, that particular binary runs a local HTTP server which pretends to be a metadata service itself. If you properly HTTP GET it, you’ll receive the token, and it’s good enough to make API calls to other Azure resources. The inconvenience is that even though Azure Arc and its hybrid metadata service have been around for years, az login doesn’t fully support it.

But it will. January 14th, 2025 is the day. The release for Windows happened somewhere in November 2024, but Linux support is yet to come. We still can use a preview version, though. Just head to azclitools’s public build pipeline, grab the latest build artifact, install it on the VM, and it will work as expected.

Azure CLI public build pipeline
Custom build of az cli

Chances are that unless you’re reading this post on New Year’s break, your AZ CLI will be already up-to-date.

The final nuance is that az login --identity works fine only because I run it as a root. That’s not now normal people execute stuff, so if I was dealing with a VM I actually care about, I’d add myself to himds (hybrid instance metadata service) group first:

  • usermod -aG himds whoami

In fact, this line should’ve been included into onboarding script itself.

And that’s about it. With little resources and some Terraform we can connect arbitrary VM to Azure and grant it passwordless RBAC permissions in a fully automated fashion. And it’s fast. And free. So use it, while it lasts.

Leave a Reply

Your email address will not be published. Required fields are marked *