<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Rahiel Akhtar]]></title><description><![CDATA[A field guide to making PIM eligible role assignments permanent with Terraform. Covers the role management policy lever, prerequisites, and gotchas.]]></description><link>https://rahielakhtar.no</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Rahiel Akhtar</title><link>https://rahielakhtar.no</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:31:01 GMT</lastBuildDate><atom:link href="https://rahielakhtar.no/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Application Gateway WAF exceptions are in preview — here’s where they fit]]></title><description><![CDATA[Intro
Azure Application Gateway’s Web Application Firewall (WAF) has a new tool in its toolbox, exceptions are now in public preview. We already have other tools for allowing traffic: custom rules, ex]]></description><link>https://rahielakhtar.no/application-gateway-waf-exceptions-are-in-preview-here-s-where-they-fit</link><guid isPermaLink="true">https://rahielakhtar.no/application-gateway-waf-exceptions-are-in-preview-here-s-where-they-fit</guid><category><![CDATA[Azure Application Gateway]]></category><category><![CDATA[Web Application Firewall]]></category><category><![CDATA[preview]]></category><category><![CDATA[networking]]></category><dc:creator><![CDATA[Rahiel Akhtar]]></dc:creator><pubDate>Tue, 13 Oct 2026 10:55:45 GMT</pubDate><content:encoded><![CDATA[<h2>Intro</h2>
<p>Azure Application Gateway’s Web Application Firewall (WAF) has a new tool in its toolbox, exceptions are now in public preview. We already have other tools for allowing traffic: custom rules, exclusions, and disabling rules. Exceptions allow you to bypass WAF inspection for specific requests.</p>
<h2>The four-tool comparison</h2>
<p>Start by checking the WAF logs to confirm the false positive, then apply the right control.</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Protection removed</th>
<th>How it works</th>
<th>When to use it</th>
</tr>
</thead>
<tbody><tr>
<td>Exclusions</td>
<td>Minimal</td>
<td>Prevents <strong>specific request attributes</strong> from being inspected, while the rest of the request is still evaluated</td>
<td>For excluding a specific request attribute — a header, cookie, or parameter — that causes a false positive. Ideal for excluding the <strong>problematic</strong> data while letting WAF protect the rest of the request</td>
</tr>
<tr>
<td>Exceptions <em>(preview)</em></td>
<td>Narrow</td>
<td>Bypass <strong>specific managed rules, rule groups, or whole rulesets</strong> for requests matching defined conditions</td>
<td>When legitimate requests trip false positives and you need to bypass specific rules for narrow cases without loosening the whole policy</td>
</tr>
<tr>
<td>Custom rules</td>
<td>Variable</td>
<td>Custom conditions, evaluated in priority order, to allow, block, log, or rate-limit requests before managed rules run</td>
<td>To enforce application-specific security requirements not covered by managed rules</td>
</tr>
<tr>
<td>Disabling rules</td>
<td>Broad</td>
<td>Changes that rule to <strong>Disabled</strong>, so it stops inspecting any request covered by the WAF policy, while other enabled rules are unaffected</td>
<td>Only for consistently confirmed false positives where no finer tool fits</td>
</tr>
</tbody></table>
<h2>Why exceptions are interesting</h2>
<p>Unlike exclusions, which exclude specific request attributes from inspection, exceptions control which security rules are evaluated for matching requests.</p>
<p>In practice, that means you can keep a rule fully active for all traffic and skip it only for the narrow, identifiable requests that trip it falsely. For example, a specific API path like /api/search that legitimately sends payloads the SQLi rules flag — you scope the exception to that path, and the rule keeps protecting everything else.</p>
<h2>Caveats</h2>
<p>Choosing the right tool takes some evaluation. With exceptions specifically, expect to iterate: because they’re so narrow, you often end up discovering request by request what you need to open up — the rule stays fully active and keeps blocking everything else (assuming you’re running WAF in prevention mode).</p>
<p>The exceptions feature has some limitations that are important to know about.The current documentation provides details on theese.</p>
<ul>
<li><p>Each WAF policy supports up to 60 exceptions.</p>
</li>
<li><p>An Application Gateway can have a total of 60 exceptions, regardless of how many WAF policies are associated with the gateway.</p>
</li>
<li><p>Within a single exception, there are limits of 600 IP addresses, or 10 URIs, or 10 request headers.</p>
</li>
</ul>
<h2>Referances</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-exceptions?tabs=portal">Microsoft Learn - Azure Application Gateway WAF exceptions list</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[What's actually crossing your ExpressRoute? Traffic Collector, with IaC]]></title><description><![CDATA[Introduction
ExpressRoute traffic visibility is a gap most Azure observability doesn't fill - As a platform engineer i'll be sharing my findings and observations setting up ExpressRoute Traffic Collec]]></description><link>https://rahielakhtar.no/what-s-actually-crossing-your-expressroute-traffic-collector-with-iac</link><guid isPermaLink="true">https://rahielakhtar.no/what-s-actually-crossing-your-expressroute-traffic-collector-with-iac</guid><category><![CDATA[Expressroute]]></category><category><![CDATA[Hybrid Networking]]></category><category><![CDATA[observability]]></category><category><![CDATA[traffic collector]]></category><category><![CDATA[azapi]]></category><category><![CDATA[expressroute circuit]]></category><dc:creator><![CDATA[Rahiel Akhtar]]></dc:creator><pubDate>Mon, 21 Sep 2026 15:27:44 GMT</pubDate><content:encoded><![CDATA[<h2>Introduction</h2>
<p>ExpressRoute traffic visibility is a gap most Azure observability doesn't fill - As a platform engineer i'll be sharing my findings and observations setting up ExpressRoute Traffic Collector using infrastructure as code with the AzApi provider. Hopefully by the end of this, you will be familiar with what it does, if it makes sense to set it up in your platform and see a practical IaC example</p>
<h2>What challenges does it solve?</h2>
<p>Traffic Collector makes sense to evaluate when your operational question is specifically about the hybrid boundary.</p>
<ol>
<li><p><em>What traffic is actually crossing my ExpressRoute?</em></p>
</li>
<li><p><em>Who are the top talkers?</em></p>
</li>
<li><p><em>Which source/destination pairs are consuming the circuit?</em></p>
</li>
</ol>
<p>It solves the visibility gap between on-premises and Azure. Traffic Collector observes <em>sampled flows</em> at the Microsoft Enterprise Edge (MSEE) with a rate of 1:4096 and aggregates flows - so it is not packet capture and shouldn't be treated as a complete record of every connection. It handles the observation independently of how traffic is routed inside Azure. For comparison, in my previous writing I covered VNet flow logs - those primarily solve the challenge of <em>What communicates inside our Azure VNets</em>.</p>
<p>I would highly recommend having a look at <a href="https://blog.cloudtrooper.net/2024/06/25/expressroute-traffic-visibility-flow-logs-or-traffic-collector/">Jose Morenos article on ExpressRoute traffic visibility</a> to gain a deeper understanding about the differences between VNet Flow Logs and ExpressRoute Traffic Collector.</p>
<h2>Overview of Traffic Collector</h2>
<p>The Collector sits at the MSEE routers and provides flow-level visibility for traffic traversing an ExpressRoute circuit. It samples flow metadata such as source and destination, ports, protocol and traffic volumes for traffic crossing the ExpressRoute private <em>or</em> Microsoft peering. The collected records are aggregated, and can be exported to Log Analytics, Storage, or Event Hubs.</p>
<h2>Infrastructure as code setup - AzApi</h2>
<p>The setup has three parts to ensure the creation of the Traffic Collector instance, collector policy and a diagnostic setting to send the logs to the desired destination - in my example, Log Analytics Workspace.</p>
<pre><code class="language-hcl">// Creating the ExpressRoute Traffic Collector resource on the circuit
resource "azapi_resource" "er_traffic_collector" {
  type      = "Microsoft.NetworkFunction/azureTrafficCollectors@2022-11-01"
  name      = var.er_traffic_collector_name
  parent_id = var.parent_id
  location  = var.location
  tags      = var.tags
}

// Creating the collector policy for the ExpressRoute Traffic Collector and defining its ingestion and emission policies
resource "azapi_resource" "er_traffic_collector_policy" {
  type      = "Microsoft.NetworkFunction/azureTrafficCollectors/collectorPolicies@2022-11-01"
  name      = var.er_traffic_collector_policy_name
  parent_id = azapi_resource.er_traffic_collector.id
  location  = var.location

  depends_on = [azapi_resource.er_traffic_collector]

  body = {
    properties = {
      ingestionPolicy = {
        ingestionType = "IPFIX"
        ingestionSources = [
          {
            resourceId = var.er_circuit_id
            sourceType = "Resource"
          }
        ]
      }
      emissionPolicies = [
        {
          emissionType = "IPFIX"
          emissionDestinations = [
            {
              destinationType = "AzureMonitor"
            }
          ]
        }
      ]
    }
  }
}


// Sending the IPFIX data to Log Analytics Workspace
resource "azapi_resource" "er_traffic_collector_to_law" {
  type      = "Microsoft.Insights/diagnosticSettings@2021-05-01-preview"
  name      = "er-traffic-collector-to-law"
  parent_id = azapi_resource.er_traffic_collector.id

  depends_on = [azapi_resource.er_traffic_collector_policy]

  body = {
    properties = {
      workspaceId = var.log_analytics_workspace_id
      logs = [
        {
          category = "ExpressRouteCircuitIpfix"
          enabled  = true
        }
      ]
    }
  }
}
</code></pre>
<p>A few things worth noticing:</p>
<ul>
<li><p><code>depends_on</code> is needed to ensure the policy is created after the Traffic Collector instance.</p>
</li>
<li><p>Traffic Collector — <code>parent_id</code> = var.parent_id (the RG ID for the ExpressRoute Circuit)</p>
</li>
<li><p>``destinationType` in the collector policy accepts AzureMonitor. If you need Event Hubs or Storage, that's handled through a separate diagnostic setting rather than through this body.</p>
</li>
<li><p>Verify that you aren't already sending the log category with diagnostic settings, If you are, the diagnostic settings code block isn't needed</p>
</li>
</ul>
<h1>Gotchas</h1>
<ul>
<li><p>Traffic Collector is currently supported for ExpressRoute Circuits with 1 gbps or greater - with private of Microsoft peering. No SKU restrictions.</p>
</li>
<li><p>The ExpressRoute circuit, the Traffic Collector, and the Log Analytics workspace must all sit in the same geo-political region.</p>
</li>
<li><p>The Traffic Collector resource itself is accessed from the ExpressRoute circuits blade. Diagnostic settings created by IaC land on the circuit.</p>
</li>
<li><p>Data starts flowing into Log Analytics within minutes, though how quickly depends on traffic volume — the 1:4096 sampling means low-volume periods may take longer to surface first records.</p>
</li>
</ul>
<p>This is part 2 on Azure network observability. That closes the planned series — more network observability posts will come as I run into things worth writing about.</p>
<h2>Referances</h2>
<h2>References</h2>
<ul>
<li><p><a href="https://blog.cloudtrooper.net/2024/06/25/expressroute-traffic-visibility-flow-logs-or-traffic-collector/">ExpressRoute traffic visibility: Flow Logs or Traffic Collector? — Jose Moreno (Cloudtrooper)</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.networkfunction/2022-05-01/azuretrafficcollectors?pivots=deployment-language-terraform">Microsoft.NetworkFunction/azureTrafficCollectors — Terraform reference</a></p>
</li>
<li><p><a href="https://github.com/Azure/terraform-provider-azapi/blob/0cbdf9e405df995ecc2da8d4d1e739c13333c186/internal/azure/generated/networkfunction/microsoft.networkfunction/2022-11-01/types.json#L602">azapi provider — NetworkFunction 2022-11-01 type definitions (GitHub)</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/en-us/azure/expressroute/how-to-configure-traffic-collector">Microsoft Learn Configure Traffic Collector for ExpressRoute</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Rolling out VNet flow logs and traffic analytics with EPAC across landing zones]]></title><description><![CDATA[Why look beyond firewall logs
Network monitoring beyond what flows through your firewall or NVA gives you a more holistic view of your traffic — patterns, trends, and what never touches the firewall i]]></description><link>https://rahielakhtar.no/rolling-out-vnet-flow-logs-and-traffic-analytics-with-epac-across-landing-zones</link><guid isPermaLink="true">https://rahielakhtar.no/rolling-out-vnet-flow-logs-and-traffic-analytics-with-epac-across-landing-zones</guid><category><![CDATA[VNet flow logs]]></category><category><![CDATA[Traffic analytics]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Azure Policy]]></category><category><![CDATA[epac]]></category><category><![CDATA[enterprise policy as code]]></category><category><![CDATA[AzureNetworking ]]></category><dc:creator><![CDATA[Rahiel Akhtar]]></dc:creator><pubDate>Fri, 28 Aug 2026 13:32:53 GMT</pubDate><content:encoded><![CDATA[<h2>Why look beyond firewall logs</h2>
<p>Network monitoring beyond what flows through your firewall or NVA gives you a more holistic view of your traffic — patterns, trends, and what never touches the firewall in the first place. That's where VNet flow logs and traffic analytics come in.</p>
<h2>Why VNet flow logs and Traffic Analytics</h2>
<p>VNet flow logs is a Network Watcher feature that captures network traffic for a Virtual Network and writes the raw output to a blob container in a designated storage account. This feature can be enabled at a VNet, subnet, or NIC level. The logs on their own aren't very useful, but paired with Traffic Analytics, the picture is clearer.</p>
<p>Traffic Analytics is what turns them into something you can work with, it ingests the raw logs, enriches them, and lands them in a Log Analytics workspace where you can query and visualise the data. A built-in dashboard covers the common views out of the box. Information such as allowed vs blocked, top talkers, and threat hits.</p>
<h2>why bother enabling them</h2>
<p>Each platform team should weigh the monitoring value setting this solution up against the cost — storage and Log Analytics ingestion aren't free, so decide before enabling broadly. A few questions that can help out:</p>
<ol>
<li><p>Do we need visibility into Azure traffic that does not traverse our firewall?</p>
</li>
<li><p>Do we need to troubleshoot workload connections — for example, from an Azure workload to a private endpoint?</p>
</li>
<li><p>Do we need visibility into inter-VNet traffic?</p>
</li>
</ol>
<h2>Automating deployment with Azure Policy</h2>
<p>In an enterprise environment with several spoke networks that each need a Network Watcher, you'll want to automate the deployment. Azure Policy has a built-in policy definition that does exactly that: <a href="https://www.azadvertizer.net/azpolicyadvertizer/3e9965dc-cc13-47ca-8259-a4252fd0cf7b.html">Configure virtual network to enable Flow Log and Traffic Analytics</a>.</p>
<p>Configuring this with Enterprise Policy as Code (EPAC) requires a policy assignment at your target scope that references the built-in policy.</p>
<p>A few things worth knowing about the assignment:</p>
<ol>
<li><p><code>policyId</code> hooks up the assignment with the correct policy definition</p>
</li>
<li><p>Flow logs are enabled per region, so each region needs its own assignment.</p>
</li>
<li><p><code>networkWatcherRG</code> and <code>networkWatcherName</code> values are hardcoded (explained below)</p>
</li>
<li><p>Retention can be 30 or 60 days.</p>
</li>
<li><p>You can centralise both: raw logs to a shared storage account, Traffic Analytics to a shared Log Analytics workspace.</p>
</li>
</ol>
<pre><code class="language-json">{
  "$schema": "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-assignment-schema.json",
  "nodeName": "/root",
  "definitionEntry": {
    "policyId": "/providers/Microsoft.Authorization/policyDefinitions/3e9965dc-cc13-47ca-8259-a4252fd0cf7b",
    "displayName": "Configure virtual network to enable Flow Log and Traffic Analytics"
  },
  "assignment": {
    "name": "Configure VNet Flow Logs and Traffic Analytics - Norway East",
    "displayName": "Configure VNet Flow Logs and Traffic Analytics - &lt;REGION&gt;",
    "description": "Deploys and enables VNet flow logs with Traffic Analytics for virtual networks in the specified region. Flow logs are written to a central storage account and Traffic Analytics data is sent to a Log Analytics workspace. Each subscription hosts its own Network Watcher flow log resource."
  },
  "metadata": {
    "assignedBy": "epac/&lt;ID&gt;/prod"
  },
  "parameters": {
    "effect": "DeployIfNotExists",
    "vnetRegion": "norwayeast",
    "storageId": "/subscriptions/&lt;SUBSCRIPTION_ID&gt;/resourceGroups/&lt;RESOURCE_GROUP&gt;/providers/Microsoft.Storage/storageAccounts/&lt;STORAGE_ACCOUNT&gt;",
    "timeInterval": "60",
    "workspaceResourceId": "/subscriptions/&lt;SUBSCRIPTION_ID&gt;/resourceGroups/&lt;RESOURCE_GROUP&gt;/providers/Microsoft.OperationalInsights/workspaces/&lt;LOG_ANALYTICS_WORKSPACE&gt;",
    "workspaceRegion": "&lt;REGION&gt;",
    "networkWatcherRG": "NetworkWatcherRG",
    "networkWatcherName": "NetworkWatcher_norwayeast",
    "retentionDays": "30"
  },
  "scope": {
    "prod": [
      "/providers/Microsoft.Management/managementGroups/&lt;MANAGEMENT_GROUP&gt;"
    ]
  }
}
</code></pre>
<h2>The naming-consistency dependency</h2>
<p>At one VNet, most of these are <em>quirks</em>. Across dozens of subscriptions and Virtual Networks, they're the difference between a working rollout and a broken one.</p>
<p>For this to scale, the values <code>networkWatcherRG</code>and <code>networkWatcherName</code> must match across subscriptions where there is a VNet you want to set up flow logs and Traffic Analytics. The assignment explicitly checks for Network Watchers in a Resource Group following this naming standard and if conditions are met the <code>DeployIfNotExists</code> effect will trigger. The compliance page will help you narrow down whether a subscription is missing the Resource Group entirely, or has the Resource Group but is missing the Network Watcher.</p>
<h3>A couple of smaller things worth knowing</h3>
<p><strong>Storage accounts can be private</strong></p>
<p>You might expect flow logs to require public network access on the storage account. They don't — you can lock the storage account down and put the blob service behind a private endpoint. Flow logs will still write.</p>
<p><strong>Data Collection Rule (DCR) and Data Collection Endpoint (DCE)</strong></p>
<p>Traffic Analytics deploys a Data Collection Rule and Data Collection Endpoint into the resource group containing the Log Analytics workspace. If you tightly control which resource types are allowed at that scope, make sure both are permitted. I had some issues debugging this part, because it does not show up in Activity Logs.</p>
<h2>References</h2>
<ul>
<li><p><a href="https://learn.microsoft.com/en-us/azure/network-watcher/vnet-flow-logs-overview">VNet flow logs overview</a></p>
</li>
<li><p><a href="https://www.azadvertizer.net/azpolicyadvertizer/3e9965dc-cc13-47ca-8259-a4252fd0cf7b.html">Built-in policy definition</a></p>
</li>
<li><p><a href="https://aidanfinn.com/?p=24580">Aidan Finn MVP - VNet Flow Logs</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Permanent PIM Eligible Role Assignment with Infrastructure as Code
]]></title><description><![CDATA[Permanent PIM Eligible Role Assignment with Infrastructure as Code
Introduction
This article will go over how to enable a PIM role assignment permanently without any expiration date for Azure resource]]></description><link>https://rahielakhtar.no/permanent-pim-eligible-role-assignment-with-infrastructure-as-code</link><guid isPermaLink="true">https://rahielakhtar.no/permanent-pim-eligible-role-assignment-with-infrastructure-as-code</guid><category><![CDATA[Terraform]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[IAM]]></category><category><![CDATA[pim]]></category><dc:creator><![CDATA[Rahiel Akhtar]]></dc:creator><pubDate>Mon, 06 Jul 2026 14:08:53 GMT</pubDate><content:encoded><![CDATA[<h1>Permanent PIM Eligible Role Assignment with Infrastructure as Code</h1>
<h2>Introduction</h2>
<p>This article will go over how to enable a PIM role assignment permanently without any expiration date for Azure resources with Terraform. You might encounter a 365-day limitation on PIM eligible role assignments. This article will show how to work around that.</p>
<h3>What I was trying to achieve</h3>
<p>While improving our security posture, we were switching from permanent normal role assignments to PIM eligible role assignments for our Entra ID groups. While enabling PIM on these, we ran into a constraint on the maximum eligible assignment duration: The management policy did not allow more than 365 days, so we couldn't set the assignment to have no expiration date. Our goal was to keep administrative overhead low and keep the PIM eligibility permanent on the assigned groups. Without a fix, we'd be renewing role assignments every 365 days, which at scale is untenable</p>
<h3>Prerequisites</h3>
<table>
<thead>
<tr>
<th>Requirement</th>
<th>Details</th>
</tr>
</thead>
<tbody><tr>
<td>Licensing</td>
<td>Entra ID P2 / Governance</td>
</tr>
<tr>
<td>Graph API permissions</td>
<td><code>Group.Read.All</code>, <code>Group.Create</code> (if also creating groups), <code>User.Read.All</code></td>
</tr>
<tr>
<td>Azure RBAC</td>
<td>User Access Administrator is sufficient at the resource group, subscription, or management group level for the identity running Terraform</td>
</tr>
<tr>
<td>Terraform provider version</td>
<td><code>azurerm</code> 4.79.0 or later</td>
</tr>
</tbody></table>
<h2>Understanding the components</h2>
<h3>PIM for Azure resources vs. Entra roles</h3>
<p>PIM can be enabled for Entra roles such as Global Administrator, Security Reader, etc., and the scope is tenant-wide.</p>
<p>But PIM can also be enabled for Azure resources that handle Azure RBAC roles such as Owner, Contributor, etc., and the scope can be set to RG, subscription, or management group level.</p>
<p>My scenario covers PIM eligibility for Azure resources.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a4bb49901674f58c01ace43/1bbc5890-f3c5-4b33-921a-f6e327224c99.png" alt="" style="display:block;margin:0 auto" />

<h3>Active vs. Eligible</h3>
<p>An active assignment means the user has the role right now and can use it without any activation step. This is what a normal <code>azurerm_role_assignment</code> grants, and also what <code>azurerm_pim_active_role_assignment</code> gives but under PIM governance.</p>
<p>An eligible assignment means the user is allowed to hold the role but does not have it until they explicitly activate it in PIM, also known as the just-in-time (JIT) model. This is what my scenario covers, with <code>azurerm_pim_eligible_role_assignment</code>.</p>
<h3>Role management policy</h3>
<p>A crucial component to understand is the role management policy. This ruleset governs how an eligible assignment or activation behaves. This policy handles multiple controls, one of which is <strong>eligibility expiration</strong>. This is set at the RG, subscription, or management group level and covers all assignments under the respective scope.</p>
<h2>Key observations</h2>
<h3>Provider version in lock file</h3>
<p>We had a lot of back and forth with <code>azurerm_pim_eligible_role_assignment</code> because of an older version of <code>azurerm</code> pinned in our lock file. That version's resource block didn't support all arguments — pin at least 4.79.0 (or latest) and you won't need to think about it.</p>
<h3>Existing management policy on the target scope</h3>
<p>Before editing, check the management policy already set at your target scope. I was never able to find a dedicated portal view for the underlying policy object itself — the closest is <a href="https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-resource-roles-configure-role-settings">Microsoft docs</a> on Azure resource role settings, which covers the same settings from the role-settings UI. A policy applied at a higher scope, like the subscription, is inherited by every resource group beneath it; it isn't scoped to just the one resource you're editing. That inheritance might make you wonder whether a permanent eligible assignment weakens protection further down. It doesn't: permanent eligibility only changes how long a principal is allowed to stay eligible, not what happens at activation — MFA, approval, and any narrower policy set at a lower scope still apply. What changes is who's responsible for cleaning up stale eligibilities, not which controls fire.</p>
<h3>Dependencies</h3>
<p>Terraform's apply order comes from resource attribute references, and here the two resources don't reference each other — they each just take <code>scope</code> and <code>role_definition_id</code> as separate input variables. Without an explicit dependency, Terraform is free to create the <code>azurerm_pim_eligible_role_assignment</code> before the <code>azurerm_role_management_policy</code> update has landed, which means the assignment gets created under whatever policy is already active on that scope. If that policy still requires an expiration, the apply fails on the same 365-day constraint from the introduction. Add <code>depends_on = [azurerm_role_management_policy.management_policy]</code> to the role assignment resource block so the policy is guaranteed to apply first.</p>
<h2>Terraform automation</h2>
<p>Below is an example of how to create:</p>
<ul>
<li><p>Entra ID group</p>
</li>
<li><p>Management policy with no expiration</p>
</li>
<li><p>PIM eligible role assignment with no expiration</p>
</li>
</ul>
<h3>Group creation</h3>
<pre><code class="language-hcl">resource "azuread_group" "group" {
  display_name     = var.group_name
  owners           = var.owners
  security_enabled = true
}
</code></pre>
<h3>Management policy creation with no expiration for PIM role assignments</h3>
<pre><code class="language-hcl">resource "azurerm_role_management_policy" "management_policy" {
  scope              = var.subscription_id
  role_definition_id = var.role_definition_id

  eligible_assignment_rules {
    expiration_required = false # Ensures expiration is not required in role assignment
  }
}
</code></pre>
<h3>PIM eligible role assignment creation without expiration</h3>
<pre><code class="language-hcl">resource "azurerm_pim_eligible_role_assignment" "pim_role_assignment" {
  scope              = var.subscription_id
  role_definition_id = var.role_definition_id
  principal_id       = var.principal_id
  depends_on         = [azurerm_role_management_policy.management_policy] # ensures assignment is compliant with new management policy
}
</code></pre>
<h3>IAM overview in portal</h3>
<p>The screenshot below shows both assignments in the Azure portal's IAM blade. One active permanent, one eligible permanent:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a4bb49901674f58c01ace43/f952e651-9b89-4d01-8f8b-0156692e1218.png" alt="" style="display:block;margin:0 auto" />

<h2>Conclusion</h2>
<p>If I did this again, I would understand and check the existing role management policy before touching anything. This is the most crucial part of the setup and requires a good understanding of the backend governance that is not visible in the Entra ID portal.</p>
<p>That upfront effort only pays off if you're repeating it: I'd recommend this approach to any team managing role assignments at scale, but not necessarily for a single role assignment — such a setup would be overkill.</p>
]]></content:encoded></item></channel></rss>