Skip to content

Generate a NIS2 compliance report

GET /compliance/nis2-report.pdf renders a downloadable PDF combining CVE exposure posture, SLA compliance, and recent security decisions — intended to be shared with auditors, management, or a compliance officer. This guide covers generating it, scoping it to a period and/or a subset of machines, and what the [À VALIDER: …] placeholder blocks mean.

Two other formats of the same underlying data exist alongside the PDF: GET /compliance/nis2-posture (JSON metrics only) and GET /compliance/nis2-report.json (a fuller JSON audit package). This guide covers the PDF, since it's the one meant to leave Repod as a standalone document.


1. Prerequisites

  • Any authenticated role (GET /compliance/nis2-report.pdf uses get_current_user, not an elevated role) — but the caller's own machine access restrictions still apply: a scoped user's report is limited to machines they can see, never widened by the query parameters below.
  • The nis2_report license feature must be entitled.
  • WeasyPrint renders the PDF server-side — no client-side dependency, just a plain curl/browser download.

2. Generate the report (default: whole fleet, live snapshot)

curl -s http://localhost:8000/api/v1/compliance/nis2-report.pdf \
  -H "Authorization: Bearer $TOKEN" \
  -o nis2_compliance_report.pdf

With no query parameters, the report covers every enabled machine the caller has access to, and the security-decisions history section covers all recorded decisions with no date filter.


3. Scope by period

period_start / period_end (ISO 8601, e.g. 2026-01-01) filter only the security-decisions history section (accept/reject/exception/upgrade decisions recorded in that window).

curl -s "http://localhost:8000/api/v1/compliance/nis2-report.pdf?period_start=2026-01-01&period_end=2026-06-30" \
  -H "Authorization: Bearer $TOKEN" \
  -o nis2_compliance_report_h1_2026.pdf

period_end without period_start is rejected (400). Omitting period_end with period_start set defaults the end to now.

The CVE posture is always a live snapshot — not retroactive

Unlike the decisions-history section, the CVE exposure/SLA posture (unpatched CVE counts, mean days exposed, SLA breaches) is always computed at generation time, never reconstructed for a past period. There is no time-series store for this data — inventory_cve is a snapshot replaced on every scan. A report generated today with period_start=2026-01-01 shows today's posture alongside January's decisions; it is not a picture of what the posture looked like in January. The report states this explicitly rather than implying a retroactive reconstruction that doesn't exist.


4. Scope by machine (client IDs and/or tags)

client_ids and tags restrict which machines the asset-scoped sections cover — the union of both, same semantics as POST /install/jobs tag targeting.

curl -s "http://localhost:8000/api/v1/compliance/nis2-report.pdf?tags=rhel9-prod,pci-scope" \
  -H "Authorization: Bearer $TOKEN" \
  -o nis2_compliance_report_pci-scope.pdf
curl -s "http://localhost:8000/api/v1/compliance/nis2-report.pdf?client_ids=3f2b1a90-...,7c8d9e10-..." \
  -H "Authorization: Bearer $TOKEN" \
  -o nis2_compliance_report_two-machines.pdf

If client_ids and/or tags are given but resolve to zero machines, the endpoint returns 422 rather than silently generating a near-empty report. For a user whose own machine access is already restricted, the requested scope is intersected with what they can see (never widened) — if that intersection is empty, it is also 422, not a report scoped to nothing.

Combine period and machine scoping freely:

curl -s "http://localhost:8000/api/v1/compliance/nis2-report.pdf?period_start=2026-01-01&tags=rhel9-prod" \
  -H "Authorization: Bearer $TOKEN" \
  -o nis2_compliance_report_rhel9-prod_2026.pdf

5. Reading the [À VALIDER: …] placeholder blocks

The report template (backend/templates/reports/nis2_report.html.j2) is version-controlled, not editable from the UI — a compliance document needs GRC sign-off before any content change, unlike Repod's other, UI-customizable email templates.

Some sections of a NIS2 Article 21 report require regulatory judgment that cannot be derived from application data alone — organizational measures, incident-response procedures, supplier risk assessments, and similar narrative content Repod has no visibility into. Rather than invent plausible legal text, those sections render as an explicit placeholder:

[À VALIDER: <description of what this section needs>]

What this means for you: any section carrying this marker is not finished — it is a labeled gap for your compliance/GRC team to fill in before the document is used as an official audit artifact. Do not remove the marker by writing something in its place casually; do not send the PDF to an external auditor with unresolved [À VALIDER: …] blocks still present, since they signal "not yet reviewed," not "not applicable."

Everything else in the report (CVE posture metrics, SLA compliance figures, the decisions history) is derived directly and factually from Repod's own data — no interpretation is layered on top of it.


Verify it worked

  1. The download completed and the PDF opens without error.
  2. The header/cover section shows the correct generation timestamp and, if you scoped it, the correct asset scope (tags/client count) and decision period.
  3. The CVE posture figures match what GET /compliance/nis2-posture returns for the same scope right now:
    curl -s "http://localhost:8000/api/v1/compliance/nis2-posture" \
      -H "Authorization: Bearer $TOKEN" | jq .
    
  4. If you set period_start/period_end, confirm the decisions-history section only lists decisions inside that window — and confirm you did not interpret the CVE posture section as being from that period too.
  5. Search the rendered PDF text for À VALIDER — note every section still flagged, and route the document to your compliance/GRC reviewer before treating it as a finished, shareable audit artifact.