Terraform Entra ID Saves the Day by automating External User Invitations for Hack-a-Thon Tenant
I inherited some responsibility for the One Fleet Platform Hack-a-Thon, which kicks off tomorrow! Yes, I am so prepared! But its not all procrastination as some people might have signed up for the event very late. Anyways, I need to get all the project leads their own Azure Subscription and make them owners. Now, I probably could’ve automated this process as well but I didn’t think of it in time and I just bust out a bunch of Azure Subscriptions the old fashioned way using the Azure Portal. However, the problem is all of these new Subscription owners are Microsoft Corp Entra ID Tenant users and these new subscriptions are in a totally different Entra ID Tenant. That’s because we just rolled out this slick new dynamic subscription provisioning mechanism that allows us to create a Just-In-Time Entra ID Tenant that is tied to a Microsoft Corporate Billing Account.
Wonderful but that means in order for these people to take ownership of their subscriptions I need to first invite them to the Entra ID Tenant created for the Hack-a-Thon.
At first, I thought I might need to just create users.
resource "azuread_user" "users" {
count = length(var.users)
user_principal_name = var.users[count.index]
display_name = var.users[count.index]
}
But after that failed miserably.
│ Error: Could not create user
│
│ with azuread_user.users[0],
│ on main.tf line 3, in resource “azuread_user” “users”:
│ 3: resource “azuread_user” “users” {
│
│ `password` is required when creating a new user
I discovered this other little resource in the azuread provider.
resource "azuread_invitation" "users" {
count = length(var.hack_a_thon_users)
user_display_name = var.hack_a_thon_users[count.index]
user_email_address = var.hack_a_thon_users[count.index]
redirect_url = "https://portal.azure.com"
message {
additional_recipients = var.hack_a_thon_admins
body = var.hack_a_thon_welcome_message
}
}
As per the documentation you need to have the following permissions: When authenticated with a service principal, this resource requires one of the following application roles: User.Invite.All, User.ReadWrite.All or Directory.ReadWrite.All Which I totally setup.
However, I got this rather frustrating error refuting my claims.
unexpected status 401 (401 Unauthorized) with error: Unauthorized: Insufficient privileges to perform requested operation by the application ‘00000003–0000–0000-c000–000000000000’.
│ ControllerName=MSGraphInviteAPI, ActionName=CreateInvite, URL absolute path=/api/8faae0c7–6015–4cb1–9903–14419c50faa8/invites
The problem was that I set them up as Delegated permissions and since I am using an App Registration to perform the action i needed to set them up as “Application Permissions”.
I discovered this by simply searching for the Entra ID error message that Terraform returned back to me:
Unauthorized: Insufficient privileges to perform requested operation by the application ‘00000003–0000–0000-c000–000000000000’. │ ControllerName=MSGraphInviteAPI, ActionName=CreateInvite, URL absolute path=/api/8faae0c7–6015–4cb1–9903–14419c50faa8/invites
A simple Bing query produced the following Stack Overflow page.
Which, although is not Terraform related in anyway, it still yielded me the correct answer: change to Application permissions.
LET’S GO!!!
The last change that I made was to update the user_type on the invitation resources. I got a little paranoid that the default value of Guest would be somehow restricted to the point where it would block the Hack-a-Thon project teams from competing so I just updated it to Member.
resource "azuread_invitation" "users" {
count = length(var.hack_a_thon_users)
user_display_name = var.hack_a_thon_users[count.index]
user_email_address = var.hack_a_thon_users[count.index]
redirect_url = "https://portal.azure.com"
message {
additional_recipients = var.hack_a_thon_admins
body = var.hack_a_thon_welcome_message
}
user_type = "Member"
}
This was a destructive act. It appeared to drop-create every invitation. I think I made this update before any of the users had a chance to act on their invitation so one concern plagues my mind: what would happen if I deleted the invitation after the user had accepted it? Would it have removed the user’s access? Deep thoughts….
Update:
Welp, the users started accepting the invites and — wouldn’t you believe it — I started seeing some churn. It seems like the user_email_address was causing us some problems. Maybe this is unique to the way Microsoft handles the Entra ID principal name and email aliases but it seems like it would be a common enough problem that other organizations would likely run into it!
# azuread_invitation.users[17] must be replaced
-/+ resource "azuread_invitation" "users" {
~ id = "20cba6ad-9ed6-4fc0-a8a4-49b722eeddfd" -> (known after apply)
~ redeem_url = "https://login.microsoftonline.com/redeem?rd=FOOBAR" -> (known after apply)
~ user_email_address = "ksoze@microsoft.com" -> "keyser.soze@microsoft.com" # forces replacement
~ user_id = "8a5f59c3-715a-4ff0-8dc0-1bbc5d029104" -> (known after apply)
# (3 unchanged attributes hidden)
# (1 unchanged block hidden)
}
I had to modify my code to ignore_changes on the user’s email address.
resource "azuread_invitation" "users" {
count = length(var.hack_a_thon_users)
user_display_name = var.hack_a_thon_users[count.index]
user_email_address = var.hack_a_thon_users[count.index]
redirect_url = "https://portal.azure.com"
message {
additional_recipients = var.hack_a_thon_admins
body = var.hack_a_thon_welcome_message
}
user_type = "Member"
lifecycle {
ignore_changes = [user_email_address]
}
}
Anyways, super excited to get started on the Hack-a-Thon!