NIS2 Art.21(2)(i): Access Control, HR Security and Asset Management — SaaS Developer Guide (2026)
Access is the attack surface. The majority of significant security incidents investigated by NCAs under NIS2 trace back to a single failure: the wrong person — or the wrong system — had access to something they should not have. Either a privileged account was not revoked after an employee departure. Or a developer account accumulated years of entitlements through role drift. Or an unmanaged asset sat on the network outside the monitoring perimeter.
NIS2 Art.21(2)(i) addresses all three failure modes in a single measure: "HR security, access control and asset management." It is the broadest of the ten mandatory measures — touching identity management, organisational policy, procurement, and operational processes simultaneously.
NCA audits beginning June 2026 will scrutinise access control maturity closely. Entities that have only reactive, undocumented access processes will face findings. This guide builds the Art.21(2)(i) framework from the ground up for SaaS development teams.
1. Art.21(2)(i) in the Full NIS2 Context
NIS2 Art.21(2) mandates ten cybersecurity risk-management measures. Art.21(2)(i) is the ninth — and spans organisational, process, and technical domains.
The Ten Mandatory Measures
| Subparagraph | Requirement | Primary Owner |
|---|---|---|
| Art.21(2)(a) | Risk analysis and information system security policies | CISO / Management |
| Art.21(2)(b) | Incident handling (see incident handling guide) | SOC / DevSecOps |
| Art.21(2)(c) | Business continuity, backup management, disaster recovery (see BCM guide) | Ops / SRE |
| Art.21(2)(d) | Supply chain security | Procurement / DevSecOps |
| Art.21(2)(e) | Security in acquisition, development and maintenance (see SDL guide) | Engineering |
| Art.21(2)(f) | Policies to assess effectiveness of cybersecurity measures | Audit / GRC |
| Art.21(2)(g) | Basic cyber hygiene and training | HR / Security Awareness |
| Art.21(2)(h) | Cryptography and encryption policies (see cryptography guide) | Architecture |
| Art.21(2)(i) | HR security, access control and asset management | IT / HR / Engineering |
| Art.21(2)(j) | Multi-factor authentication and continuous authentication (see MFA guide) | IT / IAM / Engineering |
Art.21(2)(i) is the identity and access foundation. Without it, Art.21(2)(j) MFA controls have no structure — you need to know who holds which accounts before you can enforce authentication requirements. Similarly, Art.21(2)(b) incident response depends on access logs that only exist if assets are properly inventoried and access is systematically managed.
The Exact Regulatory Text
Art.21(2)(i) requires:
"human resources security, access control policies and asset management"
ENISA's technical guidelines expand this into three distinct but interdependent domains:
- HR Security — controlling who enters, moves within, and exits the organisation in terms of access entitlements
- Access Control — governing which identities can access which resources under which conditions
- Asset Management — maintaining an accurate, current inventory of all assets that need protection
NCA auditors treat these as a triad: a gap in any one domain undermines the other two.
2. HR Security: The Identity Lifecycle
HR security under NIS2 Art.21(2)(i) is not about background checks alone. It covers the full employment lifecycle — from pre-hire screening to post-departure access revocation.
Phase 1: Pre-Employment Screening
NIS2 requirements: Entities must perform background checks proportionate to the role and access level. NIS2 does not mandate specific check types — but ENISA guidelines reference ISO 27001 A.6.1 as the baseline.
Proportionality tiers for SaaS organisations:
| Role Category | Minimum Checks | Recommended for NIS2 |
|---|---|---|
| Standard contributor | Identity verification + reference check | + employment history verification |
| Administrator / privileged access | Above + criminal record check | + credit check (financial sector) |
| CISO / security function | Above + extended reference checks | + security clearance where applicable |
| Third-party contractors with admin access | Identity + reference (same as admin tier) | + periodic re-vetting annually |
Documentation required for NCA audit:
- Written pre-employment screening policy with role tiers
- Evidence that checks were performed (vendor records, HR system entries)
- Exceptions register (documented approval for deviations)
Phase 2: Onboarding — Provisioning with Least Privilege
The onboarding phase is where role drift begins. Most organisations provision access reactively — adding permissions as employees request them — without a documented baseline.
NIS2-compliant onboarding process:
1. Role defined in RBAC matrix before start date
2. Access provisioned from role template (not from ad-hoc manager requests)
3. Provisioning logged with: who approved, which systems, what access level
4. New joiner signs acceptable use policy on day 1
5. Access confirmed via first login within 5 business days (detect provisioning errors)
Role template example (Infrastructure Engineer):
role: infrastructure-engineer
access_groups:
- k8s-prod-read # read-only on production kubernetes
- k8s-staging-write # full on staging
- aws-iam-readonly # read IAM, cannot modify
- github-org-member # base org membership
- pagerduty-responder # on-call rotations
- vault-infra-path # infra secrets path only
excluded:
- k8s-prod-write # requires explicit approval + justification
- aws-iam-admin # admin team only
- billing-admin # finance team only
Phase 3: Lifecycle Events — Transfers and Role Changes
Internal transfers are the most common source of over-entitlement in practice. When an employee moves from engineering to product, their engineering access often persists indefinitely.
NIS2 requirements for lifecycle events:
- Access modifications must be processed within a defined SLA (ENISA recommends 5 business days for non-emergency transitions)
- The modification must be driven by a formal request (ticket, HRIS trigger) — not informal conversation
- Previous access must be explicitly revoked, not just new access added
HRIS-IAM integration pattern:
# Triggered by HRIS lifecycle event
def handle_role_change(employee_id: str, old_role: str, new_role: str) -> None:
old_permissions = get_role_permissions(old_role)
new_permissions = get_role_permissions(new_role)
revoke = old_permissions - new_permissions # explicit revocation
grant = new_permissions - old_permissions # new entitlements
for perm in revoke:
revoke_access(employee_id, perm)
log_access_change(employee_id, "REVOKE", perm, trigger="role_change")
for perm in grant:
grant_access(employee_id, perm)
log_access_change(employee_id, "GRANT", perm, trigger="role_change")
Phase 4: Offboarding — The Critical Revocation Window
Offboarding failures are consistently the top Art.21(2)(i) finding in NCA audits. The risk window — from last day to full access revocation — is where most post-employment incidents occur.
NIS2 requirements for offboarding:
- Access revocation SLA: immediate for involuntary departures (same business day); 24 hours for voluntary departures
- All active sessions must be terminated (not just accounts disabled — active SSO sessions survive account disables if token TTL is long)
- Credentials must be rotated for any shared secrets the departing employee knew
- Documentation: revocation confirmation with timestamp, reviewer identity, completeness sign-off
Offboarding checklist (automated vs manual split):
AUTOMATED (triggered by HRIS termination event):
☐ Disable IdP account (Okta/Entra/Google)
☐ Revoke all OAuth tokens and refresh tokens
☐ Remove from all SSO-federated application groups
☐ Disable VPN certificate / remove from VPN group
☐ Disable SSH key access (remove from authorised_keys)
☐ Revoke API keys registered under user account
MANUAL (security team verification, same day):
☐ Confirm active Kubernetes service account tokens rotated
☐ Confirm shared vault paths accessed — rotate those secrets
☐ Confirm CI/CD pipeline credentials not employee-personal
☐ Confirm GitHub/GitLab PATs revoked
☐ Confirm cloud console sessions terminated (AWS, GCP, Azure)
Critical: Account disable ≠ session revocation. An Okta account disabled at 17:00 still has valid session cookies until their TTL expires — potentially 24 hours later. Force session termination explicitly.
3. Access Control Architecture
Principle of Least Privilege
Least privilege is the foundational requirement of Art.21(2)(i) access control. Every identity — human or non-human — should hold only the minimum access required to perform its defined function.
Common least-privilege failures in SaaS organisations:
| Pattern | Description | NIS2 Risk |
|---|---|---|
| Admin default | All developers have admin access to ease troubleshooting | Single compromised account = full environment takeover |
| Permission accumulation | Permissions added but never removed over years | Insider threat surface grows with tenure |
| Shared credentials | Database passwords known by entire team | No individual attribution, no rotation trigger |
| Over-scoped service accounts | App service account has admin rights "just in case" | Compromised app = lateral movement to entire environment |
| Long-lived tokens | API keys and PATs never expire | Exposure window is unlimited if key is leaked |
Access Control Models for NIS2 Compliance
Role-Based Access Control (RBAC) is the baseline for most SaaS organisations:
Users → assigned to → Roles → granted → Permissions → on → Resources
Example:
alice → [infrastructure-engineer] → [k8s-prod-read, staging-full] → [clusters, namespaces]
RBAC is well-suited to stable, predictable access patterns. NCA auditors will expect to see a documented role matrix, role ownership, and evidence that role assignments are reviewed.
Attribute-Based Access Control (ABAC) adds context-aware decisions:
Access decision = f(user_attributes, resource_attributes, environment_attributes)
Example policy:
ALLOW IF:
user.department = "engineering"
AND resource.classification = "internal"
AND environment.location IN ["office", "vpn"]
AND environment.time BETWEEN 07:00 AND 22:00
ABAC is appropriate for cloud-native environments where resource attributes (tags, labels) can drive fine-grained access. It requires an attribute governance process — stale attributes produce incorrect decisions.
Privileged Access Management (PAM): For NIS2 compliance, privileged access requires a separate control layer beyond standard RBAC/ABAC:
Privileged access requirements:
1. Just-In-Time (JIT) elevation — no standing admin rights
2. Approval workflow — break-glass requires manager or security approval
3. Session recording — all privileged sessions captured
4. Time-bounded — access expires automatically (max 4 hours typical)
5. Separate credentials — admin accounts distinct from daily-use accounts
Non-Human Identity (NHI) Controls
NIS2 Art.21(2)(i) covers access control for all identities — including service accounts, CI/CD pipelines, and machine-to-machine integrations. NHI sprawl is a growing attack surface.
NHI inventory requirements:
# Every NHI must be documented in the asset register
NHI_RECORD = {
"id": "svc-payments-processor",
"type": "service_account", # service_account | api_key | oauth_client | cert
"owner": "payments-team",
"system": "payment-service",
"created": "2025-03-14",
"last_rotated": "2026-01-10",
"expiry": "2026-07-10", # mandatory expiry for all NHIs
"permissions": ["payments:read", "payments:write", "vault:payments-path"],
"rotation_policy": "90_days",
"review_cadence": "quarterly"
}
Key NHI controls for NIS2:
- All service accounts must have a named human owner (team or individual)
- Credentials must rotate on a defined schedule (90 days maximum for high-privilege NHIs)
- Unused NHIs (no authentication activity for 90 days) must be reviewed and disabled
- CI/CD pipelines should use OIDC short-lived tokens instead of long-lived secrets where possible
4. Access Reviews and Certification Campaigns
Access reviews — systematic validation that current access assignments remain appropriate — are a specific NCA audit expectation under Art.21(2)(i). Ad-hoc reviews are not sufficient; a documented, recurring campaign cadence is required.
Review Cadence by Access Tier
| Access Tier | Description | Review Frequency |
|---|---|---|
| Privileged / Admin | Cloud console admin, k8s cluster-admin, DB admin | Quarterly |
| Production access | Read or write on production systems | Semi-annual |
| Standard access | Internal tooling, staging environments | Annual |
| External / third-party | Vendor and contractor access | Quarterly |
Certification Campaign Process
A certification campaign is a structured, time-boxed review where access owners confirm or revoke entitlements for their systems. The process must be documented for NCA evidence.
Campaign workflow:
1. INITIATION (Day 0)
- Generate access snapshot from IdP and application logs
- Assign review tasks to resource owners
- Set completion deadline (typically 10 business days)
2. REVIEW (Days 1-10)
- Owner reviews each assignment: CONFIRM / REVOKE / ESCALATE
- Escalated items go to security team for decision
- Owner cannot self-certify their own access
3. REMEDIATION (Days 11-15)
- Revoked access processed via IAM workflows
- Escalated decisions documented with justification
- Exceptions registered (time-limited, re-reviewed next cycle)
4. EVIDENCE CAPTURE (Day 16)
- Campaign report: completion rate, revocations count, exceptions
- Report signed by CISO or delegated authority
- Stored for NCA audit (minimum 3 years, matching NIS2 Art.21 retention)
Access Review Red Flags (NCA Will Look For)
- Zero revocations: If every review cycle confirms all access, the process is not functioning. Statistically, 5-15% of access entitlements should be revoked in any population.
- 100% completion in 24 hours: Reviews completed immediately suggest rubber-stamping, not genuine review.
- No exception handling: Missing an exception register indicates the process has no mechanism for legitimate edge cases.
- No evidence for leavers: If offboarded employees' access appears in review reports, the offboarding process is broken.
5. Asset Management
Asset management is the third pillar of Art.21(2)(i). You cannot protect what you do not know exists. An accurate, current asset inventory is a prerequisite for every other NIS2 measure.
Asset Inventory Scope
For SaaS organisations, the asset inventory must cover:
| Asset Category | Examples | Classification |
|---|---|---|
| Compute | VMs, containers, serverless functions, developer laptops | By data processed |
| Network | VPCs, subnets, firewalls, load balancers, VPN endpoints | By exposure (internal/external) |
| Data stores | Databases, blob storage, queues, caches | By data classification |
| Software | Applications, dependencies, OS images, container base images | By criticality |
| Credentials / secrets | API keys, certificates, service account keys | By privilege level |
| External services | SaaS tools, cloud provider services, CDN | By data shared |
CMDB Implementation Pattern
A Configuration Management Database (CMDB) is the standard mechanism for asset inventory. For cloud-native organisations, a CMDB can be largely automated from infrastructure-as-code:
# Auto-populate CMDB from Terraform state and cloud APIs
class NIS2AssetInventory:
def __init__(self):
self.assets = {}
def ingest_terraform_state(self, state_file: str) -> None:
"""Parse terraform.tfstate to extract resource inventory."""
with open(state_file) as f:
state = json.load(f)
for resource in state.get("resources", []):
asset_id = f"{resource['type']}.{resource['name']}"
self.assets[asset_id] = {
"type": resource["type"],
"provider": resource.get("provider", "unknown"),
"instances": len(resource.get("instances", [])),
"last_seen": datetime.now().isoformat(),
"source": "terraform_state",
"classification": self._classify(resource["type"])
}
def _classify(self, resource_type: str) -> str:
critical = ["aws_db_instance", "google_sql_database", "azurerm_sql_server"]
high = ["aws_instance", "google_compute_instance", "azurerm_virtual_machine"]
return "critical" if resource_type in critical else (
"high" if resource_type in high else "standard")
def find_unclassified(self) -> list:
return [a for a, v in self.assets.items() if v["classification"] == "unknown"]
def find_stale(self, days: int = 90) -> list:
"""Assets not seen in cloud scan for >N days — potential shadow IT."""
cutoff = datetime.now() - timedelta(days=days)
return [a for a, v in self.assets.items()
if datetime.fromisoformat(v["last_seen"]) < cutoff]
Shadow IT Detection
Shadow IT — assets deployed outside the formal inventory — is a specific NCA concern under Art.21(2)(i). Regular reconciliation between the CMDB and actual cloud state is required.
Reconciliation process:
1. Weekly cloud scan: enumerate all resources in all accounts/projects/subscriptions
2. Compare against CMDB: identify resources not in inventory
3. Alert on gaps: new resources > 7 days old not in CMDB = shadow IT finding
4. Remediation: either register in CMDB (with owner, classification) or decommission
5. Evidence: reconciliation report with finding counts and resolution status
Asset Lifecycle: Decommissioning
Asset decommissioning is where security debt accumulates. Orphaned assets — old VMs, decommissioned databases, legacy API keys — are frequently exploited attack vectors.
NIS2-compliant decommissioning:
- Decommissioning must be triggered by a formal request, not drift
- Data must be securely deleted or transferred (Art.21(2)(i) + GDPR Art.17 interaction)
- Credentials and API keys associated with the asset must be revoked
- CMDB record must be updated with decommission date, not deleted (audit trail)
- Access reviews must remove the asset from scope within the next cycle
6. Python NIS2IAMAssessor
The following tool automates gap assessment against Art.21(2)(i) requirements. It evaluates your organisation's access control, HR security, and asset management posture against NCA audit criteria.
#!/usr/bin/env python3
"""
NIS2IAMAssessor — automated Art.21(2)(i) compliance gap analyser
Assesses HR security, access control, and asset management against NCA audit criteria.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional
import json
@dataclass
class AccessControlAssessment:
"""Represents the access control posture for NIS2 Art.21(2)(i) assessment."""
# HR Security
has_screening_policy: bool = False
screening_tiers_defined: bool = False
offboarding_sla_defined: bool = False
offboarding_sla_hours: int = 72 # Should be <24h for voluntary, <4h for involuntary
hris_iam_integration: bool = False
# Access Control
rbac_model_documented: bool = False
least_privilege_enforced: bool = False
pam_solution_deployed: bool = False
jit_access_available: bool = False
nhi_inventory_maintained: bool = False
nhi_rotation_policy_defined: bool = False
nhi_max_rotation_days: int = 365 # Should be ≤90 for privileged NHIs
# Access Reviews
access_review_cadence_defined: bool = False
privileged_review_frequency_days: int = 365 # Should be ≤90
last_review_date: Optional[str] = None
last_review_revocation_rate: float = 0.0 # Should be >0
# Asset Management
asset_inventory_maintained: bool = False
cmdb_automated: bool = False
shadow_it_reconciliation: bool = False
reconciliation_frequency_days: int = 365 # Should be ≤30
decommissioning_process_documented: bool = False
@dataclass
class IAMFinding:
control: str
status: str # PASS | WARN | FAIL
severity: str # CRITICAL | HIGH | MEDIUM | LOW
detail: str
evidence_required: str
class NIS2IAMAssessor:
"""Assesses NIS2 Art.21(2)(i) compliance: HR security, access control, asset management."""
def __init__(self, assessment: AccessControlAssessment):
self.assessment = assessment
self.findings: list[IAMFinding] = []
def assess(self) -> dict:
self.findings.clear()
self._assess_hr_security()
self._assess_access_control()
self._assess_access_reviews()
self._assess_asset_management()
return self._generate_report()
def _assess_hr_security(self) -> None:
a = self.assessment
if not a.has_screening_policy:
self.findings.append(IAMFinding(
control="HR-01: Pre-employment screening policy",
status="FAIL",
severity="HIGH",
detail="No documented pre-employment screening policy. NCA will expect written policy with role-tiered requirements.",
evidence_required="Written screening policy with admin/standard/contractor tiers, HR system records of checks performed."
))
elif not a.screening_tiers_defined:
self.findings.append(IAMFinding(
control="HR-01: Screening policy — tier differentiation",
status="WARN",
severity="MEDIUM",
detail="Screening policy exists but does not differentiate by access level. Privileged roles require enhanced checks.",
evidence_required="Policy update with explicit admin-tier requirements; evidence of enhanced checks for PAM accounts."
))
else:
self.findings.append(IAMFinding(
control="HR-01: Pre-employment screening policy",
status="PASS", severity="LOW",
detail="Tiered screening policy documented.",
evidence_required="Policy document + HR records."
))
if a.offboarding_sla_hours > 24:
self.findings.append(IAMFinding(
control="HR-02: Offboarding SLA",
status="FAIL",
severity="CRITICAL",
detail=f"Offboarding SLA is {a.offboarding_sla_hours}h. NIS2 NCA expectation: ≤24h voluntary, ≤4h involuntary. Extended SLA creates post-employment access risk.",
evidence_required="Updated offboarding policy + HRIS-IdP integration evidence + sample offboarding tickets with timestamps."
))
elif not a.hris_iam_integration:
self.findings.append(IAMFinding(
control="HR-03: HRIS-IAM integration",
status="WARN",
severity="HIGH",
detail="No automated HRIS-to-IAM integration. Manual offboarding creates human error risk. NCA auditors expect automated or near-real-time revocation.",
evidence_required="Integration architecture, fallback manual process documentation, SLA monitoring evidence."
))
def _assess_access_control(self) -> None:
a = self.assessment
if not a.rbac_model_documented:
self.findings.append(IAMFinding(
control="AC-01: RBAC model documentation",
status="FAIL",
severity="HIGH",
detail="No documented role matrix. NCA auditors will request evidence that access follows a defined model with ownership and review cadence.",
evidence_required="Role matrix (roles → permissions → resources), role ownership assignments."
))
if not a.pam_solution_deployed:
self.findings.append(IAMFinding(
control="AC-02: Privileged Access Management",
status="FAIL" if not a.least_privilege_enforced else "WARN",
severity="CRITICAL",
detail="No PAM solution deployed. Standing privileged access without session recording or JIT elevation is a critical NIS2 gap. Privileged accounts are the primary target in 85% of NIS2-scope incidents.",
evidence_required="PAM deployment evidence, session recording samples, JIT approval workflow documentation."
))
if a.nhi_rotation_policy_defined and a.nhi_max_rotation_days > 90:
self.findings.append(IAMFinding(
control="AC-03: NHI credential rotation",
status="WARN",
severity="HIGH",
detail=f"NHI rotation policy defined but max rotation period is {a.nhi_max_rotation_days} days. ENISA recommends ≤90 days for privileged NHIs. Long-lived credentials amplify breach impact.",
evidence_required="Updated rotation policy, NHI inventory with last_rotated dates, automated rotation evidence."
))
elif not a.nhi_inventory_maintained:
self.findings.append(IAMFinding(
control="AC-04: NHI inventory",
status="FAIL",
severity="HIGH",
detail="No NHI inventory maintained. Service accounts, API keys and machine credentials without ownership are unmanageable and unauditable.",
evidence_required="NHI register with owner, system, created date, last rotated, expiry, permissions."
))
def _assess_access_reviews(self) -> None:
a = self.assessment
if not a.access_review_cadence_defined:
self.findings.append(IAMFinding(
control="AR-01: Access review cadence",
status="FAIL",
severity="HIGH",
detail="No documented access review cadence. NCA will expect privileged access reviewed quarterly, production access semi-annually. Ad-hoc reviews do not satisfy the requirement.",
evidence_required="Access review policy with frequency tiers, review campaign schedule, last 2 campaign reports."
))
elif a.privileged_review_frequency_days > 90:
self.findings.append(IAMFinding(
control="AR-01: Privileged access review frequency",
status="WARN",
severity="HIGH",
detail=f"Privileged access review frequency is {a.privileged_review_frequency_days} days. ENISA expects ≤90 days for privileged access.",
evidence_required="Updated review policy, quarterly campaign evidence."
))
if a.last_review_date and a.last_review_revocation_rate == 0.0:
self.findings.append(IAMFinding(
control="AR-02: Review effectiveness",
status="WARN",
severity="MEDIUM",
detail="Last access review had 0% revocation rate. This is statistically improbable and suggests the review process is not functioning (rubber-stamping). NCA auditors treat zero-revocation reviews with high suspicion.",
evidence_required="Investigation of review methodology; evidence of genuine entitlement validation."
))
def _assess_asset_management(self) -> None:
a = self.assessment
if not a.asset_inventory_maintained:
self.findings.append(IAMFinding(
control="AM-01: Asset inventory",
status="FAIL",
severity="HIGH",
detail="No asset inventory maintained. NIS2 Art.21(2)(i) explicitly requires asset management. Without inventory, scope for all other NIS2 measures is undefined.",
evidence_required="Asset inventory / CMDB covering compute, network, data stores, software, credentials."
))
elif not a.cmdb_automated:
self.findings.append(IAMFinding(
control="AM-02: CMDB automation",
status="WARN",
severity="MEDIUM",
detail="Asset inventory exists but is not automated. Manual CMDBs drift rapidly in cloud environments. NCA expects evidence that inventory reflects actual deployed state.",
evidence_required="Automation plan, reconciliation frequency evidence."
))
if not a.shadow_it_reconciliation or a.reconciliation_frequency_days > 30:
freq = a.reconciliation_frequency_days if a.shadow_it_reconciliation else "never"
self.findings.append(IAMFinding(
control="AM-03: Shadow IT reconciliation",
status="WARN",
severity="HIGH",
detail=f"Shadow IT reconciliation frequency: {freq} days. Cloud environments accumulate unregistered assets rapidly. Monthly reconciliation is the NCA-expected minimum.",
evidence_required="Reconciliation process documentation, weekly/monthly scan reports, finding resolution evidence."
))
if not a.decommissioning_process_documented:
self.findings.append(IAMFinding(
control="AM-04: Asset decommissioning",
status="WARN",
severity="MEDIUM",
detail="No decommissioning process documented. Orphaned assets are a persistent attack surface. NCA expects lifecycle management end-to-end.",
evidence_required="Decommissioning policy, sample decommission tickets with data deletion confirmation."
))
def _generate_report(self) -> dict:
critical = [f for f in self.findings if f.severity == "CRITICAL"]
high = [f for f in self.findings if f.severity == "HIGH"]
passes = [f for f in self.findings if f.status == "PASS"]
score = len(passes) / max(len(self.findings), 1) * 10
return {
"assessment_date": datetime.now().isoformat(),
"article": "NIS2 Art.21(2)(i)",
"overall_score": round(score, 1),
"audit_readiness": "HIGH" if score >= 8 else "MEDIUM" if score >= 5 else "LOW",
"critical_findings": len(critical),
"high_findings": len(high),
"total_findings": len(self.findings),
"findings": [
{
"control": f.control,
"status": f.status,
"severity": f.severity,
"detail": f.detail,
"evidence_required": f.evidence_required
}
for f in self.findings
]
}
# Example usage
if __name__ == "__main__":
# Typical mid-maturity SaaS organisation
assessment = AccessControlAssessment(
has_screening_policy=True,
screening_tiers_defined=False,
offboarding_sla_defined=True,
offboarding_sla_hours=48,
hris_iam_integration=False,
rbac_model_documented=True,
least_privilege_enforced=False,
pam_solution_deployed=False,
jit_access_available=False,
nhi_inventory_maintained=True,
nhi_rotation_policy_defined=True,
nhi_max_rotation_days=180,
access_review_cadence_defined=True,
privileged_review_frequency_days=180,
last_review_date="2026-02-01",
last_review_revocation_rate=0.03,
asset_inventory_maintained=True,
cmdb_automated=False,
shadow_it_reconciliation=True,
reconciliation_frequency_days=60,
decommissioning_process_documented=False,
)
assessor = NIS2IAMAssessor(assessment)
report = assessor.assess()
print(json.dumps(report, indent=2))
7. 25-Item NIS2 Art.21(2)(i) Compliance Checklist
Use this checklist to prepare for NCA audit evidence requests. Each item maps to a specific ENISA guideline or common NCA finding pattern.
HR Security
- HR-01 Written pre-employment screening policy with tiered requirements (standard / admin / contractor)
- HR-02 Evidence that screening was performed for all new hires and privileged-access appointments in the last 24 months
- HR-03 Exceptions register for deviations from screening policy (with documented approvals)
- HR-04 Acceptable use policy signed by all employees and contractors on day 1
- HR-05 Offboarding SLA defined: ≤4 hours for involuntary departures, ≤24 hours for voluntary
- HR-06 HRIS-to-IAM integration or equivalent automated trigger for offboarding
- HR-07 Active session termination process (not just account disable)
- HR-08 Post-departure access reconciliation report for last 12 months
Access Control
- AC-01 Documented RBAC role matrix with role ownership and last review date
- AC-02 Least privilege enforced: no standing admin rights for standard roles
- AC-03 PAM solution deployed with JIT elevation and session recording for privileged access
- AC-04 Admin accounts separate from daily-use accounts for all privileged users
- AC-05 NHI inventory: all service accounts, API keys, certs documented with owner and expiry
- AC-06 NHI rotation policy: ≤90 days for privileged credentials, automated rotation where possible
- AC-07 OIDC / short-lived tokens for CI/CD pipelines (no long-lived pipeline secrets)
- AC-08 Access control documented for all production systems (not just IdP — also DB, cloud console, k8s)
Access Reviews
- AR-01 Access review policy with tiered frequency: privileged ≤90 days, production ≤180 days
- AR-02 Last privileged access review completed within policy period (evidence: campaign report)
- AR-03 Revocations recorded in last review cycle (zero-revocation reviews require investigation)
- AR-04 Exception register: time-limited exceptions with business justification and next review date
Asset Management
- AM-01 Asset inventory / CMDB covering compute, network, data stores, software, and credentials
- AM-02 CMDB automated or reconciled against live cloud state (IaC state, cloud API scan)
- AM-03 Shadow IT reconciliation process with monthly or more frequent cadence
- AM-04 Documented asset classification scheme aligned to NIS2 criticality tiers
- AM-05 Decommissioning process: data deletion confirmation, credential revocation, CMDB update
8. Cross-Article Dependencies
Art.21(2)(i) is tightly coupled to other mandatory measures:
→ Art.21(2)(b) Incident Handling: Access logs are the primary detection signal for access-related incidents. Without a complete NHI inventory and access change log, incident analysis is blind. When a privileged account is compromised, Art.21(2)(b) processes depend on Art.21(2)(i) logging to reconstruct the timeline.
→ Art.21(2)(j) MFA: MFA enforcement requires knowing which accounts exist. The Art.21(2)(i) NHI inventory defines the scope of accounts requiring MFA implementation. PAM controls from Art.21(2)(i) typically enforce MFA at the privileged access layer.
→ Art.21(2)(e) SDL: Developer access to production systems must be controlled via Art.21(2)(i) access policies. Break-glass production access should flow through PAM, not direct credentials. The SDL guide covers how to structure production access for engineering teams.
→ Art.21(2)(h) Cryptography: Cryptographic key management is an asset management sub-problem. Key inventory, rotation schedules, and access to key material fall under both Art.21(2)(h) cryptography and Art.21(2)(i) asset management.
9. Implementation Timeline: 12-Week NCA Audit Readiness
| Week | Focus | Deliverable |
|---|---|---|
| 1-2 | Asset inventory gap analysis | CMDB scope defined, automation tooling selected |
| 3-4 | HR security policy update | Screening tiers documented, offboarding SLA updated |
| 5-6 | RBAC model documentation | Role matrix complete, ownership assigned |
| 7-8 | NHI inventory and rotation | All service accounts catalogued, rotation policy enforced |
| 9-10 | PAM deployment | JIT access operational, session recording active |
| 11 | Access review campaign | Privileged access certified, revocations processed |
| 12 | Evidence package | All 25 checklist items evidenced, NCA package assembled |
Priority ordering for organisations under time pressure:
- Offboarding SLA and HRIS automation (highest incident risk from leavers)
- NHI inventory (frequently exploited, low effort to document)
- Privileged access review (high NCA visibility)
- PAM deployment (most impactful but highest effort)
Conclusion
NIS2 Art.21(2)(i) is the identity and access spine of your compliance programme. HR security prevents credential exposure at organisation boundaries. Access control minimises blast radius when credentials are compromised. Asset management ensures your monitoring and protection covers everything that matters.
The June 2026 NCA audit window is approaching. Organisations that treat Art.21(2)(i) as a checkbox exercise — producing policies without operational evidence — will face findings. The NIS2IAMAssessor above provides a structured gap analysis starting point. The 25-item checklist maps directly to evidence categories NCA auditors will request.
Infrastructure hosted on EU-sovereign cloud eliminates one access control complexity: cloud provider employees cannot access your systems under foreign jurisdiction orders. Platforms like sota.io — EU-native, Cloud Act-free — remove the US jurisdiction risk from your Art.21(2)(i) asset management scope.
What's next in the NIS2 Art.21(2) series:
- Art.21(2)(f): Effectiveness assessment of cybersecurity measures — how to measure whether your controls actually work
- Art.21(2)(g): Cyber hygiene and security awareness training — the human layer of NIS2 compliance
- Art.21(2)(a): Risk analysis and security policies — the foundational risk management framework
NIS2 Directive 2022/2555 entered into force December 2022 with EU Member State transposition deadline October 2024. NCA supervisory activity, including on-site inspections under Art.32, is expected to intensify through mid-2026.