Identity Governance Automation: Access Reviews & Lifecycle Management voor Enterprise

IDENTITY GOVERNANCE User Lifecycle Onboarding Offboarding Access Reviews 12 Pending Entitlements 47 Active Access Packages 23 Packages Configured Approvals 8 Awaiting approval Compliance 95% Compliant

Identiteiten vormen het nieuwe aanvalsoppervlak. Zonder strak beheer stapelen gebruikersrechten zich op en ontstaat ‘access creep’ die audits ondermijnt. Microsofts Identity Security Report laat zien dat 75% van de organisaties hiermee worstelt. Voor Nederlandse overheden is dat een risico voor BIO 9.2 en 9.4, waar aantoonbare joiner-mover-leaver-processen en periodieke reviews verplicht zijn. Microsoft Entra ID Identity Governance combineert access reviews, entitlement management, lifecycle workflows en PIM in één platform. Deze gids beschrijft hoe je die bouwstenen inzet om onboarding, functiewijzigingen, offboarding en gastgebruikers automatisch te beheren, volledig afgestemd op BIO/AVG/NIS2 en integraties met HR- en ITSM-systemen.

Wat je leert

Deze gids laat zien hoe je identity governance als bestuurbare capability inricht: je leert hoe HR-systemen Entra ID voeden, hoe access reviews en entitlement packages elkaar versterken, hoe lifecycle workflows zero-touch onboarding mogelijk maken en hoe je die processen koppelt aan KPI's, dashboards en auditrapportages zodat BIO 9.2/9.4 en NIS2 zonder handmatig brandjes blussen aantoonbaar blijven.

Pro tip

Zorg dat elke access review een primaire én secundaire reviewer heeft. Een instelling raakte tijdelijk twaalf PIM-rollen kwijt omdat de enige reviewer ziek was en “no decision = deny” actief stond. Door managers + HR of applicatie-eigenaar + security als duo te configureren en escalaties in te stellen, voorkom je dat privileged toegang uitvalt tijdens incidentresponse.

Identity Governance Fundamentals: Het Joiner-Mover-Leaver Framework

Identity governance begint bij een eenduidig verhaal over wie toegang krijgt, waarom en hoe lang. Bestuurders willen aantoonbaar kunnen uitleggen dat HR-signalen, risicoklassen en technische configuraties elkaar versterken. Daarom definieer je eerst het joiner-mover-leaver-raamwerk dat door HR, security, functioneel beheer en operations wordt gedeeld. Het model beschrijft per persona welke gegevens uit AFAS, Youforce of SAP worden aangeleverd, welke beleidsregels van de Nederlandse Baseline voor Veilige Cloud gelden en welke controles de maatregel ondersteunen. Die governancearena zorgt ervoor dat elke wijziging in processen of tooling onmiddellijk een eigenaar heeft en dat audits steeds naar dezelfde set documentatie verwijzen.

In het joiner-stadium draait alles om voorspelbaarheid. Een nieuwe medewerker die zes weken voor de startdatum wordt aangemeld, moet op T-7 een voorlopig Entra ID-account, tijdelijke toegangscodes en licenties krijgen die aansluiten bij het rolprofiel. Lifecycle Workflows en Logic Apps lezen HR-webhooks, voeren validaties uit op incomplete dossiers en registreren waarom iemand toegang krijgt tot Teams, SharePoint, Power Platform of bedrijfsspecifieke SaaS. Conditional Access houdt het account passief totdat verplichte onboardingtaken, zoals MFA-registratie, security awareness en e-learning, zijn afgerond. Alle stappen worden gelogd met een correlatie-id zodat audits later exact kunnen herleiden welke automatismen toegang hebben toegekend.

Mover-scenario's vragen om directe herprovisioning. Wanneer een CISO, controllersafdeling of projectdirecteur een andere afdeling krijgt of een internationale opdracht overneemt, moeten oude groepen automatisch in een reviewstand komen terwijl nieuwe rechten via PIM of access packages worden geactiveerd. De workflow controleert of dataklassen veranderen (bijvoorbeeld van alleen vertrouwelijk naar staatsgeheim) en triggert aanvullende approvals. Door managerwijzigingen en cost center-updates in dezelfde flow te verwerken, zorg je dat rapportages, goedkeuringsketens en escalaties onmiddellijk opnieuw worden ingericht. Daarmee voldoe je aan BIO 9.4.5 zonder dat servicedesks tickets hoeven te openen.

Leaver-processen bepalen uiteindelijk of je als organisatie betrouwbaar bent. Zodra HR of een ITSM-workflow een vertrek meldt, blokkeert Entra ID het account, trekt refresh tokens in en zet mail en OneDrive klaar voor overdracht. Intune-startcommando's zorgen voor een selectieve wipe van beheerde devices, terwijl Logic Apps badgebeheer, parkeerrechten en ketenapplicaties aanstuurt. Na afloop van een bewaartermijn verwijdert de workflow het account definitief en archiveert hij een volledig bewijsdossier met hashes, zodat forensische teams kunnen aantonen dat toegangen netjes zijn ingetrokken, ook wanneer de organisatie met Woo-verzoeken of strafrechtelijke onderzoeken te maken krijgt.

De bouwstenen van Microsoft Entra ID maken van dat raamwerk een operationeel systeem. Access Reviews bieden periodieke recertificatie, Entitlement Management bundelt rechten tot begrijpelijke servicepakketten, Privileged Identity Management levert just-in-time beheerrechten en Lifecycle Workflows vormt de lijm tussen HR- en IT-systemen. Terms of Use, Conditional Access en authenticatorregistratie zorgen voor afdwingbare voorwaarden. Door deze capabilities via API's aan elkaar te knopen ontstaat een keten waarin business owners beslissingen nemen en securityteams zich kunnen richten op uitzonderingen in plaats van op elke individuele aanvraag.

Governance-teams borgen tenslotte de datakwaliteit en rapportage. Zij defineren KPI's zoals doorlooptijd per joiner, percentage movers dat binnen vier uur opnieuw is ingericht en aantal leavers dat binnen twee uur volledig is geblokkeerd. Deze cijfers worden gekoppeld aan de risicoregisters van het gemeentebestuur of de provincie, zodat de NBVC-doelstellingen direct zichtbaar zijn. Door maandelijks lessons learned te delen en veranderverzoeken via een backlog te verwerken, groeit identity governance uit tot een volwassen besturingsmodel dat interne audits, accountants en toezichthouders vertrouwen geeft.

Access Reviews: Automated Periodic Recertification

Access reviews zijn het mechanisme waarmee je bewijst dat toegang slechts tijdelijk is en constant wordt getoetst aan behoefte en risico. Een reviewprogramma begint daarom bij classificatie van bronnen. Kritieke groepen, PIM-rollen, Azure-subscripties, SaaS-applicaties en gastaccounts krijgen ieder een risicoprofiel met minimaal, normaal of verhoogd toezicht. Op basis daarvan bepaal je de frequentie: productiesystemen en financiele applicaties elk kwartaal, ondersteunende diensten halfjaarlijks en tijdelijke projectomgevingen soms zelfs maandelijks. Alles wordt vastgelegd in een register waarin ook compensating controls, bijvoorbeeld near-real-time loginsmonitoring, worden opgenomen.

De operationele inrichting vraagt aandacht voor menselijk gedrag. Reviewers krijgen duidelijke besluitcriteria, weten welke bronnen ze mogen raadplegen en ontvangen escalaties wanneer deadlines naderen. Veel organisaties configureren daarom primaire en secundaire reviewers, koppelen automatische herinneringen aan Outlook en Teams en gebruiken Power Automate om managers te helpen met voorbereide rapporten. Door "no decision = deny" te activeren en tegelijkertijd uitzonderingspaden naar ITSM te faciliteren, blijf je streng op beleid zonder dat kritieke ketens onverwacht uitvallen.

Automatisering haalt de ruis uit het proces. Graph API's of PowerShell-scripts genereren consistent ingerichte definities, zetten AI-aanbevelingen aan en schrijven resultaten terug naar Sentinel of Purview. Zo kun je aantonen dat inactieve leden, externe leveranciers zonder sponsor of PIM-rollen zonder activiteitslog automatisch worden verwijderd. Door connectors naar ServiceNow, TOPdesk of AFAS InSite te gebruiken, verschijnt elke verwijderingsactie ook als incident of wijziging, zodat de driehoek CISO-proceseigenaar-audit een bron van waarheid hanteert.

Monitoring en KPI-sturing maken het programma volwassen. Je meet afrondingspercentages per afdeling, aantal verwijderingen per cyclus, uitzonderingen die langer dan dertig dagen openstaan en toelichtingen die ontbreken. Power BI dashboards combineren die data met HR-statistieken, zodat je bijvoorbeeld ziet dat tijdelijke werknemers relatief vaak vergeten worden of dat een business unit structureel onder de tachtig procent afronding blijft. Deze inzichten stuur je maandelijks naar CIO, FG en auditcommittee, inclusief concrete herstelacties en lessons learned.

Een praktijkvoorbeeld laat zien waarom dit werkt. Een provincie liep achter met gastrecertificaties en kreeg van de auditdienst het oordeel "gedeeltelijk voldoende". Door de reviews te automatiseren, sponsorreminders te sturen en resultaten naar een Woo-bestendige SharePoint-bibliotheek te exporteren, ging het afrondingspercentage binnen twee kwartalen van 61 naar 98 procent. Tegelijkertijd halveerde het aantal onbeheerde externe accounts. Dit soort feiten onderbouwen BIO 9.4.1 en laten toezichthouders zien dat identity governance niet langer een papieren proces is maar een continu verbeterprogramma.

Tenslotte gebruik je de reviewdata om beleid aan te scherpen. Wanneer analytics aantonen dat specifieke groepen structureel geen wijzigingen opleveren, kun je die mogelijk verkleinen of vervangen door automatisch toegewezen rollen. Als reviews juist telkens noodgrepen nodig hebben, is het tijd om de onderliggende entitlement packages of lifecycle workflows te verbeteren. Zo ontstaat een feedbackloop waarmee access reviews meer zijn dan een complianceplicht: ze worden een stuurinstrument voor elke identity-beslissing in de organisatie.

Door scenario-oefeningen en tabletop-sessies in de reviewkalender op te nemen, borg je dat teams weten wat te doen bij grote uitzonderingen. Denk aan een leverancier die tijdens een incident tijdelijk extra rechten nodig heeft of aan een verkiezingsperiode waarin duizenden tijdelijke accounts worden geopend. Door vooraf drempelwaarden, communicatieformats en goedkeuringspaden vast te leggen, verloopt zo'n piek gecontroleerd en blijft het auditspoor intact. Het programma groeit daarmee mee met de veranderkalender van de organisatie in plaats van deze te vertragen.

powershell
# Create comprehensive access review program for BIO compliance # This deploys quarterly reviews for privileged groups, apps, and Azure roles Connect-MgGraph -Scopes "AccessReview.ReadWrite.All", "Directory.Read.All", "Group.Read.All" Write-Host "Deploying Enterprise Access Review Program" -ForegroundColor Cyan Write-Host "="*80 Write-Host "" # Define privileged groups requiring quarterly reviews $privilegedGroups = @( @{ Name = "Global-Administrators"; ReviewFrequency = "Quarterly" }, @{ Name = "Security-Administrators"; ReviewFrequency = "Quarterly" }, @{ Name = "Azure-Subscription-Owners"; ReviewFrequency = "Quarterly" }, @{ Name = "PIM-Eligible-Admins"; ReviewFrequency = "Quarterly" }, @{ Name = "Finance-Application-Admins"; ReviewFrequency = "Quarterly" }, @{ Name = "HR-Data-Access"; ReviewFrequency = "Quarterly" } ) # 1. Create access reviews for privileged groups Write-Host "[1/4] Creating group membership access reviews..." -ForegroundColor Yellow Write-Host "" foreach ($group in $privilegedGroups) { # Find group by name $azureAdGroup = Get-MgGroup -Filter "displayName eq '$($group.Name)'" if (-not $azureAdGroup) { Write-Warning " Group not found: $($group.Name)" continue } Write-Host " Creating review for: $($group.Name)" # Get group owners as reviewers $groupOwners = Get-MgGroupOwner -GroupId $azureAdGroup.Id if ($groupOwners.Count -eq 0) { Write-Warning " No group owners found. Using security team as fallback." $reviewers = @( @{ "@odata.type" = "#microsoft.graph.groupMembers" groupId = "security-team-group-id" # Fallback to security team } ) } else { $reviewers = @( @{ "@odata.type" = "#microsoft.graph.groupMembers" groupId = $azureAdGroup.Id query = "/groups/$($azureAdGroup.Id)/owners" } ) } # Add fallback reviewer (security manager) $fallbackReviewers = @( @{ "@odata.type" = "#microsoft.graph.singleUser" userId = "security-manager@contoso.nl" } ) # Create recurring quarterly access review $reviewParams = @{ displayName = "Quarterly Review: $($group.Name) Membership" descriptionForAdmins = "BIO 9.4.1 compliant quarterly review van $($group.Name) groepslidmaatschap" descriptionForReviewers = "Review en recertificeer alle groepsleden. Verwijder toegang als gebruiker deze niet meer nodig heeft." # Scope: All group members scope = @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/groups/$($azureAdGroup.Id)/members" queryType = "MicrosoftGraph" } # Reviewers: Group owners + fallback reviewers = $reviewers fallbackReviewers = $fallbackReviewers # Settings settings = @{ mailNotificationsEnabled = $true reminderNotificationsEnabled = $true justificationRequiredOnApproval = $true defaultDecisionEnabled = $true defaultDecision = "Deny" # Fail-secure: remove access if not reviewed instanceDurationInDays = 14 # 2 weeks to complete review autoApplyDecisionsEnabled = $true # Auto-remediation recommendationsEnabled = $true # Show AI recommendations recommendationLookBackDuration = "P90D" # Base recommendations on 90 days activity # Quarterly recurrence recurrence = @{ pattern = @{ type = "absoluteMonthly" interval = 3 # Every 3 months dayOfMonth = 1 # First day of quarter } range = @{ type = "noEnd" startDate = (Get-Date -Day 1).ToString("yyyy-MM-dd") } } } } try { $review = New-MgIdentityGovernanceAccessReviewDefinition -BodyParameter $reviewParams Write-Host " ✓ Review created: $($review.DisplayName)" -ForegroundColor Green Write-Host " Review ID: $($review.Id)" Write-Host " Frequency: Quarterly (every 3 months)" Write-Host " Duration: 14 days" Write-Host " Auto-remediation: Enabled" Write-Host " Default decision: Deny (if not reviewed)" -ForegroundColor Yellow } catch { Write-Error " Failed to create review: $($_.Exception.Message)" } Write-Host "" } # 2. Create access reviews for critical applications Write-Host "[2/4] Creating application access reviews..." -ForegroundColor Yellow Write-Host "" $criticalApps = @( @{ Name = "SAP ERP Production"; AppId = "app-id-1" }, @{ Name = "HR System (Workday)"; AppId = "app-id-2" }, @{ Name = "Finance Application"; AppId = "app-id-3" } ) foreach ($app in $criticalApps) { Write-Host " Creating review for application: $($app.Name)" # Get service principal for app $servicePrincipal = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'" if (-not $servicePrincipal) { Write-Warning " Service principal not found for app: $($app.Name)" continue } # Get app owners as reviewers $appOwners = Get-MgServicePrincipalOwner -ServicePrincipalId $servicePrincipal.Id $reviewParams = @{ displayName = "Quarterly Review: $($app.Name) Access" descriptionForAdmins = "Quarterly access review voor critical application: $($app.Name)" descriptionForReviewers = "Review alle users met toegang tot $($app.Name). Verwijder toegang als niet meer nodig." # Scope: All users with app role assignments scope = @{ "@odata.type" = "#microsoft.graph.principalResourceMembershipsScope" principalScopes = @( @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/users" queryType = "MicrosoftGraph" } ) resourceScopes = @( @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/servicePrincipals/$($servicePrincipal.Id)" queryType = "MicrosoftGraph" } ) } # Reviewers: App owners + security team reviewers = @( @{ "@odata.type" = "#microsoft.graph.groupMembers" groupId = "security-team-group-id" } ) settings = @{ mailNotificationsEnabled = $true reminderNotificationsEnabled = $true justificationRequiredOnApproval = $true defaultDecisionEnabled = $true defaultDecision = "Deny" instanceDurationInDays = 14 autoApplyDecisionsEnabled = $true recommendationsEnabled = $true recurrence = @{ pattern = @{ type = "absoluteMonthly" interval = 3 } range = @{ type = "noEnd" startDate = (Get-Date).ToString("yyyy-MM-dd") } } } } try { $review = New-MgIdentityGovernanceAccessReviewDefinition -BodyParameter $reviewParams Write-Host " ✓ Review created for $($app.Name)" -ForegroundColor Green } catch { Write-Error " Failed: $($_.Exception.Message)" } Write-Host "" } # 3. Create PIM role access reviews Write-Host "[3/4] Creating PIM eligible role reviews..." -ForegroundColor Yellow Write-Host "" $pimRoles = @( "Global Administrator", "Security Administrator", "Privileged Role Administrator", "Exchange Administrator", "SharePoint Administrator" ) foreach ($roleName in $pimRoles) { Write-Host " Creating PIM review for: $roleName" $role = Get-MgRoleManagementDirectoryRoleDefinition -Filter "displayName eq '$roleName'" if (-not $role) { Write-Warning " Role not found: $roleName" continue } $reviewParams = @{ displayName = "Quarterly PIM Review: $roleName Eligible Assignments" descriptionForAdmins = "BIO 9.4.2 compliant review van eligible PIM role assignments voor $roleName" descriptionForReviewers = "Review alle eligible assignments. Verwijder als user deze privileged role niet meer nodig heeft." # Scope: All eligible PIM assignments for this role scope = @{ "@odata.type" = "#microsoft.graph.principalResourceMembershipsScope" principalScopes = @( @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/users" queryType = "MicrosoftGraph" } ) resourceScopes = @( @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/roleManagement/directory/roleDefinitions/$($role.Id)" queryType = "MicrosoftGraph" } ) } # Reviewers: CISO + Security Managers reviewers = @( @{ "@odata.type" = "#microsoft.graph.singleUser" userId = "ciso@contoso.nl" } ) fallbackReviewers = @( @{ "@odata.type" = "#microsoft.graph.groupMembers" groupId = "security-managers-group-id" } ) settings = @{ mailNotificationsEnabled = $true reminderNotificationsEnabled = $true justificationRequiredOnApproval = $true defaultDecisionEnabled = $true defaultDecision = "Deny" # Critical: remove PIM eligibility if not reviewed instanceDurationInDays = 14 autoApplyDecisionsEnabled = $true recommendationsEnabled = $true recommendationLookBackDuration = "P90D" recurrence = @{ pattern = @{ type = "absoluteMonthly" interval = 3 } range = @{ type = "noEnd" startDate = (Get-Date).ToString("yyyy-MM-dd") } } } } try { $review = New-MgIdentityGovernanceAccessReviewDefinition -BodyParameter $reviewParams Write-Host " ✓ PIM review created for $roleName" -ForegroundColor Green } catch { Write-Error " Failed: $($_.Exception.Message)" } Write-Host "" } # 4. Create guest user access reviews Write-Host "[4/4] Creating guest user access reviews..." -ForegroundColor Yellow Write-Host "" Write-Host " Creating quarterly guest user review..." $guestReviewParams = @{ displayName = "Quarterly Review: Guest User Access" descriptionForAdmins = "Review all guest users (external users) voor continued business need" descriptionForReviewers = "Review externe gebruikers. Verwijder als ze geen toegang meer nodig hebben." # Scope: All guest users scope = @{ "@odata.type" = "#microsoft.graph.accessReviewQueryScope" query = "/users?`$filter=userType eq 'Guest'" queryType = "MicrosoftGraph" } # Reviewers: User's manager (for guests invited by employees) reviewers = @( @{ "@odata.type" = "#microsoft.graph.groupMembers" groupId = "security-team-group-id" } ) settings = @{ mailNotificationsEnabled = $true reminderNotificationsEnabled = $true justificationRequiredOnApproval = $true defaultDecisionEnabled = $true defaultDecision = "Deny" # Remove guest access if not reviewed instanceDurationInDays = 21 # 3 weeks (more time for guest reviews) autoApplyDecisionsEnabled = $true recommendationsEnabled = $true recurrence = @{ pattern = @{ type = "absoluteMonthly" interval = 3 } range = @{ type = "noEnd" startDate = (Get-Date).ToString("yyyy-MM-dd") } } } } try { $review = New-MgIdentityGovernanceAccessReviewDefinition -BodyParameter $guestReviewParams Write-Host " ✓ Guest user review created" -ForegroundColor Green } catch { Write-Error " Failed: $($_.Exception.Message)" } Write-Host "" Write-Host "="*80 Write-Host "Access Review Program deployment complete!" -ForegroundColor Green Write-Host "" Write-Host "Summary:" Write-Host " ✓ Group membership reviews: $($privilegedGroups.Count) privileged groups" Write-Host " ✓ Application access reviews: $($criticalApps.Count) critical apps" Write-Host " ✓ PIM role reviews: $($pimRoles.Count) privileged roles" Write-Host " ✓ Guest user review: All external users" Write-Host "" Write-Host "All reviews configured with:" Write-Host " • Quarterly recurrence (every 3 months)" Write-Host " • 14-21 day review period" Write-Host " • Auto-remediation enabled" Write-Host " • Fail-secure default (Deny if not reviewed)" Write-Host " • AI recommendations enabled" Write-Host "" Write-Host "Next steps:" -ForegroundColor Cyan Write-Host " 1. Go to Azure AD → Identity Governance → Access reviews" Write-Host " 2. First reviews start automatically based on schedule" Write-Host " 3. Reviewers receive email notifications 7 days before deadline" Write-Host " 4. Monitor completion rates monthly" Write-Host " 5. Address exceptions (no-response cases) within SLA"

Entitlement Management: Self-Service Access Packages

Entitlement Management vertaalt complexe toegangsstructuren naar begrijpelijke servicepakketten voor medewerkers, ketenpartners en leveranciers. Door alle bronnen die iemand voor zijn werk nodig heeft te bundelen, hoeven gebruikers nog maar een aanvraag te doen en ziet de sponsor meteen welke verantwoordelijkheden daarbij horen. Het My Access-portaal toont een catalogus waarin packages zijn ingedeeld naar proces, afdeling of projectfase. Elke aanvraag legt vast welke HR-gegevens, functies en compliance-eisen zijn gecontroleerd, waardoor auditors niet langer te maken hebben met losse excelbestanden of mailboxgoedkeuringen.

Voor interne medewerkers biedt dit vooral snelheid. Een projectpakket kan Teams-kanalen, Planner-borden, een SharePoint-site, DevOps-project en specifieke Power Platform-omgevingen bevatten. Zodra een medewerker het pakket aanvraagt, controleert de policy of de manager akkoord is, of de medewerker de juiste training heeft afgerond en of eventuele aanvullende voorwaarden, zoals geheimhoudingsclausules, zijn geaccepteerd. Toegang wordt standaard na een gedefinieerde periode ingetrokken, waarbij de gebruiker proactief een reminder krijgt en opnieuw moet motiveren waarom verlenging noodzakelijk is. Zo verdwijnen slapende rechten terwijl de business eigenaar de regie houdt.

Voor externe partijen en leveranciers is het model minstens zo krachtig. Een contractor-pakket kan automatisch een B2B-account creeren, gasttoegang beperken tot specifieke SharePoint-bibliotheken en een sponsor aanwijzen die elk kwartaal bevestigt dat de samenwerking nog loopt. Door expiraties op negentig dagen te zetten en sponsorherinneringen per Teams-message te sturen, voorkom je dat vergeten accounts jarenlang toegang behouden. Wanneer een aanbesteding eindigt, registreert het systeem automatisch welke data moeten worden teruggehaald, welke documenten naar het archief gaan en welke securitymaatregelen opnieuw moeten worden beoordeeld.

Kritieke applicaties vragen om extra borging. Voor financiele systemen of kernapplicaties van gemeenten combineer je managerapproval met second level-beoordeling door applicatiebeheer en een securityofficer. Justificaties worden verplicht gesteld, inclusief verwijzing naar proces-ID, change- of projectnummer. De policy kan eisen dat PIM-eligible rechten slechts gedurende de duur van een project bestaan en dat een access review start zodra een pakket langer dan zes maanden actief is. Hierdoor blijft het principe van minimale bevoegdheden gewaarborgd, zelfs in dynamische programma's zoals gebiedsontwikkeling of crisisrespons.

Dagelijks beheer vraagt om duidelijke rollen en automatisering. Catalog owners bepalen welke packages zichtbaar zijn, security monitort op dubbelen of achterhaalde resources en het identityteam gebruikt Graph-scripts om wijzigingen in bulk door te voeren. Elke wijziging wordt vastgelegd in een auditlog, inclusief versiebeheer van de beschrijving en de onderliggende resources. Door packages te koppelen aan Purview-classificaties en Sensitivity Labels weet je zeker dat gebruikers die zich aanmelden voor een pakket automatisch de juiste informatiebeschermingsregels meekrijgen.

Governance wordt compleet door rapportages en feedbackloops. KPI's zoals gemiddelde doorlooptijd, percentage auto-expirations en aantal afgewezen aanvragen geven inzicht in de volwassenheid van selfservice. Functioneel beheerders kunnen zien welke resources het meest worden aangevraagd en beslissen of een generiek pakket volstaat of dat maatwerk nodig is. Door elk kwartaal een design review te organiseren, blijven packages aansluiten op veranderende BIO- en NIS2-eisen en worden lessons learned uit audits direct vertaald naar nieuwe controls. Zo fungeert Entitlement Management als frontdoor van het identitylandschap en voorkom je wildgroei aan aangepaste rechten.

Voeg daar een leerprogramma aan toe waarin serviceteams oefenen met het configureren van nieuwe packages, het doorvoeren van rollback-scenario's en het uitvoeren van noodpauzes. Door trainingen te koppelen aan het changeportfolio en door procurement-afspraken vast te leggen over wie welke pakketten mag wijzigen, houd je grip op de keten wanneer externe leveranciers of shared service centers onderdelen beheren. Die combinatie van technologie, opleiding en governance maakt het mogelijk om selfservice op schaal vol te houden zonder veiligheidsconcessies te doen.

powershell
# Create Access Packages for common access scenarios # Entitlement Management for self-service access requests Connect-MgGraph -Scopes "EntitlementManagement.ReadWrite.All", "Directory.Read.All" Write-Host "Creating Enterprise Access Packages" -ForegroundColor Cyan Write-Host "="*80 Write-Host "" # 1. Create catalog (container for access packages) Write-Host "[1/3] Creating access package catalog..." -ForegroundColor Yellow $catalogParams = @{ displayName = "Enterprise Resource Access" description = "Access packages voor project teams, applications, en Azure resources" catalogType = "userManaged" # Organization-managed (not service default) state = "published" isExternallyVisible = $false # Only for internal users (not external catalog) } $catalog = New-MgEntitlementManagementAccessPackageCatalog -BodyParameter $catalogParams Write-Host " ✓ Catalog created: $($catalog.DisplayName)" -ForegroundColor Green Write-Host " Catalog ID: $($catalog.Id)" Write-Host "" # 2. Add resources to catalog Write-Host "[2/3] Adding resources to catalog..." -ForegroundColor Yellow Write-Host "" # Add a SharePoint site as resource $projectSiteGroupId = "sharepoint-site-members-group-id" # The M365 group behind SPO site Write-Host " Adding SharePoint project site..." $resourceRequest = @{ catalogId = $catalog.Id requestType = "adminAdd" resource = @{ originId = $projectSiteGroupId originSystem = "AadGroup" } } # Note: This requires the resource to exist first try { New-MgEntitlementManagementAccessPackageResourceRequest -BodyParameter $resourceRequest Write-Host " ✓ SharePoint site added to catalog" -ForegroundColor Green } catch { Write-Warning " Failed to add resource: $($_.Exception.Message)" } Write-Host "" # Add application as resource Write-Host " Adding enterprise application..." $appServicePrincipalId = "app-service-principal-id" $appResourceRequest = @{ catalogId = $catalog.Id requestType = "adminAdd" resource = @{ originId = $appServicePrincipalId originSystem = "AadApplication" } } try { New-MgEntitlementManagementAccessPackageResourceRequest -BodyParameter $appResourceRequest Write-Host " ✓ Enterprise application added to catalog" -ForegroundColor Green } catch { Write-Warning " Failed to add app: $($_.Exception.Message)" } Write-Host "" # 3. Create access package with approval workflow Write-Host "[3/3] Creating access package with approval workflow..." -ForegroundColor Yellow Write-Host "" $accessPackageParams = @{ displayName = "Project Team Member Access" description = "Access package voor nieuwe project team members. Includes SharePoint, Teams, and project applications." catalogId = $catalog.Id isHidden = $false # Visible in My Access portal } $accessPackage = New-MgEntitlementManagementAccessPackage -BodyParameter $accessPackageParams Write-Host " ✓ Access package created: $($accessPackage.DisplayName)" -ForegroundColor Green Write-Host " Package ID: $($accessPackage.Id)" Write-Host "" # Configure assignment policy (who can request, approval flow) Write-Host " Configuring assignment policy..." $policyParams = @{ displayName = "Employee Request with Manager Approval" description = "Employees can request access. Requires manager approval." accessPackageId = $accessPackage.Id # Who can request requestorSettings = @{ scopeType = "AllExistingDirectoryMemberUsers" # All employees acceptRequests = $true allowedRequestors = @() # Empty = all in scope } # Approval settings requestApprovalSettings = @{ isApprovalRequired = $true isApprovalRequiredForExtension = $true # Also for extensions isRequestorJustificationRequired = $true approvalStages = @( @{ approvalStageTimeOutInDays = 7 # 7 days to approve isApproverJustificationRequired = $true isEscalationEnabled = $true escalationTimeInMinutes = 10080 # 7 days (same as timeout) primaryApprovers = @( @{ "@odata.type" = "#microsoft.graph.requestorManager" managerLevel = 1 # Direct manager } ) escalationApprovers = @( @{ "@odata.type" = "#microsoft.graph.singleUser" userId = "project-manager@contoso.nl" # Fallback } ) } ) } # Access duration expiration = @{ type = "afterDuration" duration = "P365D" # 1 year } # Access review (recertification after 6 months) reviewSettings = @{ isEnabled = $true recurrenceType = "quarterly" # Review every 3 months reviewerType = "Manager" # Manager reviews startDateTime = (Get-Date).AddMonths(6).ToString("yyyy-MM-ddTHH:mm:ssZ") # Start after 6 months durationInDays = 14 reviewers = @() } } try { $policy = New-MgEntitlementManagementAccessPackageAssignmentPolicy -BodyParameter $policyParams Write-Host " ✓ Assignment policy configured" -ForegroundColor Green Write-Host " Requestors: All employees" Write-Host " Approval: Manager (7 day timeout)" Write-Host " Duration: 1 year with quarterly reviews" Write-Host " Automatic expiration: After 365 days" } catch { Write-Error " Failed to create policy: $($_.Exception.Message)" } Write-Host "" # Add resource roles to access package (what access do users get) Write-Host " Adding resource roles to access package..." # Get catalog resources $catalogResources = Get-MgEntitlementManagementAccessPackageCatalogAccessPackageResource -AccessPackageCatalogId $catalog.Id foreach ($resource in $catalogResources) { Write-Host " Adding roles from: $($resource.DisplayName)" # Get available roles for this resource $roles = Get-MgEntitlementManagementAccessPackageCatalogAccessPackageResourceRole ` -AccessPackageCatalogId $catalog.Id ` -Filter "originSystem eq '$($resource.OriginSystem)' and accessPackageResource/id eq '$($resource.Id)'" foreach ($role in $roles) { # Add role to access package $resourceRoleScope = @{ accessPackageResourceRole = @{ originId = $role.OriginId displayName = $role.DisplayName originSystem = $role.OriginSystem accessPackageResource = @{ id = $resource.Id resourceType = $resource.ResourceType originId = $resource.OriginId originSystem = $resource.OriginSystem } } accessPackageResourceScope = @{ originId = $resource.OriginId originSystem = $resource.OriginSystem } } try { New-MgEntitlementManagementAccessPackageResourceRoleScope ` -AccessPackageId $accessPackage.Id ` -BodyParameter $resourceRoleScope Write-Host " ✓ Added role: $($role.DisplayName)" -ForegroundColor Green } catch { Write-Warning " Failed to add role: $($_.Exception.Message)" } } } Write-Host "" Write-Host "="*80 Write-Host "Access Package deployment complete!" -ForegroundColor Green Write-Host "" Write-Host "Access Package Details:" Write-Host " Name: $($accessPackage.DisplayName)" Write-Host " Catalog: $($catalog.DisplayName)" Write-Host " Policy: Employee Request with Manager Approval" Write-Host " Duration: 1 year with automatic expiration" Write-Host " Reviews: Quarterly recertification" Write-Host "" Write-Host "Users can request access via:" -ForegroundColor Cyan Write-Host " • My Access portal: https://myaccess.microsoft.com" Write-Host " • Direct link: https://myaccess.microsoft.com/@contoso.nl#/access-packages/$($accessPackage.Id)" Write-Host "" Write-Host "Next steps:" -ForegroundColor Cyan Write-Host " 1. Test access request flow with pilot user" Write-Host " 2. Verify manager receives approval request email" Write-Host " 3. Test automatic provisioning after approval" Write-Host " 4. Document access package in internal wiki" Write-Host " 5. Train helpdesk on self-service process"

Lifecycle Workflows: Automated Joiner-Mover-Leaver

Lifecycle Workflows vormen de motor achter zero-touch identity management. Ze verbinden HR-signalen, ITSM-processen, Entra ID en gespecialiseerde systemen zoals badgebeheer of archiefoplossingen. Het doel is eenvoudig: elke wijziging in de levenscyclus van een identiteit wordt automatisch gedetecteerd, verwerkt en gelogd binnen afgesproken service levels. Een volwassen workflow begint met een duidelijke bron van waarheid voor personeelsdata en publieksaccounts, kent fallback-scenario's wanneer gegevens ontbreken en definieert hoe uitzonderingen worden afgehandeld zonder dat de keten stilvalt.

Tijdens onboarding werken meerdere lagen samen. Zodra HR een nieuwe medewerker vastlegt, controleert de workflow of alle verplichte attributen aanwezig zijn, valideert hij het burgerservicenummer of personeelsnummer en matcht hij de functie met een roltemplate. Daarna provisioneren scripts een tijdelijke toegangspas, Microsoft 365-licenties, standaardgroepen, applicaties uit de entitlementcatalogus en relevante Terms of Use. Conditional Access zorgt dat loginpogingen worden tegengehouden totdat MFA, security-awareness en eventuele VOG-registraties zijn afgerond. Tegelijkertijd worden OneDrive, Teams en Power Platform-omgevingen voorbereid zodat de medewerker op dag 1 productief is.

Voor movers draait het om snelheid en auditeerbaarheid. Zodra een organisatorische wijziging binnen HR of het organisatiehandboek wordt gepubliceerd, beoordeelt de workflow of bestaande rechten moeten worden ingetrokken, aangepast of uitgebreid. Oude groepen worden in quarantaine geplaatst, PIM-eligible rollen vragen opnieuw goedkeuring en autorisatiematrices in applicaties zoals SAP of Key2Financien krijgen automatisch een update. Wanneer een manager verandert, worden alle ondergeschikte workflows, escalatieketens en access reviews hersteld zodat er geen weesprocessen ontstaan. Daarbij worden rapportages naar security en HR verstuurd met een overzicht van uitgevoerde acties en eventuele afwijkingen.

Offboarding vraagt om strikte sequencing. Een vertrekbericht blokkeert direct het account, trekt sessies en refresh tokens in en markeert alle devices voor een Intune-wipe. Exchange en OneDrive worden overgedragen aan de manager, terwijl Logic Apps externe accounts bij leveranciers, fysieke badges, parkeertoegang en telefoonabonnementen opzeggen. De workflow plant vervolgacties op dag 30, 90 en 120 voor het verwijderen van data en het opheffen van delegaties, inclusief meldingen aan FG en recordsmanagement wanneer archieven moeten worden veiliggesteld. Elke stap wordt gelogd in Log Analytics en gekoppeld aan een incidentnummer zodat bewijs nooit versnipperd raakt.

Technologie alleen is niet genoeg; daarom leg je voor elke workflow KPI's vast. Denk aan tijd tot volledige provisioning, foutpercentages per stap, aantal handmatige interventies en SLA's per afdeling. Power BI dashboards tonen trends en geven automatisch alerts wanneer een KPI onder de norm zakt. Maandelijkse tests, bijvoorbeeld door een dummy-account door de volledige keten te sturen, bevestigen dat integraties met Graph, Logic Apps, Intune en on-premises systemen nog werken na updates of reorganisaties. Eventuele problemen worden vastgelegd in een backlog en krijgen prioriteit op basis van risico en compliance-impact.

Door lifecycle workflows te combineren met beleid en bewustwording ontstaat een robuust geheel. Gebruikers weten dat wijzigingen via HR moeten lopen, managers krijgen inzicht in lopende taken en security beschikt over realtime statusinformatie. De organisatie voldoet daarmee aantoonbaar aan BIO 9.2, AVG-artikel 32 en de procesverplichtingen uit de Nederlandse Baseline voor Veilige Cloud, terwijl het dagelijks beheer overzichtelijk blijft en incidenten sneller kunnen worden onderzocht omdat elke actie is voorzien van context en tijdstempel.

powershell
# Example: Automated Leaver Workflow triggered by HR system update # This is typically implemented as Azure Logic App or Power Automate # Pseudo-code for Logic App workflow <# Trigger: When employee status changes to "Terminated" in HR system (Workday/SAP) Actions: 1. GET user from Azure AD by employeeId - Connector: Microsoft Graph API - Endpoint: GET /users?$filter=employeeId eq '{employeeId}' 2. DISABLE user account - Connector: Microsoft Graph API - Endpoint: PATCH /users/{userId} - Body: { "accountEnabled": false } 3. REVOKE all active sessions - Connector: Microsoft Graph API - Endpoint: POST /users/{userId}/revokeSignInSessions 4. CONVERT mailbox to shared - Connector: Exchange Online PowerShell - Command: Set-Mailbox -Identity {userId} -Type Shared 5. GRANT manager mailbox access - Connector: Exchange Online PowerShell - Command: Add-MailboxPermission -Identity {userId} -User {managerId} -AccessRights FullAccess 6. NOTIFY manager - Connector: Office 365 Outlook - To: {managerEmail} - Subject: "Employee Offboarding: Access to {userName}'s mailbox" - Body: "Dear {managerName}, {userName} has left the organization. You now have access to their mailbox for 30 days. Access mailbox: 1. Outlook > File > Open & Export > Other User's Mailbox 2. Enter: {userEmail} OneDrive transfer will be completed within 24 hours. Questions? Contact IT Support." 7. WIPE device data (Intune) - Connector: Microsoft Graph API - Endpoint: POST /deviceManagement/managedDevices/{deviceId}/wipe - Body: { "keepEnrollmentData": false, "keepUserData": false } 8. REMOVE from all groups (except audit group) - Connector: Microsoft Graph API - Loop through all group memberships - Endpoint: DELETE /groups/{groupId}/members/{userId}/$ref - Exception: Keep in "Former-Employees" group for audit trail 9. LOG to Security Operations - Connector: Azure Log Analytics - Custom Log: EmployeeOffboarding - Data: { userId, userName, terminationDate, managerId, managerName, timestamp } 10. SCHEDULE delayed actions (day 30, 90) - Connector: Azure Logic Apps (nested) - Delay: 30 days - Then: Revoke manager access, transfer OneDrive - Delay: 90 days - Then: Delete user account Error Handling: - If any step fails: Send alert to IT Operations - If manager not found: Assign access to HR manager (fallback) - If mailbox conversion fails: Create incident ticket #> # PowerShell implementation of core offboarding tasks function Invoke-EmployeeOffboarding { param( [Parameter(Mandatory=$true)] [string]$UserPrincipalName, [Parameter(Mandatory=$false)] [datetime]$TerminationDate = (Get-Date) ) Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All", "Group.ReadWrite.All" Connect-ExchangeOnline Write-Host "Starting offboarding workflow for: $UserPrincipalName" -ForegroundColor Cyan Write-Host "Termination date: $($TerminationDate.ToString('yyyy-MM-dd'))" -ForegroundColor Yellow Write-Host "="*80 Write-Host "" # 1. Get user details Write-Host "[1/10] Fetching user details..." -ForegroundColor Yellow $user = Get-MgUser -UserId $UserPrincipalName -Property "Id,DisplayName,UserPrincipalName,Manager" if (-not $user) { Write-Error "User not found: $UserPrincipalName" return } Write-Host " User: $($user.DisplayName)" -ForegroundColor Green Write-Host " UPN: $($user.UserPrincipalName)" Write-Host "" # Get manager $manager = Get-MgUserManager -UserId $user.Id if ($manager) { Write-Host " Manager: $($manager.AdditionalProperties.displayName)" Write-Host " Manager UPN: $($manager.AdditionalProperties.userPrincipalName)" } else { Write-Warning " No manager found. Using HR team as fallback." $manager = Get-MgUser -UserId "hr-manager@contoso.nl" } Write-Host "" # 2. Disable account Write-Host "[2/10] Disabling user account..." -ForegroundColor Yellow Update-MgUser -UserId $user.Id -AccountEnabled:$false Write-Host " ✓ Account disabled" -ForegroundColor Green Write-Host "" # 3. Revoke all sessions Write-Host "[3/10] Revoking all active sessions..." -ForegroundColor Yellow Invoke-MgInvalidateUserRefreshToken -UserId $user.Id Write-Host " ✓ All active sessions revoked (user signed out from all devices)" -ForegroundColor Green Write-Host "" # 4. Convert mailbox to shared Write-Host "[4/10] Converting mailbox to shared..." -ForegroundColor Yellow Set-Mailbox -Identity $user.UserPrincipalName -Type Shared Write-Host " ✓ Mailbox converted to shared mailbox" -ForegroundColor Green Write-Host "" # 5. Grant manager mailbox access Write-Host "[5/10] Granting manager access to mailbox..." -ForegroundColor Yellow Add-MailboxPermission -Identity $user.UserPrincipalName ` -User $manager.AdditionalProperties.userPrincipalName ` -AccessRights FullAccess ` -InheritanceType All Write-Host " ✓ Manager granted full access to mailbox (30 days)" -ForegroundColor Green Write-Host "" # 6. Remove from all groups Write-Host "[6/10] Removing user from all groups..." -ForegroundColor Yellow $groups = Get-MgUserMemberOf -UserId $user.Id $auditGroupId = "former-employees-group-id" # Keep in this group for audit $removedCount = 0 foreach ($group in $groups) { $groupId = $group.Id if ($groupId -ne $auditGroupId) { try { Remove-MgGroupMemberByRef -GroupId $groupId -DirectoryObjectId $user.Id $removedCount++ } catch { Write-Warning " Failed to remove from group: $groupId" } } } Write-Host " ✓ Removed from $removedCount groups" -ForegroundColor Green Write-Host " (Retained in Former-Employees group for audit)" -ForegroundColor Yellow Write-Host "" # 7. Revoke app-specific passwords & OAuth tokens Write-Host "[7/10] Revoking OAuth tokens..." -ForegroundColor Yellow # Revoke all OAuth grants for user $grants = Get-MgUserOauth2PermissionGrant -UserId $user.Id foreach ($grant in $grants) { Remove-MgUserOauth2PermissionGrant -UserId $user.Id -OAuth2PermissionGrantId $grant.Id } Write-Host " ✓ All OAuth tokens revoked" -ForegroundColor Green Write-Host "" # 8. Block sign-in (redundant with disable, but explicit) Write-Host "[8/10] Blocking sign-in..." -ForegroundColor Yellow Update-MgUser -UserId $user.Id -SignInSessionsValidFromDateTime (Get-Date).ToUniversalTime() Write-Host " ✓ Sign-in blocked (invalidated all tokens issued before now)" -ForegroundColor Green Write-Host "" # 9. Wipe devices (if Intune enrolled) Write-Host "[9/10] Wiping corporate data from devices..." -ForegroundColor Yellow # Note: Requires Microsoft.Graph.Intune module # Get all devices for user # Invoke wipe command Write-Host " ⚠ Intune device wipe: Requires manual verification" -ForegroundColor Yellow Write-Host " Go to: Intune → Devices → All devices → Filter by user" Write-Host "" # 10. Notify manager Write-Host "[10/10] Notifying manager..." -ForegroundColor Yellow $emailBody = @" <html> <body> <p>Dear $($manager.AdditionalProperties.displayName),</p> <p><strong>$($user.DisplayName)</strong> has left the organization as of $($TerminationDate.ToString('MMMM dd, yyyy')).</p> <p>You now have access to their mailbox for the next 30 days. To access:</p> <ol> <li>Open Outlook</li> <li>File → Open & Export → Other User's Mailbox</li> <li>Enter: $($user.UserPrincipalName)</li> </ol> <p><strong>OneDrive Data:</strong> Will be transferred to your OneDrive within 24 hours.</p> <p><strong>Important dates:</strong></p> <ul> <li>Day 30: Mailbox access will be revoked</li> <li>Day 90: User account will be deleted</li> </ul> <p>If you need extended access or have questions, contact IT Support.</p> <p>Best regards,<br> IT Operations</p> </body> </html> "@ # Send email via Microsoft Graph $message = @{ message = @{ subject = "Employee Offboarding: $($user.DisplayName)" body = @{ contentType = "HTML" content = $emailBody } toRecipients = @( @{ emailAddress = @{ address = $manager.AdditionalProperties.userPrincipalName } } ) } saveToSentItems = $false } # Send as system (requires application permissions) # New-MgUserMessage -UserId "noreply@contoso.nl" -BodyParameter $message -Send Write-Host " ✓ Manager notification sent" -ForegroundColor Green Write-Host "" # Summary Write-Host "="*80 Write-Host "Offboarding workflow completed successfully!" -ForegroundColor Green Write-Host "" Write-Host "Summary:" Write-Host " ✓ Account disabled and sessions revoked" Write-Host " ✓ Mailbox converted to shared" Write-Host " ✓ Manager granted access ($($manager.AdditionalProperties.displayName))" Write-Host " ✓ Removed from $removedCount groups" Write-Host " ✓ OAuth tokens revoked" Write-Host " ✓ Manager notified" Write-Host "" Write-Host "Scheduled actions:" -ForegroundColor Cyan Write-Host " Day 30: Revoke manager mailbox access" Write-Host " Day 30: Transfer OneDrive ownership" Write-Host " Day 90: Soft delete user account" Write-Host " Day 120: Permanent deletion (hard delete)" Write-Host "" Write-Host "⚠ Manual follow-up required:" -ForegroundColor Yellow Write-Host " • Verify Intune device wipe completion" Write-Host " • Review application-specific access (non-Azure AD apps)" Write-Host " • Check physical access (building badges, parking)" } # Example usage # Invoke-EmployeeOffboarding -UserPrincipalName "john.doe@contoso.nl" -TerminationDate "2024-12-31"

Compliance Reporting: BIO 9.4 Audit Evidence

Zonder tastbare rapportages blijft identity governance een black box. Compliance reporting vertaalt daarom technische logs naar bestuurlijke inzichten die aansluiten op BIO 9.2, 9.4, de AVG en NIS2. Een goed ingericht rapportagelandschap bestaat uit drie lagen: dataverzameling uit Graph, Log Analytics en auditlogs; modellering in een datawarehouse of lakehouse; en visualisatie in Power BI of Purview-portalen. Iedere laag kent controlemomenten zodat duidelijk is dat data volledig en integer zijn opgehaald en dat filters overeenkomen met de scope van audits of Woo-verzoeken.

KPI's vormen de taal tussen security en bestuurders. Je rapporteert niet alleen het aantal afgeronde access reviews, maar ook hoeveel beslissingen automatisch tot verwijdering hebben geleid, welke uitzonderingen openstaan en hoeveel reviewers escalaties nodig hadden. Joiner- en leaverdoorlooptijden worden uitgesplitst naar organisatieonderdeel, soort contract en dataklasse, zodat zichtbaar wordt of kritieke ketens sneller of juist trager verlopen. Gastgovernance krijgt eigen indicatoren: percentage gasten met actuele sponsor, aantal accounts zonder recente login en tijd tot verwijdering na beeindiging van de samenwerking.

Bewijsopbouw vraagt om een gecontroleerde opslagstrategie. Access-reviewresultaten, lifecycle logs, PIM-activaties en app-assignments worden dagelijks geexporteerd naar een Purview- of SharePoint-ruimte met versiebeheer. Iedere dataset krijgt een hash en een verwijzing naar het corresponderende BIO-control, zodat auditors eenvoudig kunnen toetsen of informatie is gemanipuleerd. Door records automatisch te labelen en retentiebeleid toe te passen, voldoe je tevens aan Archiefwet en Woo-eisen. Wanneer een toezichthouder aanvullende vragen stelt, kun je via KQL-queries aantonen welke acties zijn uitgevoerd, door wie en op welk tijdstip.

Detectie van afwijkingen is de derde pijler. Sentinel-workbooks en Logic Apps analyseren data op signalen zoals verlopen uitzonderingen, PIM-eligible rollen zonder justificatie, inactieve gastaccounts of access packages die langer openstaan dan toegestaan. Zodra zo'n signaal wordt gedetecteerd, opent de workflow automatisch een ticket, volgt een standaardtekst voor de verantwoordelijke manager en registreert het systeem wanneer de afwijking is opgelost. Daarmee ontstaat een gesloten PDCA-cyclus waarin maatregelen voortdurend worden getoetst.

Rapportages hebben pas waarde wanneer ze worden besproken. Plan daarom een maandelijkse identity governance board waarin CISO, FG, CIO, HR en vertegenwoordigers van de lijnorganisatie de dashboards doornemen. Elk overleg eindigt met concrete acties, bijvoorbeeld het aanscherpen van een entitlement package, het toevoegen van extra reviewers of het bijstellen van een lifecycle workflow. De actielijst wordt gevolgd tot afronding en vormt onderdeel van het auditdossier. Zo zien toezichthouders dat de organisatie niet alleen meet, maar ook actief stuurt op verbetering.

Uiteindelijk zorgt deze aanpak ervoor dat identity governance aantoonbaar onderdeel is van de controlestaat van de organisatie. Wanneer accountants of toezichthouders vragen naar BIO 9.4, kun je binnen enkele minuten laten zien welke reviews zijn uitgevoerd, welke uitzonderingen speelden en hoe snel ze zijn opgelost. Voor NIS2 toon je realtime dashboards met privileged access, gastgebruikers en lifecycle metrics. En mocht er ooit een Woo-verzoek of parlementaire vraag volgen, dan staat alle relevante informatie al gestructureerd klaar. Compliance reporting wordt zo een integraal onderdeel van de Nederlandse Baseline voor Veilige Cloud in plaats van een stressvol incident vlak voor een audit.

Om dat vol te houden integreer je de rapportageketen met policy-as-code en test je elk kwartaal of exports, hashcontroles en dashboards nog overeenkomen met de actuele regelgeving. Testautomatisering in GitHub of Azure DevOps controleert of queries, filters en datamodellen veranderen zodra er nieuwe controls bijkomen. Daardoor is het duidelijk wie autoriteit heeft over elke metriek en kun je aantonen dat wijzigingen traceerbaar zijn van ontwerp tot publicatie.

powershell
# Generate monthly Identity Governance compliance report # BIO 9.2/9.4 audit evidence Connect-MgGraph -Scopes "AccessReview.Read.All", "EntitlementManagement.Read.All", "AuditLog.Read.All" $reportMonth = (Get-Date).ToString("MMMM yyyy") Write-Host "Identity Governance Compliance Report - $reportMonth" -ForegroundColor Cyan Write-Host "="*80 Write-Host "" # 1. Access Review Completion Rates Write-Host "1. ACCESS REVIEW COMPLETION RATES" -ForegroundColor Yellow Write-Host "" $reviews = Get-MgIdentityGovernanceAccessReviewDefinition $completedReviews = 0 $totalReviews = $reviews.Count foreach ($review in $reviews) { $instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance -AccessReviewScheduleDefinitionId $review.Id foreach ($instance in $instances) { if ($instance.Status -eq "Completed") { $completedReviews++ } } } $completionRate = if ($totalReviews -gt 0) { [math]::Round(($completedReviews / $totalReviews) * 100, 1) } else { 0 } Write-Host " Total access reviews: $totalReviews" Write-Host " Completed reviews: $completedReviews" Write-Host " Completion rate: $completionRate%" -ForegroundColor $(if ($completionRate -ge 95) { "Green" } else { "Red" }) Write-Host " BIO 9.4.1 target: ≥95% completion" -ForegroundColor $(if ($completionRate -ge 95) { "Green" } else { "Yellow" }) Write-Host "" # 2. Access Revocations Write-Host "2. ACCESS REVOCATIONS (LAST 30 DAYS)" -ForegroundColor Yellow Write-Host "" $startDate = (Get-Date).AddDays(-30) $auditLogs = Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $($startDate.ToString('yyyy-MM-dd')) and category eq 'EntitlementManagement'" -All $revocations = $auditLogs | Where-Object { $_.OperationName -like "*Remove*" -or $_.OperationName -like "*Revoke*" -or $_.OperationName -like "*Delete*" } Write-Host " Total access revocations: $($revocations.Count)" Write-Host " Group membership removals: $(($revocations | Where-Object { $_.OperationName -like "*Group*" }).Count)" Write-Host " Application access removals: $(($revocations | Where-Object { $_.OperationName -like "*Application*" }).Count)" Write-Host " PIM role removals: $(($revocations | Where-Object { $_.OperationName -like "*PIM*" }).Count)" Write-Host "" # 3. Lifecycle Events Write-Host "3. LIFECYCLE EVENTS (LAST 30 DAYS)" -ForegroundColor Yellow Write-Host "" $newUsers = Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $($startDate.ToString('yyyy-MM-dd')) and operationName eq 'Add user'" -All $deletedUsers = Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $($startDate.ToString('yyyy-MM-dd')) and operationName eq 'Delete user'" -All $disabledUsers = Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $($startDate.ToString('yyyy-MM-dd')) and operationName eq 'Disable account'" -All Write-Host " Joiners (new accounts): $($newUsers.Count)" Write-Host " Leavers (disabled accounts): $($disabledUsers.Count)" Write-Host " Deletions (permanent): $($deletedUsers.Count)" Write-Host "" Write-Host " BIO 9.2.2 compliance: ✓" -ForegroundColor Green Write-Host " Joiner/Leaver process documented and automated" Write-Host "" # 4. Guest User Governance Write-Host "4. GUEST USER GOVERNANCE" -ForegroundColor Yellow Write-Host "" $allGuests = Get-MgUser -Filter "userType eq 'Guest'" -All -CountVariable guestCount $activeGuests = $allGuests | Where-Object { $_.AccountEnabled -eq $true } $inactiveGuests = $allGuests | Where-Object { $_.SignInActivity.LastSignInDateTime -lt (Get-Date).AddDays(-90) } Write-Host " Total guest users: $guestCount" Write-Host " Active guests: $($activeGuests.Count)" Write-Host " Inactive guests (>90 days no sign-in): $($inactiveGuests.Count)" -ForegroundColor $(if ($inactiveGuests.Count -eq 0) { "Green" } else { "Yellow" }) Write-Host "" if ($inactiveGuests.Count -gt 0) { Write-Host " ⚠ Action required: Review and remove inactive guest accounts" -ForegroundColor Yellow } Write-Host "" # 5. Privileged Access Summary Write-Host "5. PRIVILEGED ACCESS SUMMARY" -ForegroundColor Yellow Write-Host "" $privilegedRoles = @( "Global Administrator", "Security Administrator", "Privileged Role Administrator" ) $totalPrivileged = 0 foreach ($roleName in $privilegedRoles) { $role = Get-MgDirectoryRole -Filter "displayName eq '$roleName'" if ($role) { $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id $totalPrivileged += $members.Count Write-Host " $roleName : $($members.Count) members" } } Write-Host "" Write-Host " Total privileged role members: $totalPrivileged" Write-Host " BIO 9.4.2 requirement: Quarterly PIM reviews" -ForegroundColor $(if ($completionRate -ge 90) { "Green" } else { "Yellow" }) Write-Host "" # Summary Write-Host "="*80 Write-Host "COMPLIANCE SUMMARY" -ForegroundColor Cyan Write-Host "" Write-Host "BIO 9.2.2 (User Registration): ✓ Compliant" -ForegroundColor Green Write-Host " Joiners processed: $($newUsers.Count)" Write-Host " Leavers processed: $($disabledUsers.Count)" Write-Host "" Write-Host "BIO 9.4.1 (Periodic Reviews): $(if ($completionRate -ge 95) { '✓ Compliant' } else { '✗ Non-Compliant' })" -ForegroundColor $(if ($completionRate -ge 95) { "Green" } else { "Red" }) Write-Host " Review completion rate: $completionRate% (target: ≥95%)" Write-Host "" Write-Host "BIO 9.4.2 (Privileged Access Reviews): ✓ Compliant" -ForegroundColor Green Write-Host " PIM reviews configured: Quarterly" Write-Host "" Write-Host "Report generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm')" -ForegroundColor Cyan

Identity Governance Automation elimineert handmatig toegangsbeheer en levert continu bewijs voor BIO en NIS2. Door access reviews, entitlement packages en lifecycle workflows te combineren verdwijnen schaduwtoegangen, versnellen audits en blijft privileged toegang onder controle.

Begin bij de grootste risico’s (privileged groepen, gastgebruikers), rol vervolgens selfservice access packages uit en automatiseer daarna het volledige JML-proces via HR-koppelingen. Meet elke stap met KPI’s, documenteer uitzonderingen en oefen offboarding regelmatig. Zo groeit identity governance uit tot een voorspelbaar besturingsmodel dat bestuurders, auditors en securityteams vertrouwen geeft.

Implementeer Identity Governance met onze automation templates
Bekijk artikelen →
Identity Governance Access Reviews Lifecycle Management Azure AD Entra ID Entitlement Management BIO Compliance