Issue expanding ad application
Community Note
- Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritise this request
- Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritise the request
- If you are interested in working on this issue or have submitted a pull request, please leave a comment
Terraform (and AzureAD Provider) Version
Terraform v1.0.2 on windows_amd64
- provider registry.terraform.io/arkiaconsulting/akc v1.1.1
- provider registry.terraform.io/hashicorp/azuread v1.6.0
- provider registry.terraform.io/hashicorp/azurerm v2.68.0
- provider registry.terraform.io/hashicorp/random v3.1.0
Affected Resource(s)
azuread_application
Terraform Configuration Files
code inside module
variable "ad_apps" {
type = set(object({
name = string
ad_display_name = string
logout_url = string
reply_URLs = list(string)
multiple_orgs = bool
optional_claims = set(string)
implicit_grant = bool
role_assignment = bool
has_secret = bool
# group_membership_claims = string
oauth2_perm_scope = set(object({
admin_consent_description = string
admin_consent_display_name = string
enabled = bool
type = string
user_consent_description = string
user_consent_display_name = string
value = string
}))
resource_accesses = set(object({
resource_id = string
accesses = set(object({
id = string
type = string
}))
}))
app_roles = set(object({
allowed_member_types = list(string)
description = string
display_name = string
is_enabled = bool
value = string
}))
}))
}
resource "random_uuid" "oauth2_perm_scope_id" {
for_each = { for v in local.oauth2_perm_scope_names : "${v.app_name}.${v.o2scope_value}" => v }
}
resource "azuread_application" "ad_app" {
lifecycle {
ignore_changes = [
identifier_uris
]
}
for_each = { for app in var.ad_apps : app.name => app }
display_name = each.value.ad_display_name
identifier_uris = []
prevent_duplicate_names = true
sign_in_audience = each.value.multiple_orgs ? "AzureADMultipleOrgs" : "AzureADMyOrg"
dynamic "optional_claims" {
for_each = length(each.value.optional_claims) > 0 ? [1] : []
content {
dynamic "id_token" {
for_each = each.value.optional_claims
content {
name = id_token.value
}
}
}
}
web {
implicit_grant {
access_token_issuance_enabled = each.value.implicit_grant
}
logout_url = each.value.logout_url
redirect_uris = each.value.reply_URLs
}
dynamic "api" {
for_each = length(each.value.oauth2_perm_scope) > 0 ? [1] : []
content {
dynamic "oauth2_permission_scope" {
for_each = each.value.oauth2_perm_scope
content {
admin_consent_description = oauth2_permission_scope.value.admin_consent_description
admin_consent_display_name = oauth2_permission_scope.value.admin_consent_display_name
enabled = oauth2_permission_scope.value.enabled
id = random_uuid.oauth2_perm_scope_id["${each.value.name}.${oauth2_permission_scope.value.value}"].result
type = oauth2_permission_scope.value.type
user_consent_description = oauth2_permission_scope.value.user_consent_description
user_consent_display_name = oauth2_permission_scope.value.user_consent_display_name
value = oauth2_permission_scope.value.value
}
}
}
}
dynamic "required_resource_access" {
for_each = each.value.resource_accesses
content {
resource_app_id = required_resource_access.value.resource_id
dynamic "resource_access" {
for_each = required_resource_access.value.accesses
content {
id = resource_access.value.id
type = resource_access.value.type
}
}
}
}
dynamic "app_role" {
for_each = each.value.app_roles
content {
allowed_member_types = app_role.value.allowed_member_types
description = app_role.value.description
display_name = app_role.value.display_name
is_enabled = app_role.value.is_enabled
value = app_role.value.value
}
}
}
output "ad_app" {
value = { for app in var.ad_apps : app.name => azuread_application.ad_app[app.name] }
}
code from main.tf
module "ad-1" {
source = "../../modules/ad"
ad_apps = local.ad_apps_1
}
module "ad-2" {
source = "../../modules/ad"
ad_apps = local.ad_apps_2
}
code from locals.tf
locals {
ad_apps_1 = [
{
name = local.fileupload_app.name
ad_display_name = local.fileupload_app.ad_display_name
reply_URLs = []
logout_url = null
multiple_orgs = false
optional_claims = []
implicit_grant = false
role_assignment = false
has_secret = true
# group_membership_claims = "All"
oauth2_perm_scope = [
{
admin_consent_description = "Allows to upload files"
admin_consent_display_name = "Upload files"
enabled = true
type = "Admin"
user_consent_description = "Allows to upload files"
user_consent_display_name = "Upload files"
value = "File.Upload"
},
{
admin_consent_description = "Allows to download files"
admin_consent_display_name = "Download files"
enabled = true
type = "Admin"
user_consent_description = "Allows to download files"
user_consent_display_name = "Download files"
value = "File.Download"
},
]
resource_accesses = [
{
resource_id = local.microsoftGraph_app_id
accesses = [
{
id = local.microsoftGraph_resource_scope_user_read
type = "Scope"
}
]
}
]
app_roles = []
},
{
name = local.process_app.name
ad_display_name = local.process_app.ad_display_name
reply_URLs = []
logout_url = null
multiple_orgs = false
optional_claims = []
implicit_grant = false
role_assignment = false
has_secret = true
# group_membership_claims = "All"
oauth2_perm_scope = [
{
admin_consent_description = "fullaccess"
admin_consent_display_name = "fullaccess"
enabled = true
type = "Admin"
value = "fullaccess"
user_consent_description = "fullaccess"
user_consent_display_name = "fullaccess"
},
]
resource_accesses = [
{
resource_id = local.microsoftGraph_app_id
accesses = [
{
id = local.microsoftGraph_resource_scope_user_read
type = "Scope"
}
]
}
]
app_roles = [
{
allowed_member_types = ["User", "Application"]
description = "requestaccess"
display_name = "requestaccess"
is_enabled = true
value = "requestaccess"
}
]
},
]
ad_apps_2 = [
{
name = local.back_app.name
ad_display_name = local.back_app.ad_display_name
reply_URLs = []
logout_url = null
multiple_orgs = false
optional_claims = []
implicit_grant = false
role_assignment = false
has_secret = true
# group_membership_claims = "All"
oauth2_perm_scope = [
{
admin_consent_description = "Allows the app to communicate with api in full access mode."
admin_consent_display_name = "Full access"
user_consent_description = "Allows the app to communicate with api in full access mode."
user_consent_display_name = "Full access"
enabled = true
type = "User"
value = "fullaccess"
},
]
resource_accesses = [
{
resource_id = module.ad-1.ad_app[local.process_app.name].application_id
accesses = [
{
id = tolist(module.ad-1.ad_app[local.process_app.name].app_role)[0].id
type = "Role"
},
{
id = tolist(module.ad-1.ad_app[local.process_app.name].oauth2_permissions)[0].id
type = "Scope"
}
]
},
{
resource_id = module.ad-1.ad_app[local.fileupload_app.name].application_id
accesses = [
{
id = tolist(module.ad-1.ad_app[local.fileupload_app.name].oauth2_permissions)[0].id
type = "Scope"
},
{
id = tolist(module.ad-1.ad_app[local.fileupload_app.name].oauth2_permissions)[1].id
type = "Scope"
}
]
}
]
app_roles = []
}
]
}
Debug Output
https://gist.github.com/elthanor/1b330fcf205581ff43a0b728be351036
Panic Output
Expected Behavior
New AD applications should be created with the defined configuration. Check the complexity between the 2 modules and their relationship. Basically the 1st module creates some applications with roles/oauth2 perms that the 2nd module will use.
Actual Behavior
Fails with pasted debug output.
Steps to Reproduce
In a new account with clean state run:
terraform apply
Important Factoids
References
- #0000
Hi @elthanor, thanks for reporting this issue. I have tried to reproduce but unfortunately the configuration provided is incomplete. In the module config, there is a local referenced local.oauth2_perm_scope_names which isn't defined, and I'm not sure which permissions this is supposed to represent. Could you clarify this or try to reduce the configuration to the minimum required to reproduce the error? This will help us to narrow down the root cause. Thanks!
Hello @manicminer ,
here is the missing main.tf inside the module
variable "resource_group_name" { type = string }
variable "location" { type = string }
variable "tags" { type = map(string) }
variable "environment" { type = string }
variable "key_vault_id" { type = string }
locals {
identity_type = "SystemAssigned"
defaultDomain = data.azuread_domains.defaultDomain.domains[0].domain_name
identifier_uri_prefix = "https://${local.defaultDomain}/${var.environment}/apps"
oauth2_perm_scope_names = flatten([for app in var.ad_apps : [
for o2scope in app.oauth2_perm_scope : {
o2scope_value = o2scope.value
app_name = app.name
}]])
}
data "azuread_domains" "defaultDomain" {
only_default = true
}
In short what the issue seems to be is an ouput ad application cannot properly expand its roles/oauth2perms. Most of the times it fails with the attached logs I am sure this can be replicated with even shorter code but I lack the time to trim this down currently. Let me know if its necessary for you though
@elthanor Circling back on this, now that we have a few 2.x releases out the door, are you able to confirm if you're still seeing this in the latest release (2.11.0 at time of writing)? A lot of changes went into the schema here and I'm hoping this has cleared up this bug. If not, I'll be happy to investigate further. Thanks!
Closing this one out due to inactivity and major release since original report