TL;DR
La directive NIS2 entre en application en octobre 2024, impactant ~160,000 entités en Europe dont ~15,000 en France. Cet article fournit un mapping technique complet des 10 exigences de l'Article 21vers les services cloud AWS, avec du code Terraform prêt à déployer et des scripts de vérification automatisée. Les amendes atteignent 10M€ ou 2% du CA mondial.
160k
Entités impactées EU
10
Exigences Art. 21
24h
Délai notification
10M€
Amende maximale
Jan 2023
NIS2 entrée en vigueur
Oct 2024
Transposition nationale
Avr 2025
Enregistrement entités
Oct 2025
Audits & sanctions
15k
Entités en France
Essentielles + Importantes
24/72h
Notification incident
Alerte / Rapport complet
€10M
Amende maximale
ou 2% CA mondial
Oct 2024
Date limite
Transposition nationale
1Introduction à NIS2
La Directive NIS2 (Network and Information Security 2) remplace NIS1 et élargit considérablement le périmètre des entités concernées. Elle impose des mesures de cybersécurité renforcées et un régime de notification des incidents strict de 24 heures[1].
Qui est concerné par NIS2 ?
Entités Essentielles
- • Énergie, Transport, Banque
- • Santé, Eau potable
- • Infrastructure numérique
- • Administration publique
Entités Importantes
- • Services postaux
- • Gestion déchets, Chimie
- • Alimentation, Fabrication
- • Services numériques (Cloud, SaaS)
Secteurs Impactés par NIS2
Énergie
~2,500 entités
Transport
~3,200 entités
Banque
~1,800 entités
Santé
~4,100 entités
Eau
~800 entités
Numérique
~12,000 entités
Poste
~500 entités
Chimie
~1,200 entités
Alimentation
~2,800 entités
Fabrication
~8,500 entités
* Estimations pour la France. Source: ANSSI, Commission Européenne 2024
Régime de Sanctions NIS2
10M€
ou 2% du CA mondial
Le montant le plus élevé s'applique
7M€
ou 1.4% du CA mondial
Le montant le plus élevé s'applique
Responsabilité personnelle: Les dirigeants peuvent être personnellement tenus responsables et interdits d'exercer des fonctions de direction.
2Exigences Techniques Article 21
L'Article 21 définit 10 mesures de gestion des risques que les entités doivent implémenter. Voici leur mapping vers les contrôles cloud :
Art. 21(2)(a)
Politiques de sécurité
Art. 21(2)(b)
Gestion des incidents
Art. 21(2)(c)
Continuité d'activité
Art. 21(2)(d)
Sécurité supply chain
Art. 21(2)(e)
Sécurité acquisition
Art. 21(2)(g)
Hygiène cyber
Art. 21(2)(h)
Cryptographie
Art. 21(2)(i)
Contrôle d'accès
Art. 21(2)(j)
Authentification
| Article | Exigence | Contrôles Cloud | Priorité |
|---|---|---|---|
| 21(2)(a) | Politiques de sécurité SI | SCPs, Security Hub, Config Rules | Haute |
| 21(2)(b) | Gestion des incidents | GuardDuty, CloudTrail, EventBridge | Critique |
| 21(2)(c) | Continuité d'activité | AWS Backup, Multi-AZ, Cross-Region | Haute |
| 21(2)(d) | Sécurité supply chain | Inspector, ECR Scanning, SBOM | Moyenne |
| 21(2)(e) | Sécurité acquisition/dev | CodeGuru, Checkov, SAST/DAST | Moyenne |
| 21(2)(f) | Évaluation efficacité | Security Hub Score, Audits | Moyenne |
| 21(2)(g) | Hygiène cyber & formation | Well-Architected, Training | Moyenne |
| 21(2)(h) | Cryptographie | KMS, ACM, EBS/S3 Encryption | Haute |
| 21(2)(i) | Contrôle d'accès | IAM, Access Analyzer, SCPs | Haute |
| 21(2)(j) | Authentification MFA | IAM MFA, SSO, Cognito | Critique |
3Mapping Cloud Détaillé
3.1 Service Control Policy (SCP) NIS2
SCP au niveau AWS Organizations pour imposer les guardrails NIS2 :
1{2 "Version": "2012-10-17",3 "Statement": [4 {5 "Sid": "NIS2Art21h-RequireEncryption",6 "Effect": "Deny",7 "Action": [8 "s3:PutObject"9 ],10 "Resource": "*",11 "Condition": {12 "Null": {13 "s3:x-amz-server-side-encryption": "true"14 }15 }16 },17 {18 "Sid": "NIS2Art21h-RequireSecureTransport",19 "Effect": "Deny",20 "Action": "s3:*",21 "Resource": "*",22 "Condition": {23 "Bool": {24 "aws:SecureTransport": "false"25 }26 }27 },28 {29 "Sid": "NIS2Art21b-PreventLoggingDisable",30 "Effect": "Deny",31 "Action": [32 "cloudtrail:DeleteTrail",33 "cloudtrail:StopLogging",34 "cloudtrail:UpdateTrail",35 "guardduty:DeleteDetector",36 "guardduty:DisassociateFromMasterAccount",37 "securityhub:DisableSecurityHub",38 "config:DeleteConfigurationRecorder",39 "config:StopConfigurationRecorder"40 ],41 "Resource": "*",42 "Condition": {43 "StringNotLike": {44 "aws:PrincipalArn": [45 "arn:aws:iam::*:role/NIS2SecurityAdmin"46 ]47 }48 }49 },50 {51 "Sid": "NIS2Art21j-RequireMFA",52 "Effect": "Deny",53 "Action": [54 "iam:CreateAccessKey",55 "iam:CreateLoginProfile",56 "iam:UpdateLoginProfile",57 "iam:AttachUserPolicy",58 "iam:AttachRolePolicy",59 "iam:PutUserPolicy",60 "iam:PutRolePolicy"61 ],62 "Resource": "*",63 "Condition": {64 "BoolIfExists": {65 "aws:MultiFactorAuthPresent": "false"66 }67 }68 },69 {70 "Sid": "NIS2Art21c-PreventBackupDeletion",71 "Effect": "Deny",72 "Action": [73 "backup:DeleteBackupPlan",74 "backup:DeleteBackupVault",75 "backup:DeleteRecoveryPoint"76 ],77 "Resource": "*",78 "Condition": {79 "StringNotLike": {80 "aws:PrincipalArn": [81 "arn:aws:iam::*:role/NIS2BackupAdmin"82 ]83 }84 }85 },86 {87 "Sid": "NIS2Art21a-RequireIMDSv2",88 "Effect": "Deny",89 "Action": "ec2:RunInstances",90 "Resource": "arn:aws:ec2:*:*:instance/*",91 "Condition": {92 "StringNotEquals": {93 "ec2:MetadataHttpTokens": "required"94 }95 }96 },97 {98 "Sid": "NIS2-RestrictToEURegions",99 "Effect": "Deny",100 "Action": "*",101 "Resource": "*",102 "Condition": {103 "StringNotEquals": {104 "aws:RequestedRegion": [105 "eu-west-1",106 "eu-west-2",107 "eu-west-3",108 "eu-central-1",109 "eu-central-2",110 "eu-north-1",111 "eu-south-1",112 "eu-south-2"113 ]114 },115 "ForAnyValue:StringNotLike": {116 "aws:PrincipalArn": [117 "arn:aws:iam::*:role/OrganizationAccountAccessRole"118 ]119 }120 }121 }122 ]123}Points clés du SCP NIS2
- • Art. 21(2)(h) : Force le chiffrement S3 et le transport sécurisé
- • Art. 21(2)(b) : Empêche la désactivation des logs de sécurité
- • Art. 21(2)(j) : Exige MFA pour les actions IAM sensibles
- • Art. 21(2)(c) : Protège les backups contre la suppression
- • Souveraineté : Restreint aux régions EU uniquement
4Implémentation Terraform
Module Terraform complet pour déployer une baseline NIS2 sur AWS :
1# terraform-nis2-baseline/main.tf2# Module Terraform pour conformité NIS2 de base34variable "organization_id" {5 description = "AWS Organization ID"6 type = string7}89variable "notification_email" {10 description = "Email pour notifications incidents"11 type = string12}1314variable "csirt_endpoint" {15 description = "Endpoint CSIRT/ANSSI pour notifications"16 type = string17 default = ""18}1920# ============================================21# Art. 21(2)(a) - Politiques de sécurité22# ============================================2324resource "aws_securityhub_account" "main" {25 enable_default_standards = true26 auto_enable_controls = true27}2829resource "aws_securityhub_standards_subscription" "cis" {30 standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.4.0"31 depends_on = [aws_securityhub_account.main]32}3334resource "aws_securityhub_standards_subscription" "aws_foundational" {35 standards_arn = "arn:aws:securityhub:eu-west-1::standards/aws-foundational-security-best-practices/v/1.0.0"36 depends_on = [aws_securityhub_account.main]37}3839# ============================================40# Art. 21(2)(b) - Gestion des incidents41# ============================================4243resource "aws_guardduty_detector" "main" {44 enable = true45 finding_publishing_frequency = "FIFTEEN_MINUTES"4647 datasources {48 s3_logs {49 enable = true50 }51 kubernetes {52 audit_logs {53 enable = true54 }55 }56 malware_protection {57 scan_ec2_instance_with_findings {58 ebs_volumes {59 enable = true60 }61 }62 }63 }6465 tags = {66 NIS2_Article = "21(2)(b)"67 Compliance = "required"68 }69}7071resource "aws_cloudtrail" "nis2_audit" {72 name = "nis2-audit-trail"73 s3_bucket_name = aws_s3_bucket.cloudtrail.id74 include_global_service_events = true75 is_multi_region_trail = true76 enable_log_file_validation = true7778 event_selector {79 read_write_type = "All"80 include_management_events = true8182 data_resource {83 type = "AWS::S3::Object"84 values = ["arn:aws:s3"]85 }86 }8788 tags = {89 NIS2_Article = "21(2)(b)"90 }91}9293# SNS Topic for incident notifications94resource "aws_sns_topic" "security_incidents" {95 name = "nis2-security-incidents"9697 tags = {98 NIS2_Article = "23"99 }100}101102resource "aws_sns_topic_subscription" "email" {103 topic_arn = aws_sns_topic.security_incidents.arn104 protocol = "email"105 endpoint = var.notification_email106}107108# EventBridge rule for GuardDuty findings109resource "aws_cloudwatch_event_rule" "guardduty_findings" {110 name = "nis2-guardduty-high-severity"111 description = "NIS2 Art.23 - Alert on high severity findings"112113 event_pattern = jsonencode({114 source = ["aws.guardduty"]115 detail-type = ["GuardDuty Finding"]116 detail = {117 severity = [{ numeric = [">=", 7] }]118 }119 })120121 tags = {122 NIS2_Article = "23"123 }124}125126resource "aws_cloudwatch_event_target" "sns" {127 rule = aws_cloudwatch_event_rule.guardduty_findings.name128 target_id = "SendToSNS"129 arn = aws_sns_topic.security_incidents.arn130}131132# ============================================133# Art. 21(2)(c) - Continuité d'activité134# ============================================135136resource "aws_backup_plan" "nis2_backup" {137 name = "nis2-backup-plan"138139 rule {140 rule_name = "daily-backup"141 target_vault_name = aws_backup_vault.main.name142 schedule = "cron(0 5 ? * * *)"143144 lifecycle {145 delete_after = 90146 }147148 copy_action {149 destination_vault_arn = aws_backup_vault.dr.arn150 lifecycle {151 delete_after = 30152 }153 }154 }155156 rule {157 rule_name = "weekly-backup"158 target_vault_name = aws_backup_vault.main.name159 schedule = "cron(0 5 ? * SAT *)"160161 lifecycle {162 delete_after = 365163 }164 }165166 tags = {167 NIS2_Article = "21(2)(c)"168 }169}170171resource "aws_backup_vault" "main" {172 name = "nis2-backup-vault"173 kms_key_arn = aws_kms_key.backup.arn174175 tags = {176 NIS2_Article = "21(2)(c)"177 }178}179180resource "aws_backup_vault" "dr" {181 provider = aws.dr_region182 name = "nis2-backup-vault-dr"183 kms_key_arn = aws_kms_key.backup_dr.arn184185 tags = {186 NIS2_Article = "21(2)(c)"187 }188}189190# ============================================191# Art. 21(2)(h) - Cryptographie192# ============================================193194resource "aws_kms_key" "main" {195 description = "NIS2 main encryption key"196 deletion_window_in_days = 30197 enable_key_rotation = true198 multi_region = true199200 policy = jsonencode({201 Version = "2012-10-17"202 Statement = [203 {204 Sid = "Enable IAM policies"205 Effect = "Allow"206 Principal = {207 AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"208 }209 Action = "kms:*"210 Resource = "*"211 }212 ]213 })214215 tags = {216 NIS2_Article = "21(2)(h)"217 }218}219220resource "aws_ebs_encryption_by_default" "enabled" {221 enabled = true222}223224resource "aws_s3_account_public_access_block" "main" {225 block_public_acls = true226 block_public_policy = true227 ignore_public_acls = true228 restrict_public_buckets = true229}230231# ============================================232# Art. 21(2)(i)(j) - Contrôle d'accès & MFA233# ============================================234235resource "aws_iam_account_password_policy" "strict" {236 minimum_password_length = 14237 require_lowercase_characters = true238 require_numbers = true239 require_uppercase_characters = true240 require_symbols = true241 allow_users_to_change_password = true242 max_password_age = 90243 password_reuse_prevention = 24244}245246resource "aws_accessanalyzer_analyzer" "main" {247 analyzer_name = "nis2-access-analyzer"248 type = "ACCOUNT"249250 tags = {251 NIS2_Article = "21(2)(i)"252 }253}254255# ============================================256# Art. 23 - Notification des incidents257# ============================================258259resource "aws_lambda_function" "incident_notifier" {260 filename = "incident_notifier.zip"261 function_name = "nis2-incident-notifier"262 role = aws_iam_role.lambda_incident.arn263 handler = "index.handler"264 runtime = "python3.11"265 timeout = 30266267 environment {268 variables = {269 CSIRT_ENDPOINT = var.csirt_endpoint270 SNS_TOPIC_ARN = aws_sns_topic.security_incidents.arn271 NOTIFICATION_DELAY = "0" # Immediate for critical272 }273 }274275 tags = {276 NIS2_Article = "23"277 Purpose = "24h incident notification"278 }279}280281# Output compliance status282output "nis2_compliance_summary" {283 value = {284 security_hub = "Enabled with CIS + AWS Foundational"285 guardduty = "Enabled with all data sources"286 cloudtrail = "Multi-region with validation"287 backup = "Daily + weekly with cross-region"288 encryption = "KMS with rotation, EBS default encryption"289 access_control = "Access Analyzer + strict password policy"290 incident_notification = "SNS + Lambda for 24h notification"291 }292}5Monitoring de Conformité
5.1 Script de Vérification Automatisée
Script Python pour auditer la conformité NIS2 Article 21 sur AWS :
1#!/usr/bin/env python32"""3NIS2 Compliance Checker for AWS4Automated assessment against NIS2 Article 21 requirements5"""67import boto38import json9from dataclasses import dataclass10from typing import List, Dict, Optional11from enum import Enum12from datetime import datetime, timedelta1314class ComplianceStatus(Enum):15 COMPLIANT = "compliant"16 PARTIAL = "partial"17 NON_COMPLIANT = "non_compliant"18 NOT_APPLICABLE = "not_applicable"1920@dataclass21class NIS2Finding:22 article: str23 requirement: str24 status: ComplianceStatus25 evidence: List[str]26 remediation: Optional[str]27 severity: str2829class NIS2ComplianceChecker:30 """31 Vérifie la conformité NIS2 Article 21 sur AWS32 """3334 def __init__(self):35 self.iam = boto3.client('iam')36 self.s3 = boto3.client('s3')37 self.ec2 = boto3.client('ec2')38 self.kms = boto3.client('kms')39 self.cloudtrail = boto3.client('cloudtrail')40 self.guardduty = boto3.client('guardduty')41 self.securityhub = boto3.client('securityhub')42 self.backup = boto3.client('backup')43 self.findings: List[NIS2Finding] = []4445 def run_full_assessment(self) -> List[NIS2Finding]:46 """Execute all NIS2 compliance checks"""47 self.check_art21_a_security_policies()48 self.check_art21_b_incident_handling()49 self.check_art21_c_business_continuity()50 self.check_art21_h_cryptography()51 self.check_art21_i_access_control()52 self.check_art21_j_authentication()53 self.check_art23_incident_notification()54 return self.findings5556 def check_art21_a_security_policies(self):57 """Art. 21(2)(a): Politiques de sécurité des SI"""58 evidence = []59 status = ComplianceStatus.NON_COMPLIANT6061 # Check for SCPs in Organization62 try:63 orgs = boto3.client('organizations')64 policies = orgs.list_policies(Filter='SERVICE_CONTROL_POLICY')65 if policies['Policies']:66 evidence.append(f"{len(policies['Policies'])} SCPs définies")67 status = ComplianceStatus.PARTIAL68 except:69 evidence.append("AWS Organizations non configuré")7071 # Check Security Hub enabled72 try:73 hubs = self.securityhub.describe_hub()74 evidence.append("Security Hub activé")75 if status == ComplianceStatus.PARTIAL:76 status = ComplianceStatus.COMPLIANT77 except:78 evidence.append("Security Hub non activé")7980 self.findings.append(NIS2Finding(81 article="Art. 21(2)(a)",82 requirement="Politiques de sécurité des systèmes d'information",83 status=status,84 evidence=evidence,85 remediation="Activer Security Hub et définir des SCPs",86 severity="HIGH"87 ))8889 def check_art21_b_incident_handling(self):90 """Art. 21(2)(b): Gestion des incidents"""91 evidence = []92 checks_passed = 09394 # GuardDuty enabled95 try:96 detectors = self.guardduty.list_detectors()97 if detectors['DetectorIds']:98 evidence.append("GuardDuty activé")99 checks_passed += 1100 except:101 evidence.append("GuardDuty non activé")102103 # CloudTrail enabled104 try:105 trails = self.cloudtrail.describe_trails()106 active_trails = [t for t in trails['trailList'] if t.get('IsMultiRegionTrail')]107 if active_trails:108 evidence.append(f"{len(active_trails)} CloudTrail multi-région")109 checks_passed += 1110 except:111 evidence.append("CloudTrail non configuré")112113 # EventBridge rules for security114 try:115 events = boto3.client('events')116 rules = events.list_rules(NamePrefix='security')117 if rules['Rules']:118 evidence.append(f"{len(rules['Rules'])} règles EventBridge sécurité")119 checks_passed += 1120 except:121 pass122123 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else124 ComplianceStatus.PARTIAL if checks_passed >= 1 else125 ComplianceStatus.NON_COMPLIANT)126127 self.findings.append(NIS2Finding(128 article="Art. 21(2)(b)",129 requirement="Gestion des incidents",130 status=status,131 evidence=evidence,132 remediation="Activer GuardDuty, CloudTrail multi-région, configurer alertes",133 severity="CRITICAL"134 ))135136 def check_art21_c_business_continuity(self):137 """Art. 21(2)(c): Continuité d'activité et gestion de crise"""138 evidence = []139 checks_passed = 0140141 # AWS Backup plans142 try:143 plans = self.backup.list_backup_plans()144 if plans['BackupPlansList']:145 evidence.append(f"{len(plans['BackupPlansList'])} plans de backup")146 checks_passed += 1147 except:148 evidence.append("Aucun plan AWS Backup")149150 # Multi-AZ RDS151 try:152 rds = boto3.client('rds')153 instances = rds.describe_db_instances()154 multi_az = [i for i in instances['DBInstances'] if i.get('MultiAZ')]155 if multi_az:156 evidence.append(f"{len(multi_az)} RDS Multi-AZ")157 checks_passed += 1158 except:159 pass160161 # S3 Cross-Region Replication162 try:163 buckets = self.s3.list_buckets()164 for bucket in buckets['Buckets'][:5]: # Sample165 try:166 repl = self.s3.get_bucket_replication(Bucket=bucket['Name'])167 evidence.append(f"S3 CRR configuré sur {bucket['Name']}")168 checks_passed += 1169 break170 except:171 pass172 except:173 pass174175 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else176 ComplianceStatus.PARTIAL if checks_passed >= 1 else177 ComplianceStatus.NON_COMPLIANT)178179 self.findings.append(NIS2Finding(180 article="Art. 21(2)(c)",181 requirement="Continuité d'activité et gestion de crise",182 status=status,183 evidence=evidence,184 remediation="Configurer AWS Backup, Multi-AZ, Cross-Region Replication",185 severity="HIGH"186 ))187188 def check_art21_h_cryptography(self):189 """Art. 21(2)(h): Politiques de cryptographie"""190 evidence = []191 checks_passed = 0192193 # KMS keys with rotation194 try:195 keys = self.kms.list_keys()196 for key in keys['Keys'][:10]:197 try:198 rotation = self.kms.get_key_rotation_status(KeyId=key['KeyId'])199 if rotation['KeyRotationEnabled']:200 checks_passed += 1201 except:202 pass203 evidence.append(f"{checks_passed} clés KMS avec rotation")204 except:205 evidence.append("Impossible de vérifier KMS")206207 # S3 default encryption208 try:209 buckets = self.s3.list_buckets()210 encrypted = 0211 for bucket in buckets['Buckets'][:10]:212 try:213 enc = self.s3.get_bucket_encryption(Bucket=bucket['Name'])214 encrypted += 1215 except:216 pass217 evidence.append(f"{encrypted}/10 buckets chiffrés par défaut")218 if encrypted >= 8:219 checks_passed += 1220 except:221 pass222223 # EBS default encryption224 try:225 ebs_enc = self.ec2.get_ebs_encryption_by_default()226 if ebs_enc['EbsEncryptionByDefault']:227 evidence.append("EBS chiffrement par défaut activé")228 checks_passed += 1229 except:230 evidence.append("EBS chiffrement par défaut non activé")231232 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else233 ComplianceStatus.PARTIAL if checks_passed >= 1 else234 ComplianceStatus.NON_COMPLIANT)235236 self.findings.append(NIS2Finding(237 article="Art. 21(2)(h)",238 requirement="Politiques d'utilisation de la cryptographie",239 status=status,240 evidence=evidence,241 remediation="Activer rotation KMS, chiffrement S3/EBS par défaut",242 severity="HIGH"243 ))244245 def check_art21_i_access_control(self):246 """Art. 21(2)(i): Sécurité des ressources humaines, contrôle d'accès"""247 evidence = []248 checks_passed = 0249250 # Check for IAM Access Analyzer251 try:252 aa = boto3.client('accessanalyzer')253 analyzers = aa.list_analyzers()254 if analyzers['analyzers']:255 evidence.append("IAM Access Analyzer activé")256 checks_passed += 1257 except:258 evidence.append("IAM Access Analyzer non configuré")259260 # Check password policy261 try:262 policy = self.iam.get_account_password_policy()263 pp = policy['PasswordPolicy']264 if pp.get('RequireUppercaseCharacters') and pp.get('RequireLowercaseCharacters'):265 evidence.append("Politique mot de passe forte")266 checks_passed += 1267 except:268 evidence.append("Politique mot de passe non définie")269270 # Check for unused credentials271 try:272 report = self.iam.generate_credential_report()273 # In practice, would analyze the report274 evidence.append("Rapport credentials généré")275 except:276 pass277278 status = (ComplianceStatus.COMPLIANT if checks_passed >= 2 else279 ComplianceStatus.PARTIAL if checks_passed >= 1 else280 ComplianceStatus.NON_COMPLIANT)281282 self.findings.append(NIS2Finding(283 article="Art. 21(2)(i)",284 requirement="Contrôle d'accès et gestion des actifs",285 status=status,286 evidence=evidence,287 remediation="Activer Access Analyzer, renforcer password policy",288 severity="HIGH"289 ))290291 def check_art21_j_authentication(self):292 """Art. 21(2)(j): Authentification multi-facteur"""293 evidence = []294295 # Check MFA on root296 try:297 summary = self.iam.get_account_summary()298 if summary['SummaryMap'].get('AccountMFAEnabled', 0) == 1:299 evidence.append("MFA activé sur compte root")300 else:301 evidence.append("MFA NON activé sur root!")302 except:303 pass304305 # Check MFA on users306 try:307 users = self.iam.list_users()308 mfa_users = 0309 for user in users['Users']:310 mfa = self.iam.list_mfa_devices(UserName=user['UserName'])311 if mfa['MFADevices']:312 mfa_users += 1313 pct = (mfa_users / len(users['Users'])) * 100 if users['Users'] else 0314 evidence.append(f"{mfa_users}/{len(users['Users'])} users avec MFA ({pct:.0f}%)")315316 status = (ComplianceStatus.COMPLIANT if pct >= 90 else317 ComplianceStatus.PARTIAL if pct >= 50 else318 ComplianceStatus.NON_COMPLIANT)319 except:320 status = ComplianceStatus.NON_COMPLIANT321322 self.findings.append(NIS2Finding(323 article="Art. 21(2)(j)",324 requirement="Authentification multi-facteur ou continue",325 status=status,326 evidence=evidence,327 remediation="Imposer MFA pour tous les utilisateurs IAM",328 severity="CRITICAL"329 ))330331 def check_art23_incident_notification(self):332 """Art. 23: Notification des incidents (24h/72h)"""333 evidence = []334335 # Check for SNS topics for incident notification336 try:337 sns = boto3.client('sns')338 topics = sns.list_topics()339 security_topics = [t for t in topics['Topics']340 if 'security' in t['TopicArn'].lower() or341 'incident' in t['TopicArn'].lower()]342 if security_topics:343 evidence.append(f"{len(security_topics)} topics SNS incidents")344 else:345 evidence.append("Aucun topic SNS pour incidents")346 except:347 pass348349 # Check EventBridge rules350 try:351 events = boto3.client('events')352 rules = events.list_rules()353 incident_rules = [r for r in rules['Rules']354 if 'incident' in r['Name'].lower() or355 'alert' in r['Name'].lower()]356 evidence.append(f"{len(incident_rules)} règles EventBridge alertes")357 except:358 pass359360 status = ComplianceStatus.PARTIAL if evidence else ComplianceStatus.NON_COMPLIANT361362 self.findings.append(NIS2Finding(363 article="Art. 23",364 requirement="Notification incidents (24h alerte, 72h rapport)",365 status=status,366 evidence=evidence,367 remediation="Configurer alertes automatiques vers CSIRT/ANSSI",368 severity="CRITICAL"369 ))370371 def generate_report(self) -> str:372 """Generate NIS2 compliance report"""373 report = []374 report.append("=" * 60)375 report.append("RAPPORT DE CONFORMITÉ NIS2")376 report.append(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}")377 report.append("=" * 60)378379 # Summary380 compliant = sum(1 for f in self.findings if f.status == ComplianceStatus.COMPLIANT)381 partial = sum(1 for f in self.findings if f.status == ComplianceStatus.PARTIAL)382 non_compliant = sum(1 for f in self.findings if f.status == ComplianceStatus.NON_COMPLIANT)383384 report.append(f"\nRésumé: {compliant} conformes, {partial} partiels, {non_compliant} non conformes")385 report.append(f"Score global: {(compliant / len(self.findings)) * 100:.0f}%")386387 # Details388 for finding in self.findings:389 report.append(f"\n[{finding.status.value.upper()}] {finding.article}")390 report.append(f" Exigence: {finding.requirement}")391 report.append(f" Sévérité: {finding.severity}")392 for e in finding.evidence:393 report.append(f" - {e}")394 if finding.remediation:395 report.append(f" Remédiation: {finding.remediation}")396397 return "\n".join(report)398399400def main():401 checker = NIS2ComplianceChecker()402 findings = checker.run_full_assessment()403 report = checker.generate_report()404 print(report)405406407if __name__ == '__main__':408 main()5.2 Queries Athena pour Monitoring Continu
Requêtes SQL pour monitorer la conformité en continu via CloudTrail :
1-- NIS2 Compliance Monitoring Queries2-- Run against CloudTrail logs in Athena34-- Query 1: Art.21(2)(b) - Incident Detection Timeline5-- Track security incidents for 24h notification requirement6SELECT7 eventTime,8 eventSource,9 eventName,10 userIdentity.arn AS actor,11 sourceIPAddress,12 errorCode,13 CASE14 WHEN eventName LIKE '%Unauthorized%' THEN 'CRITICAL'15 WHEN eventName LIKE '%AccessDenied%' THEN 'HIGH'16 WHEN errorCode = 'AccessDenied' THEN 'HIGH'17 ELSE 'MEDIUM'18 END AS severity,19 DATE_DIFF('hour', eventTime, current_timestamp) AS hours_ago20FROM cloudtrail_logs21WHERE eventTime > date_add('day', -7, current_timestamp)22 AND (23 errorCode IS NOT NULL24 OR eventName LIKE '%Delete%'25 OR eventName LIKE '%Unauthorized%'26 OR eventName IN (27 'ConsoleLogin',28 'StopLogging',29 'DeleteTrail',30 'DisableKey',31 'PutBucketPolicy',32 'PutBucketAcl'33 )34 )35ORDER BY eventTime DESC;3637-- Query 2: Art.21(2)(h) - Cryptography Compliance38-- Check encryption status of resources39SELECT40 'S3' AS service,41 COUNT(*) AS total_buckets,42 SUM(CASE WHEN encryption_enabled THEN 1 ELSE 0 END) AS encrypted,43 ROUND(SUM(CASE WHEN encryption_enabled THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS pct_encrypted44FROM (45 SELECT46 bucket_name,47 CASE WHEN server_side_encryption IS NOT NULL THEN true ELSE false END AS encryption_enabled48 FROM s3_inventory49)50UNION ALL51SELECT52 'EBS' AS service,53 COUNT(*) AS total_volumes,54 SUM(CASE WHEN encrypted THEN 1 ELSE 0 END) AS encrypted,55 ROUND(SUM(CASE WHEN encrypted THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS pct_encrypted56FROM ebs_volumes57UNION ALL58SELECT59 'RDS' AS service,60 COUNT(*) AS total_instances,61 SUM(CASE WHEN storage_encrypted THEN 1 ELSE 0 END) AS encrypted,62 ROUND(SUM(CASE WHEN storage_encrypted THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS pct_encrypted63FROM rds_instances;6465-- Query 3: Art.21(2)(j) - MFA Compliance66-- Check MFA adoption across IAM users67SELECT68 u.user_name,69 u.create_date,70 u.password_last_used,71 CASE WHEN m.serial_number IS NOT NULL THEN 'MFA_ENABLED' ELSE 'NO_MFA' END AS mfa_status,72 DATE_DIFF('day', u.password_last_used, current_timestamp) AS days_since_login,73 CASE74 WHEN m.serial_number IS NULL AND u.password_last_used IS NOT NULL THEN 'NON_COMPLIANT'75 WHEN m.serial_number IS NOT NULL THEN 'COMPLIANT'76 ELSE 'REVIEW'77 END AS nis2_status78FROM iam_users u79LEFT JOIN iam_mfa_devices m ON u.user_name = m.user_name80ORDER BY mfa_status, days_since_login DESC;8182-- Query 4: Art.21(2)(i) - Access Review83-- Identify over-privileged and unused credentials84SELECT85 user_name,86 access_key_id,87 status,88 create_date,89 access_key_last_used_date,90 DATE_DIFF('day', access_key_last_used_date, current_timestamp) AS days_unused,91 CASE92 WHEN DATE_DIFF('day', access_key_last_used_date, current_timestamp) > 90 THEN 'STALE'93 WHEN DATE_DIFF('day', access_key_last_used_date, current_timestamp) > 30 THEN 'REVIEW'94 ELSE 'ACTIVE'95 END AS credential_status96FROM iam_access_keys97WHERE status = 'Active'98ORDER BY days_unused DESC;99100-- Query 5: Art.23 - Incident Response Timeline101-- Generate incident timeline for CSIRT/ANSSI reporting102WITH incidents AS (103 SELECT104 eventTime AS detection_time,105 eventSource,106 eventName,107 userIdentity.arn AS actor,108 sourceIPAddress,109 awsRegion,110 requestParameters,111 responseElements112 FROM cloudtrail_logs113 WHERE eventTime > date_add('hour', -24, current_timestamp)114 AND (115 errorCode IN ('AccessDenied', 'UnauthorizedAccess')116 OR eventName IN (117 'DeleteTrail', 'StopLogging', 'DeleteFlowLogs',118 'DeleteDetector', 'DisableSecurityHub'119 )120 )121)122SELECT123 detection_time,124 DATE_DIFF('hour', detection_time, current_timestamp) AS hours_since_detection,125 CASE126 WHEN DATE_DIFF('hour', detection_time, current_timestamp) <= 24 THEN 'WITHIN_24H_WINDOW'127 ELSE 'PAST_DEADLINE'128 END AS notification_status,129 eventSource,130 eventName,131 actor,132 sourceIPAddress133FROM incidents134ORDER BY detection_time DESC;Checklist Conformité NIS2
Références
Préparez votre conformité NIS2
La plateforme Cyvex automatise l'évaluation de conformité NIS2 sur vos environnements cloud. Mapping Article 21, monitoring continu, et rapports prêts pour l'ANSSI.