TL;DR The Cyberbeveiligingswet has applied since 15 August 2026. Below, the ten measures of article 21 mapped onto controls that actually run, a Kyverno policy that proves one of them, the reporting deadlines as three Prometheus alerting rules, and the four duties no YAML covers. The first run of that policy found seven pods in my own cluster I’d have sworn were mirrored.
The Cyberbeveiligingswet has applied in the Netherlands since 15 August 2026. It’s the Dutch implementation of NIS2 , roughly 22 months after the directive said it should be, and it’s why the question at work stopped being hypothetical. Nobody asked me whether the cluster was secure. The question was narrower and a lot harder. Which measure in article 21 does this control satisfy, and where do we have nothing at all?
Almost every NIS2 text I could find was written by people who’ve never typed kubectl. Almost every Kubernetes security text reads as if there’s no such thing as a law. This post sits between the two. It maps the ten measures of the duty of care onto controls that run. Then a policy that blocks, and the reporting deadlines as alerting rules that fire while there’s still time to do something about them.
I’m an engineer, not a lawyer. Every legal claim below traces to the Cyberbeveiligingswet, the Cyberbeveiligingsbesluit or Directive (EU) 2022/2555, all read on 2026-09-20. Articles get renumbered and ministers publish sector rules. Check the source before you bet a fine on my summary.
The five-minute scope test
Skip the consultancy flowcharts. Three questions settle it for most organisations.
Are you a public body? Cbw article 8(1)(h) makes provinces, municipalities, water authorities and qualifying joint arrangements, the gemeenschappelijke regelingen, essential entities by operation of law. No size test, no risk assessment. A village of 9,000 people sits in the same tier as a national telco. That’s a Dutch choice, and it’s the one that catches people out. NIS2 article 2(5)(a) leaves local government optional. The Netherlands took the option and then went further. Ministries and central government zbo’s sit one point higher, in 8(1)(g).
Are you big enough? For everyone else the act points at Recommendation 2003/361/EC . Under 50 staff and under EUR 10 million on both turnover and balance sheet, you’re out, unless one of the size-independent categories catches you. Medium-sized, 50 to 249 staff or past that EUR 10 million, gets you important-entity status from either annex. Past the medium ceiling, 250 staff or EUR 50 million turnover with a balance sheet total over EUR 43 million, an annex 1 entity becomes essential, while annex 2 stays important however large you get.
Are you size-independent? Trust service providers, TLD name registries, DNS service providers and providers of public electronic communications networks and services are in regardless of headcount. So are critical entities designated under the Wwke .
Some organisations are carved out entirely. Defence, the intelligence and security services, the Public Prosecution Service, the police and the safety regions, plus the judiciary, both chambers of parliament and De Nederlandsche Bank. They keep a right to NCSC assistance under article 96.
The tier matters more than it looks. RDI supervises essential entities proactively, which means they can ask for information or turn up. For important entities, supervision mostly happens after an incident, and Cbw article 81 limits the enforcement instruments to cases where the authority already has evidence or indications of a breach. So if you’re essential, the supervisor doesn’t need an incident to ask for your policies. They can just ask.
The ten measures, mapped
Article 21(3) lists ten measures. Cbb articles 6 to 18 turn each one into a concrete duty, so the decree is what you read when you want to know what “incident handling” means on paper. Here’s how they land on a cluster.
| Measure | Cbb | What runs in my cluster | Where the evidence lives |
|---|---|---|---|
| a. risk analysis and security policy | 6 , 7 | The policy set in Git, with a risk register that names the systems it covers | Repo history, ADRs |
| b. incident handling, including logging | 8 | Falco for runtime detection, Kubernetes audit logs in Loki, Alertmanager routing | Loki retention, alert history |
| c. business continuity and crisis management | 9 | Scheduled Velero backup jobs, plus restore drills into a scratch cluster with a written outcome for each run | The drill reports, with a date, a duration and what failed |
| d. supply chain security | 10 | A registry I run, mirroring what we depend on, with a Kyverno allowlist in front of it, image signing and an SBOM per release | Policy reports, cosign verify output, the mirror itself |
| e. secure acquisition, development, maintenance | 11 | Trivy in CI, a patch window that exists, vulnerability disclosure contact in security.txt | Pipeline artefacts |
| f. assessing measure effectiveness | 18 | Scheduled evaluation with a written record. See the bottom of this post, there is a trap here | The written record itself |
| g. cyber hygiene and training | 12 | Mostly not technical. MFA rollout and onboarding are the parts that are | Training records, IdP config |
| h. cryptography policy | 13 | cert-manager for TLS, a written statement of which algorithms and key lengths are allowed | The policy document, plus TLS scan output |
| i. personnel, access control, asset management | 14 , 15 , 16 | RBAC without cluster-admin for humans, SSO, and an inventory that is generated rather than maintained | RoleBindings in Git, inventory export |
| j. MFA and secure communication | none | IdP enforcement, WebAuthn where possible | IdP policy |
Two things stood out to me. Cbb article 17 adds a duty nothing in article 21(3) hints at. You have to act on targeted alerts, security advisories and threat intelligence. If you already read NCSC advisories and patch on them, you satisfy it, but only if you can show what you did. And Cbb article 4 pulls the digital entity types out of articles 6 to 18 entirely, because Implementing Regulation (EU) 2024/2690 already covers them. Cloud providers, data centres, CDNs, managed service and managed security providers read that regulation instead.
If you’re in the government sector, the regulation goes further and names the framework: NEN-EN-ISO/IEC 27001:2023 for the management system, and the controls of NEN-EN-ISO/IEC 27002:2022 minus 5.32 and 5.34, plus the relevant BIO2 measures. Equivalent frameworks are allowed if they reach the same level.
The smallest policy that proves a measure
Measure d is where a cluster can do something a policy document can’t. Supply chain security starts with knowing where every image comes from, and an admission controller turns that from an intention into a property of the cluster.
I use Kyverno for this, because the policy is a Kubernetes resource and lives in Git next to everything else. The smallest useful version is a registry allowlist:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: registry-allowlist
annotations:
policies.kyverno.io/description: >-
Cbw art. 21(3)(d), supply chain security. Also the readiness condition for
art. 21a: a supplier ban is only survivable if every image origin is known.
spec:
background: true
rules:
- name: only-approved-registries
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: >-
Images come from registry.example.nl only (Cbw art. 21(3)(d)).
pattern:
spec:
=(ephemeralContainers):
- image: "registry.example.nl/*"
=(initContainers):
- image: "registry.example.nl/*"
containers:
- image: "registry.example.nl/*"
Kyverno 1.13 moved failureAction onto the rule. On 1.12 and older it’s spec.validationFailureAction, which is what the older policies in my repo still use.
The command that proves it blocks:
$ kubectl run test --image=nginx:1.27
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/test was blocked due to the following policies
registry-allowlist:
only-approved-registries: 'validation error: Images come from registry.example.nl
only (Cbw art. 21(3)(d)). rule only-approved-registries failed at path
/spec/containers/0/image/'
That output is the artefact. A supervisor asking how I control the supply chain gets that policy and that denial, with a timestamp on it. I’d rather hand over that than a paragraph in a Word file.
Run it in Audit first. background: true makes Kyverno scan what’s already running, and kubectl get policyreport -A then tells you how much of your cluster would’ve been refused. On my own cluster the first run turned up seven pods pulling from Docker Hub. I’d have sworn every one of them was mirrored.
One thing the policy assumes and doesn’t provide. registry.example.nl has to exist. An allowlist naming a registry nobody runs blocks every workload and calls it compliance, so measure d
starts with the artifact repository. The admission rule comes second.
Mine is zot, an OCI-native registry that ships as a single binary and syncs upstream images on a schedule. Two reasons it fits a post about the duty of care. Its CVE search is Trivy underneath, so measure e gets answered by the same component that stores the artefact, instead of a second tool with its own database to keep current. And a single binary with no database beside it is a supply chain I can read end to end, which is the whole reason I run it. Holding the bits yourself buys you three more things. An image keeps existing after an upstream tag gets retagged or deleted, which is the kind of failure that takes a cluster down on a Sunday. The replacement period in article 21a becomes something you can actually act on, because swapping a supplier means rebuilding from artefacts already on your own disk. And Cbb article 10 , which wants a supply chain policy and vetting of direct suppliers, gets a boundary it can point at, since the list of direct suppliers is the list of upstreams the mirror pulls from.
That same policy is your answer to Cbw article 21a , which has no counterpart in the directive at all. It lets the responsible minister order an entity to stop using a named party’s products in named parts of its systems, with a replacement period set in the interest of continuity. Will that power ever get used? I have no idea. The readiness question underneath it is the useful part. If a supplier were named tomorrow, could you list every place their code runs? With an allowlist and an image inventory, that’s minutes. With a spreadsheet, weeks.
The reporting clock, as alerting rules
Every other write-up states these deadlines as prose, and nothing in your monitoring reads prose. The deadlines first, then the rules.
| Step | Deadline | Measured from |
|---|---|---|
| Vroegtijdige waarschuwing (early warning) | 24 hours | Becoming aware of the significant incident |
| Melding (notification) | 72 hours | Becoming aware of the significant incident |
| Tussentijds verslag | on request | A request from the CSIRT or authority |
| Eindverslag (final report) | one month | The 72-hour notification, not the early warning |
| Voortgangsverslag | when the final report was due | The incident still running at that moment |
Two traps in that table. The final report clock runs from the notification, so a slow 72-hour filing moves your month. And the Dutch text doesn’t say “within 24 hours”. It says “onverwijld of, indien dat niet mogelijk is, binnen 24 uur”, so immediately, and only if that’s impossible do you get the 24 hours. The outer limit matches the directive, and the duty to move immediately sits on top of it.
An incident is significant when it causes or can cause a serious operational disruption or financial loss, or when it has affected or can affect other entities through considerable damage. Sector ministers can add criteria, and a decision addressed to one specific entity is confidential inside that entity, so look up your own sector and skip the generic lists.
Every deadline in that table is a duration measured from one moment, the moment you became aware. Nothing in your infrastructure knows when that was, so nothing can tell you the 24 hours are nearly up. The fix is to turn that moment into a number the monitoring can read. Declaring an incident pushes one gauge to Pushgateway, holding the Unix timestamp of the declaration:
cat <<'EOF' | curl --data-binary @- \
https://pushgateway.example.nl/metrics/job/nis2/incident/INC-2026-014
# TYPE nis2_incident_declared_timestamp_seconds gauge
nis2_incident_declared_timestamp_seconds 1758394800
EOF
Pushgateway and not a scrape, because there’s nothing to scrape. A declaration happens once, at one moment, and a scrape target has to still be there when Prometheus comes looking. No process sits in a corner repeating “we’re in an incident”, and writing one would be a worse job than this curl. Pushgateway holds the value until something overwrites it or deletes it.
The grouping key in that URL, incident/INC-2026-014, arrives as a label on the metric. That label is what later lets a rule line up the declaration of one incident with the reports filed for that same incident, which is how an alert knows it’s been answered.
From there the alerting is arithmetic on one number. time() is now, the gauge is then, and the difference is how long this incident has been running. Each rule fires ahead of its own deadline:
groups:
- name: nis2-reporting
rules:
- alert: NIS2EarlyWarningDue
expr: |
(time() - nis2_incident_declared_timestamp_seconds > 20 * 3600)
unless on (incident)
nis2_report_filed_timestamp_seconds{step="early-warning"}
labels:
severity: page
runbook: nis2-reporting
annotations:
summary: >-
Early warning for {{ $labels.incident }} is due within 4 hours
(Cbw art. 26(1)). It goes to the CSIRT and the supervisor.
- alert: NIS2NotificationDue
expr: |
(time() - nis2_incident_declared_timestamp_seconds > 68 * 3600)
unless on (incident)
nis2_report_filed_timestamp_seconds{step="notification"}
labels:
severity: page
runbook: nis2-reporting
- alert: NIS2FinalReportDue
expr: |
(time() - nis2_report_filed_timestamp_seconds{step="notification"} > 26 * 86400)
unless on (incident)
nis2_report_filed_timestamp_seconds{step="final"}
labels:
severity: ticket
runbook: nis2-reporting
Four hours of margin on a 24-hour duty, four on 72, four days on the month. Filing a report clears its own alert, because the gauge for that step appears and the unless stops matching. If you want the reasoning behind alerting on a deadline and not on a metric, it’s the same argument I made in alerting that works. An alert exists to make a human do one specific thing, and this one names the thing in its summary.
One dependency I want to state before the runbook, because it decides whether any of this works. A deadline alert is only worth writing if a page still moves someone. In an organisation where alert fatigue has set in, where everyone’s learned that most of what fires is noise to silence, NIS2EarlyWarningDue joins the queue nobody opens. You’ve then built a legal obligation on top of a channel that stopped working, and you won’t see that until the night it costs you the deadline.
If that describes where you work, the alerting is the first job and these three rules are the second. Cut the volume until everything that fires means somebody has to do something now. That’s the whole argument of alerting that works, and here it’s a precondition.
The runbook, because one incident makes two notifications
NIS2 lets a member state route reports to the CSIRT or the competent authority. Cbw articles 26(1) and 27(1) say CSIRT and competent authority. One submission on MijnNCSC covers both in practice because it gets forwarded, but the legal duty runs to two parties, and which two depends on your sector. A municipality reports to NCSC and RDI. A water authority reports to CERT-WM , housed at Het Waterschapshuis, and is supervised by ILT . Healthcare reports to Z-CERT and is supervised by IGJ . Find your own row now, before you need it.
The early warning also asks for more than the directive does. NIS2 wants two facts, whether the incident is suspected to be malicious and whether it may have cross-border consequences. Cbb article 24 adds four more, each qualified by “where possible”. The suspected start time, a description of the nature and the consequences noticeable so far, an estimated recovery time, and the measures taken or planned. Assembling seven data points inside 24 hours while the thing is still on fire is a different job from assembling two, and that’s why this lives in a runbook.
# Runbook: nis2-reporting
## t+0 Declare
- Assign INC-YYYY-NNN. Push the declared gauge (see alert annotations).
- Decide significance: serious operational disruption, financial loss, or
considerable damage to other entities. Record the reasoning as well as the verdict.
## t+0 to t+24h Early warning
Seven fields, three from Cbw art. 26(2) and four from Cbb art. 24:
1. Suspected to be unlawful or malicious? (yes / no / unknown)
2. Possible cross-border consequences? (yes / no / unknown)
3. Contact details of the responsible reporter
4. Suspected start time of the incident
5. Nature and consequences noticeable so far
6. Estimated recovery time
7. Measures taken or intended
Submit on MijnNCSC. Push the early-warning gauge.
## t+0 to t+72h Notification
- Update the early warning where it has changed
- Initial assessment: severity and impact
- Indicators of compromise, where available
- Whatever lets the CSIRT judge cross-border impact
Push the notification gauge. The one-month clock starts here.
## Without delay Service recipients
Inform them if the incident may adversely affect the service, and inform them of a
significant cyber threat where they may be affected (Cbw art. 30).
## t+1 month Final report
Detailed description, severity, consequences, probable root cause, mitigations
applied and ongoing, cross-border consequences.
Still running at that moment? File a progress report instead, then the final report
within one month of the incident being handled.
Your evidence is a retention setting
Cbb article 8 folds logging into incident handling. That sounds mild until you’re writing the final report a month later and find your audit logs rolled over.
The Kubernetes audit policy I run keeps the expensive level only where it answers a question I’ll actually get asked:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "serviceaccounts/token"]
- group: "rbac.authorization.k8s.io"
resources: ["*"]
- level: Metadata
omitStages: ["RequestReceived"]
Who read a secret, and who changed an authorisation, at full fidelity. Everything else at metadata level. Ship it to Loki, and set the retention past the reporting horizon. Your final report can land a month after the notification, a progress report can push it further, and a supervisor can ask afterwards, so 30 days of retention runs out before the final report is even due. I keep 400 days for the audit stream and 30 for application logs.
The measures with no YAML behind them
Not everything maps to a control, and pretending otherwise is where compliance theatre starts. These four don’t.
The board training certificate. Cbw article 24 goes well past NIS2 article 20 . Every board member needs the knowledge to identify risks, assess measures and judge their consequences, has to keep it demonstrably current, and has to hold a certificate naming them, the training dates, the subjects and the provider, in Dutch or English. Sitting members have until 15 August 2028, two years from entry into force, and a member appointed later gets two years from appointment. A board member who doesn’t comply can be fined EUR 25,000 personally under article 93 . Nothing in the directive asks for a certificate, a deadline or a personal fine. I read that article twice to be sure. For public bodies “the board” means the college van burgemeester en wethouders, gedeputeerde staten, or the dagelijks bestuur.
Registration, which is closer to your job than it looks. Cbw article 44 wants your name, addresses, contact details, IP ranges, sectors, the member states you serve, and your domain names, with changes reported within two weeks. Those are infrastructure facts. A merge request that adds a prefix to your IPAM or a domain to your DNS zone is a registration event. Treat it as one and the two-week duty is something you meet by accident. Don’t, and it’s something you find out you’ve been breaching for a year. Registration has been mandatory since 15 August 2026 and there’s no grace period.
Periodic evaluation, and the gap underneath it. NIS2 article 21(4) requires an entity that finds it doesn’t comply to take corrective measures without undue delay. The Dutch article 21 has no such paragraph. The nearest thing is Cbb article 18 , a periodic evaluation of effectiveness, written down, with adjustment where needed. That’s weaker, and the gap is yours to fill, because a drift you find on Tuesday and fix at the next quarterly review is legally defensible and operationally embarrassing. This is the one place I’d over-implement the law rather than meet it. If your infrastructure is declarative, the evaluation can lean on what the repo already proves, which is the argument in declarative infrastructure as compliance evidence.
Supplier vetting. Cbb article 10 wants a supply chain policy and vetting of direct suppliers. The registry allowlist proves what runs. It says nothing about whether the vendor behind it patches.
What the law actually asks of you
Reading the Cyberbeveiligingswet next to the directive was more interesting than I expected, because the Dutch additions aren’t bureaucratic noise. They point at the same thing I keep landing on from the other direction. Every municipality is essential regardless of size, because the damage when it fails doesn’t scale with its headcount. Board members carry a personal fine, because a risk nobody in the room understands is a risk nobody manages. And a minister can ban a named supplier from named systems, which is a state saying out loud that depending on a vendor you can’t inspect is a national risk.
That last one is the argument I’ve been making about my own infrastructure for years, and now it’s arriving by law. If you can’t list what you run and where it came from, a supplier ban turns into an archaeology project. I run things I understand for my own reasons. An audit is just the first time somebody else asks me to prove it. The longer version of that is in sovereign infrastructure.
Start with the mapping table. Fill the rows you can this week, and write down which ones stay empty, because a gap you’ve named is a register entry and a gap you haven’t is what the supervisor finds. Mine had row d empty until that first Audit run counted seven pods from Docker Hub, and seven is a number I could write down.
