terraform-provider-azuread icon indicating copy to clipboard operation
terraform-provider-azuread copied to clipboard

Issue expanding ad application

Open kousourakis opened this issue 4 years ago • 3 comments

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:

  1. terraform apply

Important Factoids

References

  • #0000

kousourakis avatar Jul 20 '21 16:07 kousourakis

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!

manicminer avatar Jul 27 '21 21:07 manicminer

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

kousourakis avatar Jul 29 '21 11:07 kousourakis

@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!

manicminer avatar Dec 02 '21 22:12 manicminer

Closing this one out due to inactivity and major release since original report

manicminer avatar Nov 15 '23 12:11 manicminer