In this module
IAM0.3 Access Governance Principles — Why Access Accumulates
IAM0.2 traced the five-stage identity lifecycle through your tenant and found the stages where governance is absent. This section explains why access accumulates when those stages are unmanaged — the mechanics of permission creep, the governance principles that prevent it, and the difference between governance that works and compliance theater that produces reports while leaving access untouched.
Access accumulates by default — removal requires effort
Every access grant in your tenant is an additive operation. Someone adds a user to a group. A manager approves an access request. An admin assigns a role. A developer registers an app and grants it API permissions. Each of these events increases the total access an identity holds. None of them have a corresponding automatic reversal. Access enters the tenant through dozens of channels — group assignment, role assignment, application consent, entitlement management, direct SharePoint permissions, Teams membership — and leaves through exactly one: someone deliberately removes it.
That asymmetry is the root cause of every permission creep finding in every audit. Access is easy to grant and hard to revoke. The reasons are structural, not behavioral. Nobody is negligent. The systems are designed to add access, not to question whether it's still needed. A group membership granted in 2023 persists in 2026 not because someone decided to keep it but because nothing in the lifecycle triggers its removal. The mover problem from IAM0.2 — Priya Sharma keeping finance access after transferring to SOC — isn't a process failure. It's the default outcome when stage 3 (governance) doesn't exist.
In this section you'll measure permission creep in your own tenant, examine the four governance principles that prevent it, and see how access reviews produce compliance evidence without governance value when the review process itself isn't governed.
Estimated time: 50 minutes.
Measuring permission creep — group membership over time
Permission creep is access accumulation that exceeds what the identity's current role requires. The IAM0.1 group membership trace showed Priya Sharma with 11 groups, several from completed projects and previous roles. That was one identity. Now measure the pattern across your tenant.
Access is granted at onboarding and during projects but never revoked when the need ends. A user who started with 4 groups holds 15 after three years — not because anyone decided they need 15 groups, but because nobody removed the 11 that accumulated. Annual access reviews approve everything because reviewers lack context. The permission creep is invisible until an auditor measures it.
Entra Admin Center
Identity → Users → All users → select a long-tenured user (someone employed 3+ years) → Groups
Count the groups. Then select a recently hired user and count theirs. The difference is permission creep made visible — the long-tenured user has accumulated groups over years of projects, role changes, and ad-hoc assignments. The recently hired user has only the onboarding baseline. The portal doesn't show when each membership was granted, but the difference in count between new and old employees tells the accumulation story.
The Graph API doesn't store historical group membership — you can't query "when was this user added to this group" without audit log access. But you can measure the current state and infer accumulation by comparing group counts against tenure:
$members = Get-MgUser -All -Property id, displayName, userPrincipalName,
userType, accountEnabled, createdDateTime |
Where-Object { $_.UserType -eq "Member" -and $_.AccountEnabled -eq $true }
$membershipData = $members | ForEach-Object {
$groupCount = (Get-MgUserMemberOf -UserId $_.Id -All -ErrorAction SilentlyContinue).Count
$tenureMonths = [math]::Round(((Get-Date) - $_.CreatedDateTime).TotalDays / 30)
[PSCustomObject]@{
UPN = $_.UserPrincipalName
TenureMonths = $tenureMonths
Groups = $groupCount
}
}
$brackets = @(
@{ Label = "0-6 months"; Min = 0; Max = 6 },
@{ Label = "6-12 months"; Min = 6; Max = 12 },
@{ Label = "1-2 years"; Min = 12; Max = 24 },
@{ Label = "2-3 years"; Min = 24; Max = 36 },
@{ Label = "3+ years"; Min = 36; Max = 9999 }
)
Write-Host "=== GROUP MEMBERSHIP BY TENURE ==="
foreach ($b in $brackets) {
$cohort = $membershipData | Where-Object {
$_.TenureMonths -ge $b.Min -and $_.TenureMonths -lt $b.Max
}
if ($cohort.Count -gt 0) {
$avg = [math]::Round(($cohort | Measure-Object -Property Groups -Average).Average, 1)
$max = ($cohort | Measure-Object -Property Groups -Maximum).Maximum
Write-Host " $($b.Label): $($cohort.Count) users, avg $avg groups, max $max"
}
}=== GROUP MEMBERSHIP BY TENURE ===
0-6 months: 47 users, avg 4.2 groups, max 8
6-12 months: 63 users, avg 6.1 groups, max 12
1-2 years: 189 users, avg 8.7 groups, max 19
2-3 years: 274 users, avg 11.3 groups, max 24
3+ years: 237 users, avg 14.8 groups, max 31Read the pattern. New employees (0-6 months) average 4.2 groups — roughly what onboarding assigns. By one to two years, the average has doubled to 8.7. By three or more years, it's 14.8 — more than triple the starting baseline. The maximum tells a sharper story: one identity with three or more years of tenure holds 31 group memberships.
That progression is permission creep visualized. Nobody granted 31 groups on day one. Each group was added for a reason — a project, a cross-team initiative, a temporary need, a role change. None were removed when the reason ended. The lifecycle's stage 2 (assignment) fires repeatedly. Stage 3 (governance) never fires at all.
Now find the identities with the most access:
$membershipData | Sort-Object Groups -Descending | Select-Object -First 10 |
Format-Table UPN, TenureMonths, Groups -AutoSizeUPN TenureMonths Groups
--- ------------ ------
admin.user@yourtenant.com 42 31
senior.eng@yourtenant.com 38 27
project.lead@yourtenant.com 36 24
it.manager@yourtenant.com 40 22
...The identities with the most access are the ones with the longest tenure and the broadest role history. That's not surprising — but it's the exact pattern that governance is designed to interrupt. Without periodic review, access grows monotonically with time. With review, access tracks to the current role.
At Northgate Engineering: Rachel Okafor runs the tenure-vs-groups analysis and finds the same escalating pattern. Phil Greaves, the IT Director (40 months tenure, 22 groups), holds memberships in engineering project groups, HR site access, finance reader access, and SOC operational groups — a cross-section of every department he's collaborated with over three years. When she asks Phil which of his 22 groups he actively uses, he identifies 6. The other 16 are residual — access from past projects, past responsibilities, past collaborations. Nobody asked whether he still needed them. Nobody had the mechanism to ask.
Four principles that prevent accumulation
Permission creep is the symptom. The absence of governance principles is the cause. Four principles, applied together, prevent access from accumulating beyond what each identity's current role requires. These aren't theoretical frameworks — they're the design criteria you'll use for every governance mechanism in this course.
Least privilege
Every identity should hold only the minimum access required for its current function. Not the access it needed six months ago. Not the access it might need for a future project. The access it needs right now, for the role it performs right now.
Least privilege is the principle most tenants claim to follow and fewest actually implement. Claiming least privilege while allowing 14.8 average group memberships after three years of tenure isn't least privilege — it's maximum accumulated privilege with a policy document that says otherwise. Least privilege requires an active mechanism that removes access when the justification ends. Without that mechanism, the principle is aspirational.
In this course, you'll implement least privilege through three governance tools: dynamic groups that adjust membership automatically when attributes change (Module 3), entitlement management with time-bound access packages that expire when the need ends (Module 4), and access reviews that certify remaining access on a schedule (Module 5).
Separation of duties
No single identity should hold permissions that create a conflict of interest. The identity that approves purchase orders shouldn't also process payments. The identity that creates user accounts shouldn't also assign privileged roles. The identity that configures security controls shouldn't also be the only reviewer of those controls.
Separation of duties is harder to implement than least privilege because conflicts aren't always obvious. A user who holds membership in both SG-Finance-Approvers and SG-Finance-Processors has a clear conflict. A user who holds Application Administrator and belongs to a group that governs app registration consent has a subtler one — they can both configure the consent policy and bypass it.
Query your tenant for identities that hold multiple privileged roles:
Entra Admin Center
Identity → Roles & admins → Roles & admins
Select Global Administrator. The Assignments tab shows every identity with this role. Note the names. Now go back and select User Administrator. Look for the same names. Any identity appearing in both lists holds multiple high-privilege roles — a separation of duties finding. Repeat for Exchange Administrator, SharePoint Administrator, and Authentication Administrator. The portal makes this a manual cross-reference exercise. The PowerShell query below automates it.
$privilegedUsers = Get-MgUser -All -Property id, displayName |
ForEach-Object {
$roles = Get-MgUserMemberOf -UserId $_.Id -All |
Where-Object { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.directoryRole' }
if ($roles.Count -gt 1) {
[PSCustomObject]@{
Name = $_.DisplayName
Roles = ($roles | ForEach-Object { $_.AdditionalProperties.displayName }) -join ", "
Count = $roles.Count
}
}
} | Where-Object { $_ }
Write-Host "Identities with multiple directory roles: $($privilegedUsers.Count)"
$privilegedUsers | Sort-Object Count -Descending | Format-Table -AutoSizeIdentities with multiple directory roles: 7
Name Roles Count
---- ----- -----
Admin User Global Administrator, Exchange Administrator, SharePoint... 4
IT Manager User Administrator, Groups Administrator, License Admin... 3
Security Lead Security Administrator, Compliance Administrator 2
...Each identity with multiple roles is a potential separation-of-duties concern. The identity with Global Administrator and three other roles doesn't need the other three — Global Administrator includes their permissions. That's not separation of duties. That's role accumulation. Module 7 addresses this with PIM governance — eligible roles instead of permanent assignments, approval workflows for activation, and review cadences that question whether each role is still needed.
Need-to-know
Access should be scoped to the information the identity needs, not the information the identity could theoretically use. A SOC analyst investigating an incident needs access to security logs, not to the HR SharePoint site. A finance analyst processing invoices needs access to the finance system, not to the engineering project repository.
Need-to-know is the data dimension of least privilege. Least privilege limits what the identity can do. Need-to-know limits what the identity can see. In M365, need-to-know is enforced through group-scoped SharePoint permissions, Teams channel membership, Purview sensitivity labels, and application-scoped access. The IAM0.1 finding — Priya Sharma holding SG-SharePoint-HR-Site access as a SOC analyst — is a need-to-know violation. She can see HR data her current role doesn't require.
Periodic certification
Access that was appropriate at the time of grant may not be appropriate today. Periodic certification is the mechanism that tests whether current access matches current need — not once, but on a recurring schedule. Quarterly for privileged access. Semi-annually for standard access. At minimum, annually for everything.
Certification works when the reviewer has context — they know what the access grants, they know whether the identity still needs it, and they have the authority to remove it. Certification fails when the reviewer lacks context, which is most of the time. That failure has a name.
The "Approve All" problem — governance vs compliance theater
Access reviews in Entra ID route to reviewers — typically the identity's manager or the group's owner — and ask them to approve or deny each access. The review produces a completion report. The report shows that 100% of items were reviewed. The audit is satisfied.
The problem is what happens during the review. A manager with 40 direct reports, each holding 8-12 group memberships, receives a review with 400+ items. The review interface shows each membership with the identity's name, the group name, and an approve/deny button. It doesn't show what the group grants access to. It doesn't show when the membership was assigned. It doesn't show whether the identity has used the access recently.
The manager has two options: spend three hours investigating each membership to make an informed decision, or click "Approve All" and finish in 60 seconds. The completion rate for both approaches is 100%. The governance value is zero for the second.
If your tenant has access reviews, you queried their state in IAM0.2. Now examine the completion pattern more closely. If you have review history, look at the time between review start and completion:
$reviews = Get-MgIdentityGovernanceAccessReviewDefinition -All -ErrorAction SilentlyContinue
if ($reviews) {
$reviews | ForEach-Object {
$instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance `
-AccessReviewScheduleDefinitionId $_.Id -All
$instances | ForEach-Object {
$decisions = Get-MgIdentityGovernanceAccessReviewDefinitionInstanceDecision `
-AccessReviewScheduleDefinitionId $_.Id `
-AccessReviewInstanceId $_.Id -All
$total = $decisions.Count
$approved = ($decisions | Where-Object { $_.Decision -eq "Approve" }).Count
$denied = ($decisions | Where-Object { $_.Decision -eq "Deny" }).Count
$noResponse = ($decisions | Where-Object { $_.Decision -eq "NotReviewed" }).Count
[PSCustomObject]@{
Review = $_.DisplayName
Items = $total
Approved = $approved
Denied = $denied
NoResponse = $noResponse
DenyRate = if ($total -gt 0) { "$([math]::Round($denied/$total*100, 1))%" } else { "N/A" }
}
}
} | Format-Table -AutoSize
} else {
Write-Host "No access reviews configured — cannot measure review quality."
Write-Host "This is itself a finding: Stage 3 (Governance) is absent."
}Review Items Approved Denied NoResponse DenyRate
------ ----- -------- ------ ---------- --------
Quarterly - Privileged Roles 18 18 0 0 0.0%
Annual - All Groups 342 338 2 2 0.6%
Guest Review - Q1 23 21 1 1 4.3%A deny rate below 5% across all reviews is the signature of "Approve All." An organization with 342 group memberships under review found exactly 2 that should be removed. That's a 0.6% deny rate. Either the initial access assignments are 99.4% accurate — which contradicts the tenure-vs-groups data showing systematic accumulation — or the reviewers aren't actually evaluating each membership. The second explanation is more likely.
The distinction matters because it separates governance from compliance theater. Both produce a completion report. Both satisfy an audit checkbox. Only one actually reduces access risk. Module 5 builds access reviews that work — reviews with context (what does this group grant?), with usage data (when did this identity last access this resource?), with AI-assisted recommendations (this user hasn't used this permission in 90 days — recommend remove), and with review quality monitoring that detects rubber-stamp patterns.
At Northgate Engineering: Rachel Okafor's annual group review completed with a 99.1% approval rate. She pulls the review log and finds that Tom Ashworth, reviewing access for his 12 SOC team members across 96 group memberships, approved all 96 in 47 seconds. He approved Priya Sharma's finance access, her Project Alpha access, and her 2023 migration project access without knowing what any of those groups grant. The review created an audit record showing 100% completion. It created zero governance value. The access remains exactly as it was before the review.
Governance vs compliance — the distinction that defines this course
Compliance is evidence that a process ran. Governance is evidence that the process produced the right outcome. An access review with 100% completion and 0% denial is compliant — the review ran, the reviewer responded, the report is generated. It's not governed — the access wasn't actually evaluated. A lifecycle workflow that fires on employeeHireDate is compliant when it executes. It's not governed if the hire date is wrong and the workflow fires on the wrong day, or if the workflow doesn't fire at all because 80% of accounts have no hire date.
This distinction runs through every module. When you build lifecycle workflows in Module 2, you'll validate that the workflow actually fired and produced the correct outcome — not just that the workflow exists. When you design access reviews in Module 5, you'll monitor review quality — not just completion. When you generate compliance evidence in Module 13, you'll produce evidence that the controls work, not just that they're configured.
The three diagnostic questions from IAM0.1 test for governance, not compliance. "Why does this identity have this access?" asks for the justification — a governed answer names the business need and the approval. A compliant answer says "it was granted." "When was this access last reviewed?" asks for the cadence — a governed answer gives the date and the reviewer's decision. A compliant answer says "we run annual reviews." "Who is accountable?" asks for the person — a governed answer names the owner. A compliant answer points to a policy document.
When you apply these principles across the course, the question is always governance: does the control produce the outcome it's designed to produce? Not: does the control exist?
Applying the principles — a decision point
Before you move on, work through this scenario using the four principles.
Your tenant has 214 groups. You've found that 67 are ownerless (IAM0.2), group membership averages 14.8 for identities with 3+ years tenure, and the annual access review completed with a 0.6% deny rate. A new CISO arrives and asks: "How do you know that every user has only the access they need?"
Think through what an honest answer looks like. You have the data from IAM0.1, IAM0.2, and this section. What does each governance principle say about the current state?
Least privilege: The tenure-vs-groups data shows access growing monotonically with time. An identity that started with 4 groups and now holds 15 hasn't been right-sized since creation. Least privilege requires active removal. The current state has no removal mechanism.
Separation of duties: Seven identities hold multiple directory roles. The admin with Global Administrator plus three other roles has accumulated privilege that exceeds any role definition. Without a roles review, the accumulation goes unquestioned.
Need-to-know: Priya Sharma holds HR site access as a SOC analyst. The annual review approved it. The reviewer didn't know what the group grants because the group has no description and the review interface doesn't show resource-level detail. Need-to-know requires context the current review process doesn't provide.
Periodic certification: The annual review exists but produces a 0.6% deny rate across 342 memberships. That's certification that ran, not certification that worked. The review cadence is annual — acceptable for low-risk access, too infrequent for privileged access or sensitive data.
The honest answer to the CISO is: we can't confirm least privilege. We have an access review that runs annually and completes with near-100% approval. The data suggests the review isn't evaluating access — it's confirming it. The tenant-vs-groups analysis, the ownerless group count, and the review deny rate each tell the same story: access accumulates, and nothing in the current lifecycle reverses it.
That's the gap this course closes. By Module 14, the answer to the CISO is a documented IAM program with lifecycle automation that adjusts access on role change, access reviews with context and usage data that produce meaningful deny rates, and governance metrics that measure whether the program is working — not just running.
At Northgate Engineering: Rachel Okafor presents the governance gap analysis to the new CISO using exactly this data. The CISO's response: "So the access review is an audit artifact, not a security control." Rachel confirms. The CISO asks for a remediation plan. Rachel's plan is the IAM program this course teaches — lifecycle workflows (Module 2), governed group architecture (Module 3), entitlement management with time-bound access (Module 4), access reviews redesigned with context and usage data (Module 5), and governance metrics that detect rubber-stamp patterns (Module 12). The conversation that started with "how do you know?" ends with "build me a program that can answer that question."
Reusable script — the permission creep and review quality diagnostics from this section:
# IAM0.3 — Permission Creep and Review Quality Diagnostic
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All",
"GroupMember.Read.All", "RoleManagement.Read.Directory",
"AccessReview.Read.All"
# 1. Group membership by tenure (permission creep indicator)
$members = Get-MgUser -All -Property id, displayName, userPrincipalName,
userType, accountEnabled, createdDateTime |
Where-Object { $_.UserType -eq "Member" -and $_.AccountEnabled -eq $true }
$brackets = @(
@{ Label = "0-6 months"; Min = 0; Max = 6 },
@{ Label = "6-12 months"; Min = 6; Max = 12 },
@{ Label = "1-2 years"; Min = 12; Max = 24 },
@{ Label = "2-3 years"; Min = 24; Max = 36 },
@{ Label = "3+ years"; Min = 36; Max = 9999 }
)
Write-Host "=== PERMISSION CREEP: GROUP MEMBERSHIP BY TENURE ==="
foreach ($b in $brackets) {
$cohort = $members | Where-Object {
$tenure = [math]::Round(((Get-Date) - $_.CreatedDateTime).TotalDays / 30)
$tenure -ge $b.Min -and $tenure -lt $b.Max
}
if ($cohort.Count -gt 0) {
$groups = $cohort | ForEach-Object {
(Get-MgUserMemberOf -UserId $_.Id -All -ErrorAction SilentlyContinue).Count
}
$avg = [math]::Round(($groups | Measure-Object -Average).Average, 1)
$max = ($groups | Measure-Object -Maximum).Maximum
Write-Host " $($b.Label): $($cohort.Count) users, avg $avg groups, max $max"
}
}
# 2. Multiple directory role holders (separation of duties)
Write-Host "`n=== SEPARATION OF DUTIES: MULTIPLE ROLE HOLDERS ==="
$members | ForEach-Object {
$roles = Get-MgUserMemberOf -UserId $_.Id -All |
Where-Object { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.directoryRole' }
if ($roles.Count -gt 1) {
Write-Host " $($_.DisplayName): $($roles.Count) roles — $(
($roles | ForEach-Object { $_.AdditionalProperties.displayName }) -join ', '
)"
}
}
# 3. Access review quality (if reviews exist)
Write-Host "`n=== ACCESS REVIEW QUALITY ==="
$reviews = Get-MgIdentityGovernanceAccessReviewDefinition -All -ErrorAction SilentlyContinue
if ($reviews) {
$reviews | ForEach-Object {
$instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance `
-AccessReviewScheduleDefinitionId $_.Id -All
$instances | ForEach-Object {
$decisions = Get-MgIdentityGovernanceAccessReviewDefinitionInstanceDecision `
-AccessReviewScheduleDefinitionId $_.Id `
-AccessReviewInstanceId $_.Id -All
$total = $decisions.Count
$denied = ($decisions | Where-Object { $_.Decision -eq "Deny" }).Count
$rate = if ($total -gt 0) { [math]::Round($denied/$total*100, 1) } else { 0 }
Write-Host " $($_.DisplayName): $total items, $denied denied ($rate% deny rate)"
if ($rate -lt 5 -and $total -gt 10) {
Write-Host " WARNING: Deny rate below 5% — possible rubber-stamp pattern"
}
}
}
} else {
Write-Host " No access reviews configured. Stage 3 governance: ABSENT."
}Decision-point simulation
Scenario 1. Your permission creep query reveals that the average user has 4.2 group memberships, but users who've been at the organization longer than 3 years average 7.8. The CISO wants to strip all "excess" memberships immediately. Why is that a bad idea, and what do you propose instead?
Bulk removal without review will break legitimate access. A user with 7.8 memberships might genuinely need 6 of them for their current role. The 1.8 "excess" memberships might include a project group they still contribute to. Propose a phased access review: identify users with above-average memberships, send their managers a review request listing each group with last-access data, and remove only the memberships managers confirm are unnecessary. Module 12 builds this into an automated access review program.
Scenario 2. Every quarter, managers receive access review notifications for their direct reports. Your data shows that 78% of reviews are completed in under 60 seconds with "Approve All" for every user. Is the access review program working?
No. A 60-second "Approve All" review is rubber-stamping, not governance. The manager isn't evaluating whether each person needs each access — they're clearing a task from their inbox. The fix isn't more reviews — it's better reviews. Reduce scope (review only high-privilege access quarterly, routine access annually), provide context (show last-access dates alongside each membership), and use ML recommendations that pre-flag memberships the user hasn't exercised. Module 12 addresses this with review design that makes meaningful evaluation the path of least resistance.
Scenario 3. A developer needs temporary access to a production database to debug an urgent issue. The normal access request process takes 48 hours. The developer's manager grants direct access with a promise to "remove it next week." Three months later, the access is still there. What governance principle failed, and how do you prevent it?
Time-bound access enforcement failed. The promise of manual removal is the weakest form of governance — it depends on a human remembering to act. The fix: entitlement management (Module 11) with time-bound access packages. The developer requests access for 4 hours. The package grants access and automatically revokes it when the time expires. No human action required for removal. Emergency access gets a 24-hour maximum with automatic revocation and an audit log entry.
You're reading the free modules of Identity and Access Management in Microsoft 365
The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts.