After publishing my initial runbook exploring this topic, I decided to test out and implement the HashiCorp for_each
meta-argument method for managing the Azure AD User base of a production environment I'm currently working with. I wanted to share my findings from that experience here. In this 2nd part of my series on Azure AD & RBAC with Terraform, I define the requirements necessary for setting up Azure AD User and Group administration using this alternative method. I've also highlighted some tips from issues I ran into during my implementation of this and a Validation section containing helpful commands for post-checkouts and troubleshooting. While the initial runbook and demo in this series still contains useful information such as a deep dive on the security behind Azure AD and RBAC and some test login scenarios, I've found this CSV file method to be much more efficient at managing users and group membership. Also, I've refined the security privileges around the GitHub Actions SPN and have implemented the creation and management of the SPN, including its API permissions, using Terraform resource blocks.
/azuread-users-groups-roles-pt2
/azure-dev-infra
az login
export ARM_SUBSCRIPTION_ID=$(az account show --query id | xargs)
export ARM_ACCESS_KEY="<insert storage account access key used for backend state configs>"
For enterprise environments with multiple subscriptions, run the following to get the specific SubID you need: az account list --query '[].{SubID:id, SubName:name}' -o table
gh-actions-runbooks-ad
SPN and its required API permissions configured in the apps.tf
file under the azure-dev-infra
directory. This will be used to perform the terraform plan output in the GitHub PR comments. This config resides under this directory since the SPN is required for all other infrastructure plan outputs in the other directories of the repo.az ad app permission admin-consent --id <app_id value from outputs>
ARM_SUBSCRIPTION_ID="<sub_id>"
ARM_TENANT_ID="<tenant_id>"
ARM_CLIENT_ID="<app_id>"
ARM_CLIENT_SECRET="<auth_client_secret>"
In the previous runbook, step 4. mentioned assigning the SPN the User Admin AD role however, I found this is not required with the API Permissions configured to allow AD User, Group and Domain Reader access. The Subscription Owner RBAC role is not needed since we can assign the Subscription Reader RBAC role as we just need it to be allowed to read the data during the terraform plan.
users.csv
file with users. If you're importing an existing Azure AD user base into Terraform, navigate in the Portal UI to Azure AD > Users : Download users to capture a csv file of existing users. Extract the user data from the existing CSV file and populate the users.csv
Terraform file with the required fields: first_name,last_name,mail_nickname,preferred_language
The usage_location
value is required for users that are assgined Microsoft licenses such as O365
department
such as Art or Engineering if you'd like to auto-assign them to the Azure AD Groups created in this lab. The Art group uses Dynamic Membership to assign users requiring the Azure AD Premium P1 license. The Art group also includes a Conditional Access policy assignment enforcing MFA into the portal for all users in the group. The Engineering group resource block includes a for_each argument which loops through all users and for each user assigned to the Engineering department it assigns their membership to the Engineering group. The Engineering group does not require any Azure AD Premium licenses and is a nice option for automating group membership when you want to keep costs down and don't require the use of conditional access policies.github-actions
bot output in your PR comments which uses the gh-actions-runbooks-ad
SPN created in Step 2. Make adjustments to your code as needed.mail_nickname
value set in the users.csv
file. This identifier can be used for assigning Azure AD group owner/membership.lastname + first letter of first name + numerical value for length of first name + !123
123
as I found the Microsoft password length requirement was not met on users with last names less than 5 chars long. This should fix that issue.users.csv
file and be sure to remove any user identifiers (i.e., azuread_user.users["userarose"]
) manually assigned to Azure AD groups.terraform state list
terraform state show 'azuread_user.users["userarose"]'
az ad user list --query "[].{name:displayName,userPrincipalName:userPrincipalName, ObjectID:id}" -o tsv
az ad group list --query "[?contains(displayName,'Engineering')].{ name: displayName }" -o tsv
az ad group member list --group "Engineering" --query "[].{ name: displayName }" -o tsv
You now have much more efficient method for managing Azure AD Users using the CSV file and Terraform for_each
meta-arguments. Azure AD Group membership is dynamically configured using for_each
meta-arguments against department names assigned to users which excludes the requirement for purchasing Azure AD Premium P1 licenses. The SPN used by the Github Actions workflow is further locked down to adhere to the principal of least privileges and it's configured in the Terraform code. Having your AD users, groups and SPN's configured as code allows for consistency of settings and permissions across your infrastructure. It also increases a level of security awareness since PR's will require review/approval for code changes.