Aller au contenu principal
Retour au blog
ComplianceNIS2EU RegulationDeadline Oct 2024

NIS2 et Cloud : Guide Technique de Conformité

Mapping complet NIS2 Article 21 vers les contrôles AWS/Azure/GCP. Code Terraform, scripts de vérification, et monitoring de conformité.

C
Cyvex Research Lab
28 Décembre 2024
28 min
Code source

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

Calendrier NIS2Deadline Approche

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

Essentiels
Importants

É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

Entités Essentielles

10M€

ou 2% du CA mondial

Le montant le plus élevé s'applique

Entités Importantes

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 :

Mapping NIS2 → Cloud Controls
ConformePartielNon conforme

Art. 21(2)(a)

Politiques de sécurité

Partiel

Art. 21(2)(b)

Gestion des incidents

Conforme

Art. 21(2)(c)

Continuité d'activité

Conforme

Art. 21(2)(d)

Sécurité supply chain

Partiel

Art. 21(2)(e)

Sécurité acquisition

Partiel

Art. 21(2)(g)

Hygiène cyber

Non conforme

Art. 21(2)(h)

Cryptographie

Conforme

Art. 21(2)(i)

Contrôle d'accès

Partiel

Art. 21(2)(j)

Authentification

Conforme
ArticleExigenceContrôles CloudPriorité
21(2)(a)Politiques de sécurité SISCPs, Security Hub, Config RulesHaute
21(2)(b)Gestion des incidentsGuardDuty, CloudTrail, EventBridgeCritique
21(2)(c)Continuité d'activitéAWS Backup, Multi-AZ, Cross-RegionHaute
21(2)(d)Sécurité supply chainInspector, ECR Scanning, SBOMMoyenne
21(2)(e)Sécurité acquisition/devCodeGuru, Checkov, SAST/DASTMoyenne
21(2)(f)Évaluation efficacitéSecurity Hub Score, AuditsMoyenne
21(2)(g)Hygiène cyber & formationWell-Architected, TrainingMoyenne
21(2)(h)CryptographieKMS, ACM, EBS/S3 EncryptionHaute
21(2)(i)Contrôle d'accèsIAM, Access Analyzer, SCPsHaute
21(2)(j)Authentification MFAIAM MFA, SSO, CognitoCritique

3Mapping Cloud Détaillé

3.1 Service Control Policy (SCP) NIS2

SCP au niveau AWS Organizations pour imposer les guardrails NIS2 :

scp-nis2-baseline.jsonjson
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 :

terraform-nis2-baseline/main.tfhcl
1# terraform-nis2-baseline/main.tf
2# Module Terraform pour conformité NIS2 de base
3
4variable "organization_id" {
5 description = "AWS Organization ID"
6 type = string
7}
8
9variable "notification_email" {
10 description = "Email pour notifications incidents"
11 type = string
12}
13
14variable "csirt_endpoint" {
15 description = "Endpoint CSIRT/ANSSI pour notifications"
16 type = string
17 default = ""
18}
19
20# ============================================
21# Art. 21(2)(a) - Politiques de sécurité
22# ============================================
23
24resource "aws_securityhub_account" "main" {
25 enable_default_standards = true
26 auto_enable_controls = true
27}
28
29resource "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}
33
34resource "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}
38
39# ============================================
40# Art. 21(2)(b) - Gestion des incidents
41# ============================================
42
43resource "aws_guardduty_detector" "main" {
44 enable = true
45 finding_publishing_frequency = "FIFTEEN_MINUTES"
46
47 datasources {
48 s3_logs {
49 enable = true
50 }
51 kubernetes {
52 audit_logs {
53 enable = true
54 }
55 }
56 malware_protection {
57 scan_ec2_instance_with_findings {
58 ebs_volumes {
59 enable = true
60 }
61 }
62 }
63 }
64
65 tags = {
66 NIS2_Article = "21(2)(b)"
67 Compliance = "required"
68 }
69}
70
71resource "aws_cloudtrail" "nis2_audit" {
72 name = "nis2-audit-trail"
73 s3_bucket_name = aws_s3_bucket.cloudtrail.id
74 include_global_service_events = true
75 is_multi_region_trail = true
76 enable_log_file_validation = true
77
78 event_selector {
79 read_write_type = "All"
80 include_management_events = true
81
82 data_resource {
83 type = "AWS::S3::Object"
84 values = ["arn:aws:s3"]
85 }
86 }
87
88 tags = {
89 NIS2_Article = "21(2)(b)"
90 }
91}
92
93# SNS Topic for incident notifications
94resource "aws_sns_topic" "security_incidents" {
95 name = "nis2-security-incidents"
96
97 tags = {
98 NIS2_Article = "23"
99 }
100}
101
102resource "aws_sns_topic_subscription" "email" {
103 topic_arn = aws_sns_topic.security_incidents.arn
104 protocol = "email"
105 endpoint = var.notification_email
106}
107
108# EventBridge rule for GuardDuty findings
109resource "aws_cloudwatch_event_rule" "guardduty_findings" {
110 name = "nis2-guardduty-high-severity"
111 description = "NIS2 Art.23 - Alert on high severity findings"
112
113 event_pattern = jsonencode({
114 source = ["aws.guardduty"]
115 detail-type = ["GuardDuty Finding"]
116 detail = {
117 severity = [{ numeric = [">=", 7] }]
118 }
119 })
120
121 tags = {
122 NIS2_Article = "23"
123 }
124}
125
126resource "aws_cloudwatch_event_target" "sns" {
127 rule = aws_cloudwatch_event_rule.guardduty_findings.name
128 target_id = "SendToSNS"
129 arn = aws_sns_topic.security_incidents.arn
130}
131
132# ============================================
133# Art. 21(2)(c) - Continuité d'activité
134# ============================================
135
136resource "aws_backup_plan" "nis2_backup" {
137 name = "nis2-backup-plan"
138
139 rule {
140 rule_name = "daily-backup"
141 target_vault_name = aws_backup_vault.main.name
142 schedule = "cron(0 5 ? * * *)"
143
144 lifecycle {
145 delete_after = 90
146 }
147
148 copy_action {
149 destination_vault_arn = aws_backup_vault.dr.arn
150 lifecycle {
151 delete_after = 30
152 }
153 }
154 }
155
156 rule {
157 rule_name = "weekly-backup"
158 target_vault_name = aws_backup_vault.main.name
159 schedule = "cron(0 5 ? * SAT *)"
160
161 lifecycle {
162 delete_after = 365
163 }
164 }
165
166 tags = {
167 NIS2_Article = "21(2)(c)"
168 }
169}
170
171resource "aws_backup_vault" "main" {
172 name = "nis2-backup-vault"
173 kms_key_arn = aws_kms_key.backup.arn
174
175 tags = {
176 NIS2_Article = "21(2)(c)"
177 }
178}
179
180resource "aws_backup_vault" "dr" {
181 provider = aws.dr_region
182 name = "nis2-backup-vault-dr"
183 kms_key_arn = aws_kms_key.backup_dr.arn
184
185 tags = {
186 NIS2_Article = "21(2)(c)"
187 }
188}
189
190# ============================================
191# Art. 21(2)(h) - Cryptographie
192# ============================================
193
194resource "aws_kms_key" "main" {
195 description = "NIS2 main encryption key"
196 deletion_window_in_days = 30
197 enable_key_rotation = true
198 multi_region = true
199
200 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 })
214
215 tags = {
216 NIS2_Article = "21(2)(h)"
217 }
218}
219
220resource "aws_ebs_encryption_by_default" "enabled" {
221 enabled = true
222}
223
224resource "aws_s3_account_public_access_block" "main" {
225 block_public_acls = true
226 block_public_policy = true
227 ignore_public_acls = true
228 restrict_public_buckets = true
229}
230
231# ============================================
232# Art. 21(2)(i)(j) - Contrôle d'accès & MFA
233# ============================================
234
235resource "aws_iam_account_password_policy" "strict" {
236 minimum_password_length = 14
237 require_lowercase_characters = true
238 require_numbers = true
239 require_uppercase_characters = true
240 require_symbols = true
241 allow_users_to_change_password = true
242 max_password_age = 90
243 password_reuse_prevention = 24
244}
245
246resource "aws_accessanalyzer_analyzer" "main" {
247 analyzer_name = "nis2-access-analyzer"
248 type = "ACCOUNT"
249
250 tags = {
251 NIS2_Article = "21(2)(i)"
252 }
253}
254
255# ============================================
256# Art. 23 - Notification des incidents
257# ============================================
258
259resource "aws_lambda_function" "incident_notifier" {
260 filename = "incident_notifier.zip"
261 function_name = "nis2-incident-notifier"
262 role = aws_iam_role.lambda_incident.arn
263 handler = "index.handler"
264 runtime = "python3.11"
265 timeout = 30
266
267 environment {
268 variables = {
269 CSIRT_ENDPOINT = var.csirt_endpoint
270 SNS_TOPIC_ARN = aws_sns_topic.security_incidents.arn
271 NOTIFICATION_DELAY = "0" # Immediate for critical
272 }
273 }
274
275 tags = {
276 NIS2_Article = "23"
277 Purpose = "24h incident notification"
278 }
279}
280
281# Output compliance status
282output "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 :

nis2_compliance_checker.pypython
1#!/usr/bin/env python3
2"""
3NIS2 Compliance Checker for AWS
4Automated assessment against NIS2 Article 21 requirements
5"""
6
7import boto3
8import json
9from dataclasses import dataclass
10from typing import List, Dict, Optional
11from enum import Enum
12from datetime import datetime, timedelta
13
14class ComplianceStatus(Enum):
15 COMPLIANT = "compliant"
16 PARTIAL = "partial"
17 NON_COMPLIANT = "non_compliant"
18 NOT_APPLICABLE = "not_applicable"
19
20@dataclass
21class NIS2Finding:
22 article: str
23 requirement: str
24 status: ComplianceStatus
25 evidence: List[str]
26 remediation: Optional[str]
27 severity: str
28
29class NIS2ComplianceChecker:
30 """
31 Vérifie la conformité NIS2 Article 21 sur AWS
32 """
33
34 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] = []
44
45 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.findings
55
56 def check_art21_a_security_policies(self):
57 """Art. 21(2)(a): Politiques de sécurité des SI"""
58 evidence = []
59 status = ComplianceStatus.NON_COMPLIANT
60
61 # Check for SCPs in Organization
62 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.PARTIAL
68 except:
69 evidence.append("AWS Organizations non configuré")
70
71 # Check Security Hub enabled
72 try:
73 hubs = self.securityhub.describe_hub()
74 evidence.append("Security Hub activé")
75 if status == ComplianceStatus.PARTIAL:
76 status = ComplianceStatus.COMPLIANT
77 except:
78 evidence.append("Security Hub non activé")
79
80 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 ))
88
89 def check_art21_b_incident_handling(self):
90 """Art. 21(2)(b): Gestion des incidents"""
91 evidence = []
92 checks_passed = 0
93
94 # GuardDuty enabled
95 try:
96 detectors = self.guardduty.list_detectors()
97 if detectors['DetectorIds']:
98 evidence.append("GuardDuty activé")
99 checks_passed += 1
100 except:
101 evidence.append("GuardDuty non activé")
102
103 # CloudTrail enabled
104 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 += 1
110 except:
111 evidence.append("CloudTrail non configuré")
112
113 # EventBridge rules for security
114 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 += 1
120 except:
121 pass
122
123 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else
124 ComplianceStatus.PARTIAL if checks_passed >= 1 else
125 ComplianceStatus.NON_COMPLIANT)
126
127 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 ))
135
136 def check_art21_c_business_continuity(self):
137 """Art. 21(2)(c): Continuité d'activité et gestion de crise"""
138 evidence = []
139 checks_passed = 0
140
141 # AWS Backup plans
142 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 += 1
147 except:
148 evidence.append("Aucun plan AWS Backup")
149
150 # Multi-AZ RDS
151 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 += 1
158 except:
159 pass
160
161 # S3 Cross-Region Replication
162 try:
163 buckets = self.s3.list_buckets()
164 for bucket in buckets['Buckets'][:5]: # Sample
165 try:
166 repl = self.s3.get_bucket_replication(Bucket=bucket['Name'])
167 evidence.append(f"S3 CRR configuré sur {bucket['Name']}")
168 checks_passed += 1
169 break
170 except:
171 pass
172 except:
173 pass
174
175 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else
176 ComplianceStatus.PARTIAL if checks_passed >= 1 else
177 ComplianceStatus.NON_COMPLIANT)
178
179 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 ))
187
188 def check_art21_h_cryptography(self):
189 """Art. 21(2)(h): Politiques de cryptographie"""
190 evidence = []
191 checks_passed = 0
192
193 # KMS keys with rotation
194 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 += 1
201 except:
202 pass
203 evidence.append(f"{checks_passed} clés KMS avec rotation")
204 except:
205 evidence.append("Impossible de vérifier KMS")
206
207 # S3 default encryption
208 try:
209 buckets = self.s3.list_buckets()
210 encrypted = 0
211 for bucket in buckets['Buckets'][:10]:
212 try:
213 enc = self.s3.get_bucket_encryption(Bucket=bucket['Name'])
214 encrypted += 1
215 except:
216 pass
217 evidence.append(f"{encrypted}/10 buckets chiffrés par défaut")
218 if encrypted >= 8:
219 checks_passed += 1
220 except:
221 pass
222
223 # EBS default encryption
224 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 += 1
229 except:
230 evidence.append("EBS chiffrement par défaut non activé")
231
232 status = (ComplianceStatus.COMPLIANT if checks_passed >= 3 else
233 ComplianceStatus.PARTIAL if checks_passed >= 1 else
234 ComplianceStatus.NON_COMPLIANT)
235
236 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 ))
244
245 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 = 0
249
250 # Check for IAM Access Analyzer
251 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 += 1
257 except:
258 evidence.append("IAM Access Analyzer non configuré")
259
260 # Check password policy
261 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 += 1
267 except:
268 evidence.append("Politique mot de passe non définie")
269
270 # Check for unused credentials
271 try:
272 report = self.iam.generate_credential_report()
273 # In practice, would analyze the report
274 evidence.append("Rapport credentials généré")
275 except:
276 pass
277
278 status = (ComplianceStatus.COMPLIANT if checks_passed >= 2 else
279 ComplianceStatus.PARTIAL if checks_passed >= 1 else
280 ComplianceStatus.NON_COMPLIANT)
281
282 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 ))
290
291 def check_art21_j_authentication(self):
292 """Art. 21(2)(j): Authentification multi-facteur"""
293 evidence = []
294
295 # Check MFA on root
296 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 pass
304
305 # Check MFA on users
306 try:
307 users = self.iam.list_users()
308 mfa_users = 0
309 for user in users['Users']:
310 mfa = self.iam.list_mfa_devices(UserName=user['UserName'])
311 if mfa['MFADevices']:
312 mfa_users += 1
313 pct = (mfa_users / len(users['Users'])) * 100 if users['Users'] else 0
314 evidence.append(f"{mfa_users}/{len(users['Users'])} users avec MFA ({pct:.0f}%)")
315
316 status = (ComplianceStatus.COMPLIANT if pct >= 90 else
317 ComplianceStatus.PARTIAL if pct >= 50 else
318 ComplianceStatus.NON_COMPLIANT)
319 except:
320 status = ComplianceStatus.NON_COMPLIANT
321
322 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 ))
330
331 def check_art23_incident_notification(self):
332 """Art. 23: Notification des incidents (24h/72h)"""
333 evidence = []
334
335 # Check for SNS topics for incident notification
336 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() or
341 '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 pass
348
349 # Check EventBridge rules
350 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() or
355 'alert' in r['Name'].lower()]
356 evidence.append(f"{len(incident_rules)} règles EventBridge alertes")
357 except:
358 pass
359
360 status = ComplianceStatus.PARTIAL if evidence else ComplianceStatus.NON_COMPLIANT
361
362 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 ))
370
371 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)
378
379 # Summary
380 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)
383
384 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}%")
386
387 # Details
388 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}")
396
397 return "\n".join(report)
398
399
400def main():
401 checker = NIS2ComplianceChecker()
402 findings = checker.run_full_assessment()
403 report = checker.generate_report()
404 print(report)
405
406
407if __name__ == '__main__':
408 main()

5.2 Queries Athena pour Monitoring Continu

Requêtes SQL pour monitorer la conformité en continu via CloudTrail :

nis2-monitoring-queries.sqlsql
1-- NIS2 Compliance Monitoring Queries
2-- Run against CloudTrail logs in Athena
3
4-- Query 1: Art.21(2)(b) - Incident Detection Timeline
5-- Track security incidents for 24h notification requirement
6SELECT
7 eventTime,
8 eventSource,
9 eventName,
10 userIdentity.arn AS actor,
11 sourceIPAddress,
12 errorCode,
13 CASE
14 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_ago
20FROM cloudtrail_logs
21WHERE eventTime > date_add('day', -7, current_timestamp)
22 AND (
23 errorCode IS NOT NULL
24 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;
36
37-- Query 2: Art.21(2)(h) - Cryptography Compliance
38-- Check encryption status of resources
39SELECT
40 '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_encrypted
44FROM (
45 SELECT
46 bucket_name,
47 CASE WHEN server_side_encryption IS NOT NULL THEN true ELSE false END AS encryption_enabled
48 FROM s3_inventory
49)
50UNION ALL
51SELECT
52 '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_encrypted
56FROM ebs_volumes
57UNION ALL
58SELECT
59 '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_encrypted
63FROM rds_instances;
64
65-- Query 3: Art.21(2)(j) - MFA Compliance
66-- Check MFA adoption across IAM users
67SELECT
68 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 CASE
74 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_status
78FROM iam_users u
79LEFT JOIN iam_mfa_devices m ON u.user_name = m.user_name
80ORDER BY mfa_status, days_since_login DESC;
81
82-- Query 4: Art.21(2)(i) - Access Review
83-- Identify over-privileged and unused credentials
84SELECT
85 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 CASE
92 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_status
96FROM iam_access_keys
97WHERE status = 'Active'
98ORDER BY days_unused DESC;
99
100-- Query 5: Art.23 - Incident Response Timeline
101-- Generate incident timeline for CSIRT/ANSSI reporting
102WITH incidents AS (
103 SELECT
104 eventTime AS detection_time,
105 eventSource,
106 eventName,
107 userIdentity.arn AS actor,
108 sourceIPAddress,
109 awsRegion,
110 requestParameters,
111 responseElements
112 FROM cloudtrail_logs
113 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)
122SELECT
123 detection_time,
124 DATE_DIFF('hour', detection_time, current_timestamp) AS hours_since_detection,
125 CASE
126 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 sourceIPAddress
133FROM incidents
134ORDER BY detection_time DESC;

Checklist Conformité NIS2

Security Hub activé avec CIS + AWS Foundational
GuardDuty activé sur tous les comptes
CloudTrail multi-région avec validation
AWS Backup avec rétention 90j + cross-region
KMS avec rotation automatique activée
EBS/S3/RDS chiffrement par défaut
MFA obligatoire pour tous les utilisateurs
IAM Access Analyzer configuré
SCPs NIS2 déployées sur l'Organization
Alertes incidents vers CSIRT configurées

Références

[1]
European Parliament. "Directive (EU) 2022/2555 (NIS2)". Official Journal of the EU, 2022. Link ↗
[2]
ANSSI. "NIS2 - Anticipez la transposition". ANSSI.gouv.fr, 2024. Link ↗
[3]
ENISA. "NIS2 Directive Guidance". ENISA, 2024. Link ↗
[4]
AWS. "Compliance Programs - NIS". AWS Compliance, 2024. Link ↗
[5]
European Commission. "NIS2 Factsheet". Digital Strategy, 2023. Link ↗

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.

CRL

Cyvex Research Lab

Security Research Team @ Cyvex

Équipe spécialisée en conformité réglementaire cloud. Expertise NIS2, DORA, et frameworks de sécurité européens. Collaboration active avec l'ANSSI et les autorités nationales de cybersécurité.

Partager cet article