Managing Azure Active Directory with Terraform

Learn how to manage Azure AD resources including users, groups, applications, and service principals using Terraform

Azure Active Directory (Azure AD) is Microsoft’s cloud-based identity and access management service. This guide demonstrates how to manage Azure AD resources using Terraform.

Video Tutorial

Learn more about managing Azure Active Directory with Terraform in this comprehensive video tutorial:

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Global Administrator or Privileged Role Administrator permissions
  • Azure AD Premium P2 license (for some features)

Project Structure

terraform-azure-ad/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── ad/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── policies/
    └── conditional_access.json

Azure AD Configuration

Create modules/ad/main.tf:

# Azure AD Users
resource "azuread_user" "example" {
  user_principal_name = "user@${var.domain_name}"
  display_name        = "Example User"
  mail_nickname       = "user"
  password           = var.user_password

  force_password_change = true
  
  usage_location = "US"

  job_title    = "Software Engineer"
  department   = "Engineering"
  company_name = var.company_name

  show_in_address_list = true
  account_enabled     = true
}

# Azure AD Groups
resource "azuread_group" "example" {
  display_name     = "Example Group"
  description      = "Example security group"
  security_enabled = true
  
  owners = [
    data.azuread_client_config.current.object_id
  ]

  members = [
    azuread_user.example.object_id
  ]
}

# Azure AD Application
resource "azuread_application" "example" {
  display_name = "Example Application"
  
  web {
    homepage_url  = "https://example.com"
    redirect_uris = ["https://example.com/auth/callback"]

    implicit_grant {
      access_token_issuance_enabled = true
      id_token_issuance_enabled     = true
    }
  }

  required_resource_access {
    resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

    resource_access {
      id   = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" # User.Read
      type = "Scope"
    }
  }

  api {
    oauth2_permission_scope {
      admin_consent_description  = "Allow the application to access example on behalf of the signed-in user."
      admin_consent_display_name = "Access example"
      enabled                   = true
      id                        = random_uuid.scope.result
      type                      = "User"
      user_consent_description  = "Allow the application to access example on your behalf."
      user_consent_display_name = "Access example"
      value                     = "access_as_user"
    }
  }
}

# Service Principal
# The azuread provider's v2.0 upgrade renamed azuread_application's
# application_id attribute to client_id, and azuread_service_principal's
# application_id argument to client_id to match.
resource "azuread_service_principal" "example" {
  client_id = azuread_application.example.client_id

  app_role_assignment_required = false
  
  features {
    custom_single_sign_on_app = true
    enterprise_application    = true
  }

  tags = ["example", "terraform-managed"]
}

# App roles are declared inline on azuread_application (the standalone
# azuread_app_role resource was removed in the azuread provider's v2.0
# upgrade, the same breaking change that consolidated
# oauth2_permission_scope onto the application resource - see the api
# block above):
#
# resource "azuread_application" "example" {
#   # ...
#   app_role {
#     allowed_member_types = ["User", "Application"]
#     description          = "Example role for users"
#     display_name         = "ExampleRole"
#     enabled              = true
#     id                   = random_uuid.role.result
#     value                = "example.role"
#   }
# }

# Administrative Unit
resource "azuread_administrative_unit" "example" {
  display_name = "Example Admin Unit"
  description  = "Example administrative unit for managing resources"

  members = [
    azuread_user.example.object_id
  ]
}

# Conditional Access Policy
resource "azuread_conditional_access_policy" "example" {
  display_name = "Example Policy"
  state        = "enabled"

  conditions {
    client_app_types = ["all"]

    applications {
      included_applications = ["all"]
      excluded_applications = []
    }

    users {
      included_users = ["all"]
      excluded_users = [
        "GuestsOrExternalUsers"
      ]
    }

    platforms {
      included_platforms = ["all"]
      excluded_platforms = []
    }

    locations {
      included_locations = ["all"]
      excluded_locations = ["AllTrusted"]
    }
  }

  grant_controls {
    operator = "OR"
    built_in_controls = ["mfa"]
  }

  session_controls {
    application_enforced_restrictions_enabled = true
    cloud_app_security_policy                = "monitorOnly"
    sign_in_frequency                        = 4
    sign_in_frequency_period                 = "hours"
    persistent_browser_mode                  = "always"
  }
}

# Named Location
resource "azuread_named_location" "example" {
  display_name = "Example Location"
  ip_ranges = [
    "203.0.113.0/24",
    "198.51.100.0/24"
  ]
  country {
    countries_and_regions = [
      "US",
      "CA"
    ]
    include_unknown_countries_and_regions = false
  }
}

Authentication and Authorization

  1. OAuth2 Permissions

OAuth2 permission scopes are declared inline via the api.oauth2_permission_scope block on azuread_application (shown in the Azure AD Configuration section above) - the standalone azuread_application_oauth2_permission resource was removed in the azuread provider’s v2.0 upgrade.

resource "azuread_application_password" "example" {
  application_object_id = azuread_application.example.object_id
  display_name         = "Example Password"
  end_date_relative    = "8760h" # 1 year
}
  1. Role Assignments
resource "azuread_directory_role" "example" {
  display_name = "Application Administrator"
}

resource "azuread_directory_role_assignment" "example" {
  role_id             = azuread_directory_role.example.template_id
  principal_object_id = azuread_user.example.object_id
}

Security Configuration

  1. Authentication Methods
resource "azuread_authentication_strength_policy" "example" {
  display_name = "Example Auth Policy"
  description  = "Example authentication strength policy"

  allowed_combinations = ["password", "microsoftAuthenticator"]
}

resource "azuread_authentication_method_policy" "example" {
  authentication_method_target {
    target = "all_users"
    authentication_method_target_state {
      state = "enabled"
      authentication_method_target_settings {
        setting_name  = "numberMatchingRequired"
        setting_value = "true"
      }
    }
  }
}
  1. Identity Protection
resource "azuread_identity_protection_policy" "sign_in_risk" {
  display_name = "Sign-in risk policy"
  state        = "enabled"

  conditions {
    sign_in_risk_levels = ["high"]
    
    users {
      included_users = ["all"]
      excluded_users = []
    }
  }

  controls {
    operation_type = "block"
  }
}

Access Reviews

  1. Access Review Configuration
resource "azuread_access_review_schedule_definition" "example" {
  display_name = "Example Access Review"
  description  = "Review access to resources"

  scope {
    query = "/groups/id-guid"
    query_type = "MicrosoftGraph"
    query_root = "group"
  }

  reviewers {
    query = "/users/id-guid"
    query_type = "MicrosoftGraph"
    query_root = "user"
  }

  duration_in_days = 7
  
  recurrence {
    pattern {
      type     = "weekly"
      interval = 1
    }
    range {
      start_date = "2025-01-01"
      number_of_occurrences = 52
    }
  }
}

Best Practices

  1. Security

    • Enable MFA
    • Use conditional access
    • Implement least privilege
    • Regular access reviews
  2. Identity Management

    • Use groups for access
    • Implement naming standards
    • Regular cleanup
    • Monitor sign-ins
  3. Application Management

    • Secure client secrets
    • Review permissions
    • Monitor usage
    • Regular updates
  4. Governance

    • Implement policies
    • Regular audits
    • Document processes
    • Monitor changes

Advanced Features

  1. Custom Security Attributes
resource "azuread_custom_security_attribute_definition" "example" {
  attribute_name = "riskLevel"
  attribute_type = "String"
  
  is_collection = false
  use_presets  = false
  
  permitted_values = [
    "low",
    "medium",
    "high"
  ]
}
  1. Authentication Context
resource "azuread_authentication_context" "example" {
  display_name = "High Security Access"
  is_usable    = true
}

resource "azuread_conditional_access_policy" "auth_context" {
  display_name = "Require Strong Auth"
  state        = "enabled"

  conditions {
    client_app_types = ["all"]
    
    applications {
      included_applications = ["all"]
    }

    users {
      included_users = ["all"]
    }

    authentication_context {
      included_context_ids = [azuread_authentication_context.example.id]
    }
  }

  grant_controls {
    operator = "AND"
    built_in_controls = ["mfa", "compliantDevice"]
  }
}

Conclusion

You’ve learned how to manage Azure Active Directory using Terraform. This setup provides:

  • User and group management
  • Application registration
  • Security policies
  • Access controls

Remember to:

  • Monitor security
  • Review access regularly
  • Update policies
  • Maintain documentation