Privacy by Design in PA: een wettelijke verplichting en een architectonische keuze

De Algemene Verordening Gegevensbescherming (AVG), die op 25 mei 2018 in werking is getreden, beperkt zich niet tot het opleggen van verplichtingen bureaucratisch voor overheidsdiensten: artikel 25 bepaalt dat gegevensbescherming moet worden geïntegreerd al vanaf de ontwerpfase van informatiesystemen en processen die persoonsgegevens verwerken. Dit principe, bekend als Privacy door ontwerp, maakt naleving van de regelgeving tot één geheel architectonische kwaliteit van de software.

Voor een ontwikkelaar of architect die werkt aan Italiaanse PA-systemen: digitaal register, elektronisch patiëntendossier, servicetoegangsportals met SPID of CIE, beheersystemen voor administratieve procedures - begrijpen en implementeren correct GDPR-by-Design is niet optioneel. De sancties voorzien door de Garantautoriteit voor de Bescherming van Persoonsgegevens ze kunnen 4% van de wereldwijde jaaromzet voor particuliere entiteiten bereiken; voor PA's omvatten de implicaties blokkades voor behandeling, schadevergoeding en aanzienlijke reputatieschade.

Wat u in dit artikel leert

  • De 7 fundamentele principes van Privacy by Design en hoe u deze kunt vertalen naar concrete architectonische beslissingen
  • Pseudonimiserings- en anonimiseringspatronen voor databases en overheids-API's
  • Implementatie van het dataminimalisatiepatroon in PA-microservices
  • Toestemmingsbeheer: architectuur en implementatie van een AVG-compatibel systeem
  • Automatische gegevensbewaring en recht op verwijdering in relationele databases
  • Conforme auditregistratie: hoe u behandelingen kunt volgen zonder de privacy te schenden
  • DPIA (Data Protection Impact Assessment) voor systemen met een hoog risico

De 7 fundamentele principes en hun impact op de architectuur

Ann Cavoukian, voormalig informatie- en privacycommissaris van Ontario, heeft de zeven principes van Privacy by Design geformaliseerd de AVG is geïmplementeerd. Elk principe vertaalt zich in specifieke architectonische keuzes:

PbD-principe Artikel AVG Architectonisch patroon Concrete technieken
Proactief, niet reactief Artikel 25 Privacy voor bedreigingsmodellering Privacy Risicobeoordeling in de ontwerpfase
Privacy als standaard Artikel 25, lid 2 Standaard aanmelden Expliciete toestemming, standaard minimalisatie
Geïntegreerd in het ontwerp Artikel 25, lid 1 In privacy ingebedde architectuur Pseudonimisering, encryptie in rust
Volledige functionaliteit Artikel 5 Het vermijden van nulsom Privacy + veiligheid zijn niet met elkaar in conflict
End-to-end beveiliging Artikel 32 Verdediging in de diepte TLS, encryptie, sleutelbeheer
Zichtbaarheid en transparantie Artikel 13/14 Audittrail + openbaarmaking Geanonimiseerde logboekregistratie, privacyverklaring
Respect voor de privacy van gebruikers Artikel 7/8/17 Gebruikersgerichte bediening Toestemmingsinterface, recht om vergeten te worden, draagbaarheid

Patroon 1: Dataminimalisatie in PA Microservices

Het beginsel van minimalisering (Art. 5(1)(c) AVG) vereist dat de verzamelde gegevens “adequaat, relevant en beperkt zijn tot wat noodzakelijk zijn met betrekking tot de doeleinden waarvoor zij worden verwerkt". In een microservices-architectuur vertaalt dit zich in het patroon Dataminimalisatie aan de grens: elke dienst mag alleen de gegevens ontvangen die hij nodig heeft om te presteren zijn specifieke functie.

Overweeg een identiteitsverificatiedienst om toegang te krijgen tot een gezondheidszorguitkering. De dienst heeft geen naam nodig volledige informatie van de burger om na te gaan of hij recht heeft op de uitkering: een anoniem identificatiemiddel is voor hem en de burger voldoende rechthebbende status. Het patroon wordt geïmplementeerd via Selectieve DTO's (Data Transfer Objects). e projectievragen.

# Pattern Data Minimization - Esempio Python/FastAPI
# Scenario: servizio prestazioni sanitarie PA

from pydantic import BaseModel
from typing import Optional
import hashlib

# SBAGLIATO: trasferisce dati non necessari
class CitizenFullDTO(BaseModel):
    citizen_id: str
    fiscal_code: str
    first_name: str      # Non necessario per verifica
    last_name: str       # Non necessario per verifica
    birth_date: str      # Non necessario per verifica
    address: str         # Non necessario per verifica
    phone: str           # Non necessario per verifica

# CORRETTO: minimizza i dati al minimo necessario
class CitizenEligibilityDTO(BaseModel):
    # Solo un token opaco, non il codice fiscale reale
    pseudonymous_id: str
    is_eligible: bool
    benefit_category: str
    # Nessun dato personale identificativo

# Service layer con minimizzazione
class HealthBenefitService:
    def __init__(self, citizen_repo, pseudonym_service):
        self.citizen_repo = citizen_repo
        self.pseudonym_service = pseudonym_service

    def check_eligibility(self, pseudonymous_id: str, benefit_code: str) -> CitizenEligibilityDTO:
        # Risolve il pseudonimo solo internamente, non lo espone
        real_id = self.pseudonym_service.resolve(pseudonymous_id)

        # Query mirata: solo il dato necessario
        is_eligible = self.citizen_repo.check_benefit_eligibility(
            citizen_id=real_id,
            benefit_code=benefit_code
        )

        return CitizenEligibilityDTO(
            pseudonymous_id=pseudonymous_id,
            is_eligible=is_eligible,
            benefit_category=benefit_code
        )
        # NON ritorna: nome, CF, indirizzo, telefono, email

# Repository con projection query (minimizzazione lato DB)
class CitizenRepository:
    def check_benefit_eligibility(self, citizen_id: str, benefit_code: str) -> bool:
        # SELECT solo la colonna necessaria, non SELECT *
        query = """
            SELECT EXISTS(
                SELECT 1 FROM citizen_benefits
                WHERE citizen_id = {citizen_id}
                AND benefit_code = {benefit_code}
                AND valid_until >= CURRENT_DATE
            )
        """
        return self.db.execute(query, {"citizen_id": citizen_id, "benefit_code": benefit_code}).scalar()

Patroon 2: Pseudonimisering en tokenisering

Pseudonimisering (gedefinieerd in artikel 4, lid 5 AVG) is een van de technische maatregelen die expliciet in de kunst worden genoemd. 25 vind ik leuk voldoende om naleving van Privacy by Design aan te tonen. Verschilt van anonimisering: gepseudonimiseerde gegevens kunnen dat wel kunnen worden herleid tot de belanghebbende door middel van aanvullende informatie die afzonderlijk wordt bewaard, terwijl anonieme gegevens dat niet zijn kunnen zijn (en vallen dus niet onder de AVG).

Voor PA-systemen verdient pseudonimisering vaak de voorkeur boven anonimisering, omdat u hierdoor de gegevens kunt behouden interne traceerbaarheid (noodzakelijk voor audit en compliance) terwijl de identiteit van de betrokkenen in de systemen wordt beschermd front-end en in de logs.

# Pattern Pseudonimizzazione con Vault separato
# Architettura: Pseudonym Vault isolato dal sistema principale

import secrets
import hmac
import hashlib
from datetime import datetime, timedelta
from dataclasses import dataclass

@dataclass
class PseudonymRecord:
    pseudonym: str
    real_id: str
    created_at: datetime
    expires_at: datetime
    purpose: str  # Finalità del trattamento (Art. 5(1)(b))

class PseudonymVault:
    """
    Vault isolato che gestisce la mappatura pseudonimo <-> identità reale.
    Accesso ristretto: solo servizi autorizzati con chiave vault.
    Log di ogni accesso per audit GDPR.
    """

    def __init__(self, vault_key: bytes, db_connection):
        self._vault_key = vault_key
        self._db = db_connection

    def create_pseudonym(
        self,
        real_id: str,
        purpose: str,
        validity_days: int = 365
    ) -> str:
        """
        Genera pseudonimo crittograficamente sicuro.
        Usa HMAC-SHA256 con chiave vault per essere deterministico
        (stesso real_id + purpose = stesso pseudonimo) ma non invertibile
        senza la chiave vault.
        """
        # HMAC deterministico: stesso input = stesso output
        # Ma non reversibile senza vault_key
        pseudonym_bytes = hmac.new(
            key=self._vault_key,
            msg=f"{real_id}:{purpose}".encode(),
            digestmod=hashlib.sha256
        ).digest()

        # Converti in stringa URL-safe
        pseudonym = pseudonym_bytes.hex()[:32]  # 128-bit, abbondante

        # Registra nel vault (separato dal DB principale)
        record = PseudonymRecord(
            pseudonym=pseudonym,
            real_id=real_id,
            created_at=datetime.utcnow(),
            expires_at=datetime.utcnow() + timedelta(days=validity_days),
            purpose=purpose
        )
        self._db.insert_pseudonym(record)

        return pseudonym

    def resolve_pseudonym(self, pseudonym: str, requesting_service: str) -> str:
        """
        Risolve pseudonimo -> identità reale.
        Richiede autorizzazione esplicita del servizio richiedente.
        Logga ogni accesso per audit.
        """
        # Audit log obbligatorio
        self._log_resolution_access(
            pseudonym=pseudonym,
            requesting_service=requesting_service,
            timestamp=datetime.utcnow()
        )

        record = self._db.get_pseudonym(pseudonym)

        if not record:
            raise ValueError("Pseudonym not found")

        if datetime.utcnow() > record.expires_at:
            raise ValueError("Pseudonym expired")

        return record.real_id

    def _log_resolution_access(self, pseudonym: str, requesting_service: str, timestamp: datetime):
        """
        Log GDPR-compliant: registra chi ha risolto quale pseudonimo e quando.
        Il log stesso usa solo lo pseudonimo (non l'identità reale).
        """
        self._db.insert_audit_log({
            "action": "PSEUDONYM_RESOLVED",
            "pseudonym_hash": hashlib.sha256(pseudonym.encode()).hexdigest(),
            "requesting_service": requesting_service,
            "timestamp": timestamp.isoformat(),
            "legal_basis": "GDPR Art. 6(1)(e) - Task public interest"
        })

Patroon 3: AVG-conform toestemmingsbeheer

Toestemming (art. 7 AVG) moet zijn gratis, specifiek, geïnformeerd en ondubbelzinnig. Voor PA's, in de meeste gevallen In sommige gevallen is de verwerking niet gebaseerd op toestemming, maar op verschillende rechtsgrondslagen (Art. 6(1)(e): taak van algemeen belang, o Kunst. 6(1)(c): wettelijke verplichting). Wanneer toestemming echter de gekozen rechtsgrondslag is, bijvoorbeeld voor communicatie marketing, nieuwsbrieven of optionele verwerking: het toestemmingsbeheersysteem moet aan nauwkeurige eisen voldoen.

Un Platform voor toestemmingsbeheer (CMP) voor de PA moet het onthouden: wat werd geaccepteerd, wanneer, waarmee versie van de informatie, via welk kanaal, en moet intrekking net zo eenvoudig mogelijk maken als de verstrekking was.

-- Schema SQL: Consent Management GDPR-compliant
-- Database separato o schema isolato con accesso controllato

CREATE TABLE consent_purposes (
    purpose_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(64) UNIQUE NOT NULL,
    name_it TEXT NOT NULL,
    name_en TEXT,
    description_it TEXT NOT NULL,
    legal_basis VARCHAR(32) NOT NULL,  -- 'consent', 'legal_obligation', 'public_task'
    data_categories TEXT[] NOT NULL,   -- Categorie di dati trattati
    retention_days INTEGER NOT NULL,
    third_parties TEXT[],              -- Destinatari (trasparenza)
    created_at TIMESTAMPTZ DEFAULT NOW(),
    version INTEGER NOT NULL DEFAULT 1,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE citizen_consents (
    consent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    -- Pseudonimo, non ID reale
    citizen_pseudonym VARCHAR(64) NOT NULL,
    purpose_id UUID REFERENCES consent_purposes(purpose_id),
    status VARCHAR(16) NOT NULL CHECK (status IN ('granted', 'denied', 'withdrawn')),
    granted_at TIMESTAMPTZ,
    withdrawn_at TIMESTAMPTZ,
    -- Prova del consenso (Art. 7(1): titolare deve dimostrare il consenso)
    proof_channel VARCHAR(32) NOT NULL,    -- 'web_portal', 'mobile_app', 'paper'
    proof_ip_hash VARCHAR(64),             -- Hash dell'IP, non IP raw
    proof_session_id VARCHAR(128),
    privacy_policy_version VARCHAR(16) NOT NULL,
    -- Lingua in cui è stata mostrata l'informativa
    consent_language VARCHAR(8) NOT NULL DEFAULT 'it',
    user_agent_hash VARCHAR(64)
);

-- Indice per query rapide sulla revoca
CREATE INDEX idx_consents_pseudonym ON citizen_consents(citizen_pseudonym, status);

-- Vista per audit: mostra solo dati necessari al DPO
CREATE VIEW consent_audit_view AS
SELECT
    c.consent_id,
    c.citizen_pseudonym,
    p.code AS purpose_code,
    p.name_it AS purpose_name,
    c.status,
    c.granted_at,
    c.withdrawn_at,
    c.proof_channel,
    c.privacy_policy_version
FROM citizen_consents c
JOIN consent_purposes p ON c.purpose_id = p.purpose_id;

-- Funzione per il diritto di revoca (Art. 7(3))
CREATE OR REPLACE FUNCTION withdraw_consent(
    p_citizen_pseudonym VARCHAR(64),
    p_purpose_code VARCHAR(64)
) RETURNS VOID AS $
BEGIN
    UPDATE citizen_consents
    SET
        status = 'withdrawn',
        withdrawn_at = NOW()
    WHERE
        citizen_pseudonym = p_citizen_pseudonym
        AND purpose_id = (
            SELECT purpose_id FROM consent_purposes WHERE code = p_purpose_code
        )
        AND status = 'granted';

    -- Logga l'evento per audit
    INSERT INTO consent_events (
        event_type, citizen_pseudonym, purpose_code, occurred_at
    ) VALUES (
        'CONSENT_WITHDRAWN', p_citizen_pseudonym, p_purpose_code, NOW()
    );
END;
$ LANGUAGE plpgsql;

Patroon 4: Automatische gegevensbewaring en recht op verwijdering

Het beginsel van "beperking van opslag" (Art. 5(1)(e) AVG) vereist dat persoonlijke gegevens worden opgeslagen "in een vorm die de identificatie van geïnteresseerde partijen mogelijk maakt voor een periode die de prestatie niet overschrijdt van de doeleinden waarvoor ze worden verwerkt“In de praktijk moet elk PA-systeem een retentiebeleid implementeren automatisch dat verlopen gegevens verwijdert of anonimiseert zonder handmatige tussenkomst.

Het recht op gegevenswissing (Art. 17 AVG), bekend als het ‘recht om vergeten te worden’, voegt een extra laag van complexiteit toe: het systeem moet de gegevens van een specifiek individu op verzoek kunnen verwijderen, met inachtneming van eventuele uitzonderingen (bijvoorbeeld gegevens die nodig zijn om te voldoen aan wettelijke verplichtingen).

# Data Retention Manager - Python con scheduling automatico
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from dataclasses import dataclass
from enum import Enum
from datetime import datetime, timedelta
from typing import List

class RetentionAction(Enum):
    DELETE = "delete"
    ANONYMIZE = "anonymize"
    ARCHIVE = "archive"

@dataclass
class RetentionPolicy:
    table_name: str
    date_column: str
    retention_days: int
    action: RetentionAction
    legal_basis: str  # Documentazione dell'obbligo legale

# Registro delle politiche di retention (da configurazione, non hardcoded)
RETENTION_POLICIES: List[RetentionPolicy] = [
    RetentionPolicy(
        table_name="citizen_session_logs",
        date_column="created_at",
        retention_days=90,  # 3 mesi per log sessione
        action=RetentionAction.DELETE,
        legal_basis="GDPR Art. 5(1)(e) - Storage limitation"
    ),
    RetentionPolicy(
        table_name="service_requests",
        date_column="completed_at",
        retention_days=2555,  # 7 anni per atti amministrativi
        action=RetentionAction.ANONYMIZE,
        legal_basis="D.Lgs. 82/2005 Art. 40 - Conservazione atti"
    ),
    RetentionPolicy(
        table_name="consent_records",
        date_column="withdrawn_at",
        retention_days=365,  # 1 anno dopo revoca per prova
        action=RetentionAction.DELETE,
        legal_basis="GDPR Art. 7(1) - Prova consenso"
    ),
]

class DataRetentionManager:
    def __init__(self, db, audit_logger):
        self.db = db
        self.audit_logger = audit_logger
        self.scheduler = AsyncIOScheduler()

    async def run_retention_policy(self, policy: RetentionPolicy) -> dict:
        """Esegue una politica di retention e ritorna statistiche."""
        cutoff_date = datetime.utcnow() - timedelta(days=policy.retention_days)
        stats = {"policy": policy.table_name, "action": policy.action.value, "rows_affected": 0}

        if policy.action == RetentionAction.DELETE:
            result = await self.db.execute(
                f"DELETE FROM {policy.table_name} WHERE {policy.date_column} < $1",
                cutoff_date
            )
            stats["rows_affected"] = result.rowcount

        elif policy.action == RetentionAction.ANONYMIZE:
            # Anonymize: sostituisce dati identificativi con hash o NULL
            result = await self.db.execute(
                f"""UPDATE {policy.table_name}
                    SET
                        fiscal_code = 'ANONYMIZED_' || gen_random_uuid()::text,
                        first_name = NULL,
                        last_name = NULL,
                        email = NULL,
                        phone = NULL
                    WHERE {policy.date_column} < $1
                    AND fiscal_code NOT LIKE 'ANONYMIZED_%'""",
                cutoff_date
            )
            stats["rows_affected"] = result.rowcount

        # Audit log obbligatorio per ogni operazione di retention
        await self.audit_logger.log({
            "event": "RETENTION_EXECUTED",
            "policy": policy.table_name,
            "action": policy.action.value,
            "cutoff_date": cutoff_date.isoformat(),
            "rows_affected": stats["rows_affected"],
            "legal_basis": policy.legal_basis,
            "executed_at": datetime.utcnow().isoformat()
        })

        return stats

    async def handle_erasure_request(self, citizen_pseudonym: str, request_id: str) -> dict:
        """
        Gestisce il diritto alla cancellazione (Art. 17 GDPR).
        Verifica le eccezioni prima di procedere.
        """
        # Verifica eccezioni Art. 17(3) - obblighi legali, interesse pubblico, etc.
        exceptions = await self._check_erasure_exceptions(citizen_pseudonym)

        if exceptions:
            # Non cancella, documenta il motivo del diniego
            await self.audit_logger.log({
                "event": "ERASURE_REQUEST_DENIED",
                "citizen_pseudonym": citizen_pseudonym,
                "request_id": request_id,
                "reasons": exceptions
            })
            return {"status": "denied", "reasons": exceptions}

        # Procede con la cancellazione
        tables_affected = []
        for table in ["citizen_session_logs", "consent_records", "service_requests_temp"]:
            rows = await self.db.execute(
                f"DELETE FROM {table} WHERE citizen_pseudonym = $1",
                citizen_pseudonym
            )
            if rows.rowcount > 0:
                tables_affected.append(table)

        await self.audit_logger.log({
            "event": "ERASURE_REQUEST_EXECUTED",
            "citizen_pseudonym": citizen_pseudonym,
            "request_id": request_id,
            "tables_affected": tables_affected,
            "executed_at": datetime.utcnow().isoformat()
        })

        return {"status": "completed", "tables_affected": tables_affected}

DPIA: wanneer het verplicht is en hoe je het structureert

La Gegevensbeschermingseffectbeoordeling (DPIA, Data Protection Impact Assessment) is verplicht overeenkomstig art. 35 AVG bij de verwerking "kan een hoog risico vormen voor de rechten en vrijheden van natuurlijke personen"Voor de Italiaanse PA heeft de Privacy Garant een lijst gepubliceerd met behandelingen die dit vereisen DPIA is verplicht.

PA-behandelingen waarvoor verplichte DPIA vereist is

  • Systematische profilering van burgers (bijvoorbeeld sociaal-economische scores)
  • Grootschalige behandeling van bepaalde categorieën gegevens (gezondheidszorg, justitie)
  • Systematisch toezicht van openbare ruimtes (videobewaking)
  • Matching/koppelsystemen van datasets uit verschillende bronnen
  • Gegevens van kwetsbare mensen (minderjarigen, patiënten, asielzoekers)
  • Innovatief gebruik van technologieën (AI/ML voor geautomatiseerde beslissingen art. 22)
  • Internationale overdracht van persoonlijke gegevens

Een gestructureerde DPIA omvat: beschrijving van de verwerking en de doeleinden ervan, beoordeling van noodzakelijkheid en proportionaliteit, identificatie en beoordeling van risico's voor de rechten van betrokkenen, en de beoogde maatregelen om de risico's aan te pakken (inclusief waarborgen en beveiligingsmechanismen). De betrokkenheid van de DPO (Data Protection Officer) is verplicht (Art. 35(2)) en de DPIA moeten worden bijgewerkt wanneer de behandeling verandert.

Patroon 5: AVG-conforme auditregistratie

Een onderschatte eis van de AVG is de verantwoordelijkheid (verantwoording, art. 5, lid 2): de eigenaar van de behandeling moet therapietrouw kunnen aantonen. Dit vereist een auditregistratiesysteem dat traceert bewerkingen op persoonsgegevens, maar die – paradoxaal genoeg – zelf geen bron worden van overmatige verwerking van persoonsgegevens.

Een AVG-conform auditlogboek moet:

  • Dossier WHO toegang gekregen tot welke gegevens en wanneer, waar mogelijk met behulp van pseudoniemen of systeem-ID's
  • Zijn onveranderlijk (alleen toevoegen) om de integriteit van het bewijsmateriaal te waarborgen
  • Zorg voor een eigen bewaarbeleid (logboeken worden niet eeuwig bewaard)
  • Wees beschermd tegen ongeautoriseerde toegang (gescheiden van de hoofddatabase)
  • Ondersteun efficiënte vragen om te reageren op verzoeken van de DPO of de Garantautoriteit
# Audit Logger immutabile con PostgreSQL - append-only tramite trigger
# Usa una tabella separata con accesso write-only dall'applicazione

-- Schema audit log (database o schema separato)
CREATE TABLE gdpr_audit_log (
    log_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    -- Identificatori: pseudonimizzati dove possibile
    subject_pseudonym VARCHAR(64),       -- Chi ha eseguito l'azione
    affected_entity_pseudonym VARCHAR(64), -- Su quale entità
    -- Cosa è successo
    action_type VARCHAR(64) NOT NULL,    -- READ, UPDATE, DELETE, EXPORT, etc.
    resource_type VARCHAR(64) NOT NULL,  -- citizen_record, consent, health_data, etc.
    -- Contesto
    legal_basis VARCHAR(128),            -- Base giuridica del trattamento
    purpose VARCHAR(256),                -- Finalità del trattamento
    -- Metadati tecnici (senza dati personali)
    service_name VARCHAR(128) NOT NULL,
    request_id VARCHAR(128),
    -- Timestamp immutabile
    occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    -- NON includere: IP raw, user agent completo, payload della request
);

-- Nega UPDATE e DELETE sulla tabella audit (append-only)
CREATE RULE no_update_audit AS ON UPDATE TO gdpr_audit_log DO INSTEAD NOTHING;
CREATE RULE no_delete_audit AS ON DELETE TO gdpr_audit_log DO INSTEAD NOTHING;

-- Funzione trigger per log automatico su tabelle sensibili
CREATE OR REPLACE FUNCTION audit_sensitive_access() RETURNS TRIGGER AS $
BEGIN
    INSERT INTO gdpr_audit_log (
        action_type,
        resource_type,
        subject_pseudonym,
        affected_entity_pseudonym,
        service_name,
        legal_basis
    ) VALUES (
        TG_OP,                          -- INSERT/UPDATE/DELETE
        TG_TABLE_NAME,
        current_setting('app.current_user_pseudonym', true),
        NEW.citizen_pseudonym,
        current_setting('app.service_name', true),
        current_setting('app.legal_basis', true)
    );
    RETURN NEW;
END;
$ LANGUAGE plpgsql SECURITY DEFINER;

-- Applica il trigger alle tabelle sensibili
CREATE TRIGGER audit_health_records
    AFTER INSERT OR UPDATE OR DELETE ON health_records
    FOR EACH ROW EXECUTE FUNCTION audit_sensitive_access();

Algemene architectuur: AVG-by-Design in een PA-service

Door alle beschreven patronen samen te voegen, omvat de architectuur van een AVG-by-Design-compatibele PA-service:

  • API-gateway: Ingangspunt dat de authenticatie verifieert (SPID/CIE), en pseudonimisering toepast en geeft de rechtsgrondslag uit in het kader van het verzoek (via header of middleware)
  • Pseudoniem kluis: geïsoleerde dienst voor het in kaart brengen van pseudoniemen, met beperkte toegang en auditlogboek van elke resolutie
  • Microservices met dataminimalisatie: elke dienst ontvangt alleen de noodzakelijke gegevens, stelt minimale DTO's bloot, gebruik projectiequery's naar de database
  • Toestemmingsbeheerservice: beheert toestemmingen, verifieert de rechtsgrondslagen, ondersteunt intrekking en gegevensexport (portabiliteit art. 20)
  • Bewaarplanner: periodieke taken die bewaarbeleid toepassen en verzoeken beheren annulering en het opstellen van rapporten voor de DPO
  • Auditlogboekservice: alleen toevoegen, afzonderlijk, met alleen-schrijven-toegang vanuit de applicatie en alleen-lezen door de DPO/garant

Handige tools en raamwerken voor GDPR-by-Design in de PA

  • OpenDP: Python-bibliotheek voor differentiële privacy, handig voor geanonimiseerde analyses
  • ARX-tool voor gegevensanonimisering: open source tool voor het anonimiseren van datasets
  • Sleutelmantel: Open source identiteitsprovider met ingebouwde ondersteuning voor toestemmingsbeheer
  • Gegevensschets: voor hashing en MinHash, handig bij schaalbare pseudonimisering
  • PgAudit: PostgreSQL-extensie voor auditlogboekregistratie op databaseniveau
  • Ontwerpers Italië: officiële AgID-richtlijnen over privacy by design voor PA
  • CNIL Privacy by Design-gids: praktische methodologie van de Franse autoriteit, toepasbaar in Italië

Rol van de DPO en samenwerking met het ontwikkelteam

Op grond van art. 37 AVG is de aanstelling van een functionaris voor gegevensbescherming (DPO) verplicht voor alle overheidsinstanties en overheidsinstanties (met uitzondering van rechtbanken bij de uitoefening van jurisdictiefuncties). De DPO is dat niet slechts een compliance-cijfer: voor ontwikkelingsteams vertegenwoordigt hij een interne gegevensbeschermingsconsulent die moeten vanaf de beginfase van een project betrokken worden.

In de praktijk moet het ontwikkelteam:

  • Betrek de FG bij de architectuurbeoordeling bij het verwerken van persoonsgegevens
  • Documenteer privacyontwerpkeuzes in Architecture Decision Records (ADR)
  • Neem controles op de naleving van de AVG op in de definities van gebruikersverhalen
  • Voer een privacyrisicobeoordeling uit voor elke nieuwe functie die de verwerking van persoonsgegevens introduceert
  • Houd het Verwerkingsregister (art. 30) waar mogelijk geautomatiseerd actueel

Conclusies en volgende stappen

GDPR-by-Design is geen bureaucratische checklist die aan het einde van de ontwikkeling moet worden afgevinkt: het is een architecturale aanpak die, indien geïntegreerd vanaf de vroege stadia van het project, veiligere, transparantere en robuustere systemen oplevert. De patronen die in dit artikel worden beschreven: gegevensminimalisatie, pseudonimisering, toestemmingsbeheer, retentie automatische auditregistratie – ze zijn van toepassing op elke digitale dienst van de Italiaanse PA, vanaf een eenvoudige dienst pagina voor het verzamelen van toestemming voor een institutionele nieuwsbrief tot aan complexe systemen zoals het medisch dossier elektronische of digitale betalingsplatforms.

In het volgende artikel van deze serie zullen we analyseren hoe toegankelijke gebruikersinterfaces voor de tweede PA kunnen worden geïmplementeerd de WCAG 2.1 AA-standaard: een andere wettelijke vereiste die, net als de AVG, degenen beloont die deze vanaf de ontwerpfase integreren.

Gerelateerde artikelen in deze serie

  • GovTech #01: eIDAS 2.0 en EUDI Wallet - Europese digitale identiteit en verifieerbare inloggegevens
  • GovTech #02: OpenID Connect voor overheidsidentiteit - SPID, CIE en best practices op het gebied van beveiliging
  • GovTech #05: Toegankelijke gebruikersinterface voor PA - WCAG 2.1 AA-implementatie
  • GovTech #06: Overheids-API-integratie - SPID, CIE en pagoPA