Technische Documentatie

Architectuur, dataflow, databaseschema, n8n workflows en beveiligingsoverwegingen voor beheerders en self-hosters.

Architectuurdiagram

Security Informer bestaat uit vijf primaire lagen: externe bronnen, workflow-orchestratie, kernopslag, management interfaces en notificatiekanalen.

  ╔═══════════════════════════════════════════════════════════════════════╗
  ║                    SECURITY INFORMER STACK                           ║
  ╚═══════════════════════════════════════════════════════════════════════╝

  ┌─────────────────────── EXTERNE BRONNEN ───────────────────────────┐
  │  VulnCheck NVD++ │ CISA KEV │ EPSS │ ENISA EUVD │ NVD NIST        │
  │  RSS: NCSC-NL, BleepingComputer, Krebs, THN, Dark Reading         │
  └───────────────────────────┬───────────────────────────────────────┘
                              │ HTTPS/REST/RSS
                    ┌─────────▼──────────┐
                    │      n8n           │  uw eigen n8n instantie (extern)
                    │  Workflow Engine   │  7 workflows, cron-gestuurd
                    └────────┬───────────┘
                             │
          ┌──────────────────┼──────────────────────┐
          │                  │                      │
   ┌──────▼──────┐    ┌──────▼──────┐       ┌──────▼──────┐
   │ PostgreSQL  │    │  Claude AI  │       │  Telegram   │
   │  (central)  │◄──►│  Anthropic  │       │    Bot      │
   └──────┬──────┘    └─────────────┘       └─────────────┘
          │
    ┌─────┴──────────────────────────┐
    │                                │
┌───▼──────┐  ┌─────────┐  ┌────────▼────────┐
│  NocoDB  │  │ Grafana │  │  Admin Portaal  │
│  (assets)│  │ (stats) │  │  (Flask/Python) │
└──────────┘  └─────────┘  └─────────────────┘
    │              │                │
    │         uw eigen         uw eigen
  uw eigen    Grafana-         Admin-
  NocoDB-     domein           domein
  domein
          │
     ┌────▼─────────────────────────────────────────┐
     │               TRAEFIK                        │
     │  TLS-terminatie, routing, Let's Encrypt      │
     └──────────────────────────────────────────────┘
          │
     Internet (HTTPS only)

Dataflow: van CVE naar notificatie

Stap-voor-stap hoe een nieuwe CVE het systeem doorloopt van bronpublicatie tot Telegram-alert.

1

CVE publicatie bij bron

Een CVE wordt gepubliceerd in VulnCheck NVD++ (of CISA KEV, EPSS, ENISA EUVD). Bij CISA KEV wordt de CVE ook gemarkeerd als Known Exploited.

2

n8n fetch-workflow

De CVE fetch-workflow (SI - CVE Feeds) draait elke 2 uur en combineert alle vier CVE-bronnen in één workflow: VulnCheck NVD++, CISA KEV, EPSS en ENISA EUVD. n8n haalt nieuwe CVE's op via HTTP en filtert op publicatiedatum.

3

Opslag in PostgreSQL

Elke CVE wordt opgeslagen in de tabel cve_raw met alle metadata: CVE-ID, beschrijving, CVSS-score, publicatiedatum, KEV-status en EPSS-score. Duplicaten worden genegeerd via ON CONFLICT DO NOTHING.

4

AI matching-workflow

Elk uur verwerkt de SI - AI Analyse workflow een batch van 50 CVE's die nog niet gematcht zijn (ai_analysed = false). Het stuurt CVE + asset database naar Claude AI voor relevantieanalyse.

5

Relevantiescore berekening

Op basis van de Claude AI-respons en de objectieve CVE-metadata wordt de relevantiescore berekend en opgeslagen in cve_matches. Asset-suggesties gaan naar asset_suggestions.

6

Notificatieregel evaluatie

De notificatieworkflow controleert elke 15 minuten of er matches zijn die voldoen aan een actieve notificatieregel (tijdvenster, minimum scores, KEV-status). Dit wordt gedaan via een PostgreSQL CTE-query die tijdvensters evalueert.

7

Telegram alert / e-mail rapport

Relevante matches worden als Telegram-bericht verstuurd met: CVE-ID, beschrijving, CVSS, relevantiescore, betrokken assets en aanbevolen actie. Voor e-mail worden matches gebundeld in een gestructureerd rapport.

Databaseschema

PostgreSQL 16 database. Alle tabellen met primaire kolombeschrijvingen.

Kerntabellen

Tabel: cve_raw
KolomTypeBeschrijving
idSERIAL PKInterne identificatie
cve_idVARCHAR(20) UNIQUECVE-identifier (bijv. CVE-2024-12345)
descriptionTEXTEngelse beschrijving van de kwetsbaarheid
cvss_v3_scoreNUMERIC(3,1)CVSS v3 base score (0.0 – 10.0)
cvss_v3_vectorTEXTCVSS v3 vector string
epss_scoreNUMERIC(5,4)EPSS score (0.0000 – 1.0000)
kev_statusBOOLEANTrue als CVE in CISA KEV staat
published_atTIMESTAMPTZPublicatiedatum CVE
sourceVARCHAR(20)Primaire bron: vulncheck, cisa_kev, enisa, nvd
ai_analysedBOOLEANFalse = nog niet verwerkt door AI
affected_cpesTEXTCPE-strings als tekst (geen JSONB)
affected_productsTEXTGetroffen producten in leesbare vorm
created_atTIMESTAMPTZOpslaagtijdstip in dit systeem
Tabel: assets
KolomTypeBeschrijving
idSERIAL PKInterne identificatie
vendorVARCHAR(100)Fabrikant of leverancier
productVARCHAR(200)Productnaam
versionVARCHAR(50)Versie of versierange
cpeTEXTCPE 2.3 identifier voor exacte matching
criticalityENUMlow / medium / high / critical
internet_facingBOOLEANPubliek bereikbaar?
subsidiary_idINT FKKoppeling aan subsidiaries.id
notesTEXTVrij tekstveld voor context
activeBOOLEANFalse = uitgefaseerd, niet meer matchen
created_atTIMESTAMPTZAanmaaktijdstip
Tabel: cve_matches
KolomTypeBeschrijving
idSERIAL PKInterne identificatie
cve_idINT FKKoppeling aan cve_raw.id
asset_idINT FKKoppeling aan assets.id
relevance_scoreNUMERIC(3,1)Berekende relevantiescore (1.0 – 10.0)
ai_reasoningTEXTClaude AI motivatie voor de match
match_typeENUMdirect / indirect / advisory
priorityENUMcritical / high / medium / low
telegram_sentBOOLEANTrue = Telegram-alert al verstuurd
email_sentBOOLEANTrue = opgenomen in e-mailrapport
created_atTIMESTAMPTZTijdstip van match
Tabel: notification_rules
KolomTypeBeschrijving
idSERIAL PKInterne identificatie
nameVARCHAR(100)Beschrijvende naam van de regel
activeBOOLEANFalse = tijdelijk uitgeschakeld
days_of_weekINTEGER[]Array van ISO weekdagnummers (1=ma, 7=zo)
time_startTIMEBegin tijdvenster (lokale tijd)
time_endTIMEEinde tijdvenster
channelVARCHAR(20)telegram of email
min_priority_scoreNUMERIC(3,1)Minimale priority score (1–10)
min_relevance_scoreNUMERIC(3,1)Minimale relevance score (1–10)
kev_always_sendBOOLEANKEV-alerts altijd sturen
Tabel: component_health
KolomTypeBeschrijving
idSERIAL PKInterne identificatie
component_nameVARCHAR(50) UNIQUEComponentnaam (bijv. vulncheck, cisa_kev, telegram)
statusVARCHAR(20)healthy / degraded / error / unknown
last_activity_atTIMESTAMPTZLaatste succesvolle activiteit
last_tested_atTIMESTAMPTZLaatste health check tijdstip
last_test_resultVARCHAR(20)passed / failed / skipped
last_errorTEXTLaatste foutmelding
consecutive_failuresINTEGERAantal opeenvolgende mislukte checks

n8n Workflows

Security Informer bestaat uit zeven n8n workflows. Elke workflow heeft een specifieke verantwoordelijkheid en draait op een eigen schema.

#NaamSchemaFunctie
01 SI - CVE Feeds Elke 2 uur Haalt CVE's op van VulnCheck NVD++, CISA KEV, EPSS en ENISA EUVD; slaat op in PostgreSQL, markeert KEV-status en EPSS-scores
02 SI - RSS Feeds Elke 4 uur Fetch 5 RSS-feeds (NCSC-NL, BleepingComputer, Krebs, THN, NCSC-UK); slaat artikelen op in news_items
03 SI - AI Analyse Elk uur (50 CVEs/batch) Verwerkt ongeanalyseerde CVE's via Claude AI, berekent relevantiescore (1–10), schrijft matches en asset-suggesties
04 SI - Telegram Alerts Elke 15 minuten Evalueert notificatieregels op tijdvenster en drempelwaardes, stuurt Telegram-alerts met inline keyboards voor asset-suggesties
05 SI - Email Rapport Maandag 08:00 Verstuurt wekelijks e-mailrapport met overzicht van CVE-matches, gerankt op relevantiescore
06 SI - Telegram Callback Webhook (realtime) Verwerkt inline keyboard-reacties vanuit Telegram: goedkeuren of snoozen van asset-suggesties
07 SI - Health Check Elk uur Controleert status van alle componenten (CVE-bronnen, Telegram, SMTP, n8n); schrijft resultaten naar component_health

Notificatie-engine

De notificatie-engine evalueert welke CVE-asset matches een notificatie moeten triggeren op basis van de actieve notificatieregels.

CTE-query (vereenvoudigd)

sql
-- Bepaal welke matches nu genotificeerd moeten worden
WITH current_time_rules AS (
  -- Selecteer regels die actief zijn op dit moment
  SELECT *
  FROM notification_rules
  WHERE active = true
    AND EXTRACT(ISODOW FROM NOW()) = ANY(days_of_week)
    AND time_start <= LOCALTIME
    AND time_end   >  LOCALTIME
),
pending_matches AS (
  -- Alle matches die nog niet genotificeerd zijn
  SELECT
    cam.*,
    c.cve_id, c.description, c.cvss_v3_score,
    c.is_kev, c.epss_score,
    a.vendor, a.product, a.criticality, a.internet_facing
  FROM cve_asset_matches cam
  JOIN cves c ON c.id = cam.cve_id
  JOIN assets a ON a.id = cam.asset_id
  WHERE cam.notified_at IS NULL
)
SELECT pm.*
FROM pending_matches pm
JOIN current_time_rules ctr ON (
  -- Voldoet aan drempelwaardes
  pm.relevance_score >= ctr.min_relevance_score
  OR
  -- KEV override: altijd sturen ongeacht tijdvenster
  (pm.is_kev = true AND ctr.kev_always_send = true)
)
ORDER BY pm.relevance_score DESC;

Health Check Systeem

Elke CVE-bron en systeemcomponent heeft een gezondheidsregistratie in de sources tabel. De health check-logica is zo ontworpen dat alleen getest wordt wanneer dat zinvol is.

Test-beslissingslogica

sql
-- Een component wordt ALLEEN getest als:
SELECT *
FROM component_health
WHERE
  -- Nooit eerder getest, of
  last_tested_at IS NULL
  OR (
    -- Laatste test is meer dan 1 uur geleden, EN
    last_tested_at < NOW() - INTERVAL '1 hour'
    AND
    -- Er was geen activiteit in de laatste 15 minuten
    (last_activity_at IS NULL
     OR last_activity_at < NOW() - INTERVAL '15 minutes')
  );

Dit voorkomt onnodige API-calls wanneer een bron recent actief was. Een bron die 5 minuten geleden data heeft gefetcht, wordt niet opnieuw getest — dat zou zowel nutteloos als potentieel rate-limiting zijn.

Beveiligingsoverwegingen

Geen publieke PostgreSQL poort

PostgreSQL heeft geen externe poortmapping in compose.yaml. Communicatie gaat uitsluitend via het interne Docker-netwerk. Directe databasetoegang van buitenaf is structureel onmogelijk.

API keys als n8n credentials

Alle API keys (VulnCheck, Anthropic, Telegram) worden opgeslagen als versleutelde n8n credentials. Workflow JSON-bestanden bevatten nooit raw API keys — alleen credential ID-referenties.

Traefik TLS-terminatie

Alle publieke endpoints (NocoDB, Admin, Grafana) zijn uitsluitend bereikbaar via HTTPS. Traefik regelt automatische certificaatverlenging via Let's Encrypt. HTTP-toegang wordt omgeleid.

Minimale databaserechten

Elke service gebruikt een eigen PostgreSQL-gebruiker met minimale rechten: NocoDB heeft alleen SELECT/INSERT/UPDATE op zijn tabellen, Grafana heeft uitsluitend SELECT-rechten op statistiektabellen.

.env beveiliging
Het .env-bestand bevat alle secrets en mag nooit worden gecommit naar git. Zorg dat .env in .gitignore staat. Gebruik voor productieopstelling een secrets manager (bijv. Docker Secrets, HashiCorp Vault).

Backup & Restore

Wat u minimaal moet backuppen voor een volledig herstel.

Backup

bash
# PostgreSQL dump (bevat alle CVE's, assets, regels, matches)
docker compose exec postgres pg_dump \
  -U si_app security_informer \
  | gzip > backup_$(date +%Y%m%d_%H%M).sql.gz

# .env bestand (bevat alle secrets)
cp .env /veilige/locatie/.env.backup

# n8n workflow JSONs (voor reproduceerbare deployment)
ls workflows/*.json
# Exporteer ook de huidige workflow-configuratie via n8n API:
curl -s "${N8N_BASE_URL}/api/v1/workflows" \
  -H "X-N8N-API-KEY: ${N8N_API_KEY}" | jq . > workflows_export.json

Restore

bash
# Herstel PostgreSQL uit backup
gunzip -c backup_20260315_1200.sql.gz | \
  docker compose exec -T postgres psql -U si_app security_informer

# Herstart services
docker compose restart

Resource vereisten

ComponentRAM (min)RAM (aanbevolen)CPUOpslag
PostgreSQL128 MB256 MB0.5 core1–5 GB (groeit met CVE-data)
NocoDB128 MB256 MB0.25 core50 MB
Admin Portaal64 MB128 MB0.1 core100 MB
Grafana128 MB256 MB0.25 core200 MB
n8n (extern)256 MB512 MB0.5 core500 MB
Totaal704 MB1.4 GB1.6 cores~2–7 GB

Een VPS met 2 GB RAM en 2 CPU cores is ruim voldoende voor een productie-installatie. Opslag groeit voornamelijk door CVE-data (NVD bevat >250.000 CVE's; dagelijks ~100–300 nieuwe).

Omgevingsvariabelen — volledig overzicht

VariabeleBeschrijvingVerplichtStandaard
POSTGRES_DBDatabasenaamNeesecurity_informer
POSTGRES_USERHoofd DB gebruikerNeesi_app
POSTGRES_PASSWORDHoofd DB wachtwoordJa—
POSTGRES_NOCODB_USERNocoDB DB gebruikerNeesi_nocodb
POSTGRES_NOCODB_PASSWORDNocoDB DB wachtwoordJa—
POSTGRES_GRAFANA_PASSWORDGrafana DB wachtwoordJa—
NC_AUTH_JWT_SECRETNocoDB JWT signing secretJa—
NC_ADMIN_EMAILNocoDB admin e-mailJa—
NC_ADMIN_PASSWORDNocoDB admin wachtwoordJa—
ADMIN_SECRET_KEYFlask session secret keyJa—
ADMIN_USERNAMEAdmin portaal gebruikersnaamNeeadmin
ADMIN_PASSWORDAdmin portaal wachtwoordJa—
N8N_BASE_URLn8n instantie URLJa—
N8N_API_KEYn8n API keyJa—
VULNCHECK_API_KEYVulnCheck Bearer tokenJa—
NVD_API_KEYNVD NIST API key (hogere rate limit)Nee—
TELEGRAM_BOT_TOKENTelegram bot tokenJa—
TELEGRAM_CHAT_IDTelegram chat/groep IDJa—
SMTP_HOSTSMTP server hostnameNee—
SMTP_PORTSMTP poortNee587
SMTP_USERSMTP gebruikerNee—
SMTP_PASSWORDSMTP wachtwoordNee—
REPORT_TO_EMAILOntvanger e-mailrapportenNee—
NOCODB_DOMAINDomein voor NocoDB (Traefik)Ja—
ADMIN_DOMAINDomein voor Admin Portaal (Traefik)Ja—
GRAFANA_DOMAINDomein voor Grafana (Traefik)Ja—
GF_SECURITY_ADMIN_PASSWORDGrafana admin wachtwoordJa—
N8N_CREDENTIAL_ANTHROPIC_IDn8n credential ID voor AnthropicNa stap 6—
N8N_CREDENTIAL_TELEGRAM_IDn8n credential ID voor TelegramNa stap 6—
N8N_CREDENTIAL_VULNCHECK_IDn8n credential ID voor VulnCheckNa stap 6—