2026-04-16·14 min read·

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

SubparagraphRequirementPrimary Owner
Art.21(2)(a)Risk analysis and information system security policiesCISO / 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 securityProcurement / 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 measuresAudit / GRC
Art.21(2)(g)Basic cyber hygiene and trainingHR / Security Awareness
Art.21(2)(h)Cryptography and encryption policies (see cryptography guide)Architecture
Art.21(2)(i)HR security, access control and asset managementIT / 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:

  1. HR Security — controlling who enters, moves within, and exits the organisation in terms of access entitlements
  2. Access Control — governing which identities can access which resources under which conditions
  3. 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 CategoryMinimum ChecksRecommended for NIS2
Standard contributorIdentity verification + reference check+ employment history verification
Administrator / privileged accessAbove + criminal record check+ credit check (financial sector)
CISO / security functionAbove + extended reference checks+ security clearance where applicable
Third-party contractors with admin accessIdentity + reference (same as admin tier)+ periodic re-vetting annually

Documentation required for NCA audit:

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:

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:

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:

PatternDescriptionNIS2 Risk
Admin defaultAll developers have admin access to ease troubleshootingSingle compromised account = full environment takeover
Permission accumulationPermissions added but never removed over yearsInsider threat surface grows with tenure
Shared credentialsDatabase passwords known by entire teamNo individual attribution, no rotation trigger
Over-scoped service accountsApp service account has admin rights "just in case"Compromised app = lateral movement to entire environment
Long-lived tokensAPI keys and PATs never expireExposure 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:


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 TierDescriptionReview Frequency
Privileged / AdminCloud console admin, k8s cluster-admin, DB adminQuarterly
Production accessRead or write on production systemsSemi-annual
Standard accessInternal tooling, staging environmentsAnnual
External / third-partyVendor and contractor accessQuarterly

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)


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 CategoryExamplesClassification
ComputeVMs, containers, serverless functions, developer laptopsBy data processed
NetworkVPCs, subnets, firewalls, load balancers, VPN endpointsBy exposure (internal/external)
Data storesDatabases, blob storage, queues, cachesBy data classification
SoftwareApplications, dependencies, OS images, container base imagesBy criticality
Credentials / secretsAPI keys, certificates, service account keysBy privilege level
External servicesSaaS tools, cloud provider services, CDNBy 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:


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

Access Control

Access Reviews

Asset Management


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

WeekFocusDeliverable
1-2Asset inventory gap analysisCMDB scope defined, automation tooling selected
3-4HR security policy updateScreening tiers documented, offboarding SLA updated
5-6RBAC model documentationRole matrix complete, ownership assigned
7-8NHI inventory and rotationAll service accounts catalogued, rotation policy enforced
9-10PAM deploymentJIT access operational, session recording active
11Access review campaignPrivileged access certified, revocations processed
12Evidence packageAll 25 checklist items evidenced, NCA package assembled

Priority ordering for organisations under time pressure:

  1. Offboarding SLA and HRIS automation (highest incident risk from leavers)
  2. NHI inventory (frequently exploited, low effort to document)
  3. Privileged access review (high NCA visibility)
  4. 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:


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.