
Compliance
GDPR Article 32 in Practice: Technical Measures an Auditor Can Verify
GDPR compliance at the infrastructure layer: encryption, pseudonymisation, provable restore, and erasure across backups, logs and vector stores.
Search for GDPR compliance and you get legal checklists from compliance vendors. Search for what an engineer builds and you get upstream documentation — the Kubernetes EncryptionConfiguration, Loki's deletion API, Qdrant's optimiser — that never says GDPR once. The join is where the work lives.
This article stays inside security of processing. Whether personal data may lawfully leave the EU is a separate question; this is how it is secured wherever it sits.
Article 32 Is a Specification, Not a Sentiment
Article 32(1) reads like a requirements document because it is one. It requires measures ensuring a level of security appropriate to the risk, including "(a) the pseudonymisation and encryption of personal data; (b) the ability to ensure the ongoing confidentiality, integrity, availability and resilience of processing systems and services; (c) the ability to restore the availability and access to personal data in a timely manner in the event of a physical or technical incident; (d) a process for regularly testing, assessing and evaluating the effectiveness" of those measures.
Four limbs, four buildable things. Article 32(2) says what to size them against: risks "from accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to personal data" — threat categories, not virtues.
"Appropriate" is a proportionality test — state of the art, cost, and the nature and purposes of the processing, weighed against risk to people. The answer is defensible rather than correct, so the reasoning has to exist in writing. Article 5(2) makes the controller "responsible for, and be able to demonstrate compliance with" the principles. The burden is demonstrability: not that you encrypt, but that you can show you did, on a date.
Article 30(1)(g) forces the mapping into writing anyway: the record of processing must contain "where possible, a general description of the technical and organisational security measures referred to in Article 32(1)". The under-250-employee exemption in Article 30(5) evaporates where processing risks rights and freedoms, is "not occasional", or touches special-category data. Platform processing is not occasional.
Article 32 sits in the Articles 25-to-39 range, so infringements fall under Article 83(4)(a): up to EUR 10 000 000 or 2% of worldwide annual turnover, whichever is higher — the lower tier.
Encryption at Rest Is Three Independent Layers
"Is it encrypted at rest" is one line in a questionnaire and three separate controls in a cluster. They remove different threats, and having one does not give the others. The first is API-server encryption of what lands in etcd: an EncryptionConfiguration loaded with --encryption-provider-config.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- customresourcedefinitions.apiextensions.k8s.io
providers:
# The FIRST provider listed encrypts new writes.
- kms:
apiVersion: v2
name: sovereign-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
timeout: 3s
# identity belongs LAST, and only until what is already written has
# been re-encrypted. Leave it first and every write is in clear.
- identity: {}Two traps live in that file. Order is semantic — the first provider encrypts new writes, so an identity entry at the top means everything is written in the clear while the configuration looks encrypted. And aescbc, still widely copied, is rated Weak upstream, "not recommended due to CBC's vulnerability to padding oracle attacks". KMS v2 is stable from v1.29; v1 was deprecated in v1.28 and off by default from v1.29.
Enabling encryption changes nothing already written: until every affected object is rewritten, the old plaintext sits in etcd underneath. Where the key material lives is the sovereignty question — the same one as external secrets and key custody. A KMS you do not control is an escrow you cannot audit.
The second layer is volume encryption, LUKS or the CSI driver's own. It removes one threat: a disk leaving the building with data on it. It does nothing against a compromised node, where the volume is mounted and readable. The third is column encryption in the database — the only layer that holds against legitimate database access, and the one that makes crypto-shredding possible.
The payoff is statutory. Article 34(3)(a) removes the obligation to communicate a breach to data subjects where the controller applied measures "that render the personal data unintelligible to any person who is not authorised to access it, such as encryption". The EDPB is direct: encrypted data "will be unintelligible" without the key, whereas "pseudonymisation techniques alone cannot be regarded as" enough.
Pseudonymisation Is a Data-Model Decision, Not a Library Call
Article 4(5) defines pseudonymisation as processing such that data "can no longer be attributed to a specific data subject without the use of additional information, provided that such additional information is kept separately". The load-bearing phrase is "kept separately". A hashed email beside the name, in one table, behind one role, is an obfuscated identifier.
Recital 26 closes the other door: data "which could be attributed to a natural person by the use of additional information should be considered to be information on an identifiable natural person". Pseudonymisation reduces risk; it does not move data out of the Regulation.
The EDPB's Guidelines 01/2025 on Pseudonymisation — adopted for public consultation in January 2025 and still carrying that status, so direction rather than requirement — introduce the pseudonymisation domain: the context in which attribution is meant to be precluded. They hold that data stays personal even where it "and additional information are not in the hands of the same person", which undercuts the split where a processor holds tokens and the controller the mapping. The question is not which hash function, but which role reads which store.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE SCHEMA IF NOT EXISTS keyvault;
CREATE ROLE app_rw NOLOGIN;
-- Article 4(5) "additional information", kept separately: own schema,
-- own grants, own backup policy, own retention.
CREATE TABLE keyvault.subject_key (
subject_token uuid PRIMARY KEY,
dek text NOT NULL -- wrapped by an external KMS in production
);
REVOKE ALL ON SCHEMA keyvault FROM app_rw;
-- Ciphertext and an opaque token; app_rw cannot reach the keys.
CREATE TABLE subject_profile (
subject_token uuid PRIMARY KEY,
email_ct bytea NOT NULL
);
GRANT SELECT, INSERT, UPDATE, DELETE ON subject_profile TO app_rw;
-- Issue the key and write the ciphertext in one transaction.
WITH k AS (
INSERT INTO keyvault.subject_key
VALUES ('00000000-0000-4000-8000-000000000001', gen_random_uuid()::text)
RETURNING subject_token, dek
)
INSERT INTO subject_profile (subject_token, email_ct)
SELECT k.subject_token, pgp_sym_encrypt('[email protected]', k.dek) FROM k;
-- Crypto-shred: one row. Every ciphertext for that subject, wherever it
-- was copied, stops being readable at the same instant.
DELETE FROM keyvault.subject_key
WHERE subject_token = '00000000-0000-4000-8000-000000000001';Two properties fall out. The key table gets its own backup policy and retention, so separation is real at the storage layer. And erasure becomes a single-row delete, not a rewrite chasing replicas. Article 25(1) names pseudonymisation as a by-design measure, implemented "at the time of the determination of the means for processing" — a schema decision, expensive to revisit.
Limb (c): Restoring Availability Is an RTO You Have to Demonstrate
Limb (c) is the one most teams believe they already satisfy. "We have backups" is not what it asks. It asks for the ability to restore availability and access to personal data in a timely manner after an incident — ability, timely, demonstrated. A backup nobody restored is an assertion.
The drill that discharges it is a recovery into a throwaway namespace with a clock on it. On CloudNativePG, point-in-time recovery is a bootstrap mode, so the drill is a manifest — schedulable and reviewable.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: subject-db-drill # throwaway clone; production untouched
namespace: restore-drill
spec:
instances: 1
bootstrap:
recovery:
source: subject-db-prod
recoveryTarget:
targetTime: '2026-08-01 03:00:00.00000+00' # the claim under test
externalClusters:
- name: subject-db-prod
# Barman Cloud Plugin. The in-tree barmanObjectStore still works,
# but is deprecated from v1.26 — do not start new work on it.
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: subject-db-store # an ObjectStore resource
serverName: subject-db-prod
storage:
size: 200GiNote what moved. CloudNativePG deprecated the in-tree barmanObjectStore in v1.26 for the Barman Cloud Plugin, and spec.backup.retentionPolicy with it, so the store is now its own ObjectStore resource referenced by name. recoveryTarget.targetTime is unaffected, which is why the drill is built on it — but retention is a moving part, not a stable field.
Velero has a quieter version. A Backup carries spec.ttl, and left unset, "a default value of 30 days will be used" — a personal-data retention period nobody chose. The RPO and RTO discipline is unchanged; Article 32 adds that the drill result is evidence, kept and dated even when it fails.
The Erasure Problem: Article 17 Against Backups, Logs and Embeddings
Article 17(1) obliges the controller to erase personal data "without undue delay" on the listed grounds — no longer necessary for the purposes collected for, consent withdrawn with no other basis. In an application that is a delete endpoint. Across an estate it is a fan-out over storage classes that define deletion differently, and two do not mean it.
Start with the database, the layer people assume is solved. In PostgreSQL "an UPDATE or DELETE of a row does not immediately remove the old version of the row". The dead tuple stays until VACUUM, and standard VACUUM "will not return the space to the operating system". The row is gone from queries and present on the volume, the replica, the base backup, and every WAL segment that recorded it. A self-hosted RAG stack on pgvector inherits it exactly.
Then the backups, and this is the wall. Object storage under a COMPLIANCE-mode lock is deliberately undeletable: on S3 "a protected object version can't be overwritten or deleted by any user, including the root user", and retention "can't be shortened". Self-hosted object storage is no escape — MinIO calls its COMPLIANCE lock functionally identical. So the control satisfying limbs (b) and (c) is what makes a literal Article 17 delete impossible — design, not defect. Article 17(3)(b) disapplies erasure where processing is "necessary for compliance with a legal obligation" under Union or Member State law: a real carve-out for a statutory retention period, not for a lock chosen because immutability is good hygiene. Which of the two each retention class is, gets written down.
Logs are the class most often forgotten: nobody treats an access log as a personal-data store until someone greps one. Loki deletes entries only if asked properly — retention enabled on the compactor, delete_request_store set, deletion_mode chosen. With filter-only, matching lines "are filtered out when querying Loki. They are not removed from storage." With filter-and-delete they are removed. Left on filter-only you get clean query results over untouched data — the most convincing kind of wrong. Self-hosted observability makes it fixable, not automatic.
compactor:
working_directory: /var/loki/compactor
retention_enabled: true # delete requests ignored without this
delete_request_store: s3
limits_config:
retention_period: 720h
# filter-only removes matching lines from query results only.
# Only filter-and-delete removes them from object storage.
deletion_mode: filter-and-delete
schema_config:
configs:
# Log-entry deletion is supported only on TSDB or BoltDB Shipper.
- from: '2026-01-01'
store: tsdb
object_store: s3
schema: v13
index: { prefix: index_, period: 24h }Vectors are the newest class and behave like the oldest. Qdrant "marks records as deleted and ignores them for future queries", and they persist until the Vacuum Optimizer rebuilds the segment. Delete by payload filter, not by point ID — nobody can enumerate one subject's points after months of re-embedding — and the call schedules erasure rather than performing it.
#!/usr/bin/env bash
# Excerpt: angle-bracket placeholders are deployment-specific.
set -euo pipefail
SUBJECT="<SUBJECT_TOKEN>" # pseudonymous token, never the email
TENANT="<TENANT>"; LOKI="<LOKI_BASE_URL>"; QDRANT="<QDRANT_BASE_URL>"
FROM=$(date -u -d '-400 days' +%s); TO=$(date -u +%s)
# 1. Loki. On filter-only this hides lines instead of removing them.
curl -sS -X POST "$LOKI/loki/api/v1/delete" \
-H "X-Scope-OrgID: $TENANT" \
--data-urlencode "query={app=\"api\"} |= \"$SUBJECT\"" \
--data-urlencode "start=$FROM" --data-urlencode "end=$TO"
# 2. Qdrant. By filter: point IDs are not enumerable per subject.
curl -sS -X POST "$QDRANT/collections/documents/points/delete" \
-H "Content-Type: application/json" \
-d "{\"filter\":{\"must\":[{\"key\":\"subject_token\",\"match\":{\"value\":\"$SUBJECT\"}}]}}"
# 3. Postgres. Destroy the key. This only shreds copies that never held
# it: the key store's own backups must be short-lived and non-WORM.
psql -v ON_ERROR_STOP=1 -c \
"DELETE FROM keyvault.subject_key WHERE subject_token = '$SUBJECT'::uuid;"
# 4. Register it: under Article 5(2), an unrecorded erasure did not happen.
printf '%s subject=%s key=destroyed\n' "$(date -u +%FT%TZ)" "$SUBJECT" \
>> /var/log/erasure-register.logWhich leaves the case with no delete path: personal data in a WORM-locked backup with years of retention left. The answer is never to have written it there in the clear. Encrypt the column with a per-subject key held separately, and destroying that key renders every copy unreadable at once.
Limb (d): Testing the Measures Is a Pipeline, Not an Annual Exercise
"Regularly testing, assessing and evaluating the effectiveness" separates a control catalogue from a control system, and it decays fastest. It has a checkable output: an artefact with a date on it.
The default answer — we run kube-bench — has a gap worth knowing before someone else finds it. Its newest CIS mapping, read on 1 August 2026, is cis-1.12, covering Kubernetes 1.32 to 1.34. Current stable is 1.36. Nothing matches, so you run an older profile against a newer control plane, or auto-detect into one. Neither is fatal; both need recording, with the profile that ran. Image scans are the same: Trivy exits 0 by default even when it finds things, so --exit-code 1 --severity CRITICAL,HIGH is what makes it a gate.
Continuous enforcement is the other half. A quarterly scan says what was true in April; an admission policy says what has been true since. Kyverno does that, with one field to read carefully. spec.validationFailureAction is deprecated in favour of spec.rules[*].validate[*].failureAction, so migrate — but deprecation moves the field, it does not change what it does: on either, Enforce blocks the create or update and Audit only logs a violation to a policy report. The evidence question is never which field a policy uses but which value it carries: a rule on Audit emits reports that read like enforcement and stop nothing.
What failure looks like is not exotic. In January 2026 the CNIL fined FRANCE TRAVAIL EUR 5 000 000 under deliberation SAN-2026-003 for breaching Article 32, finding — in my translation of the French — that the authentication letting CAP EMPLOI advisers into the system was not sufficiently robust, and noting the insufficiency of logging able to detect anomalous behaviour. Authentication and logging.
Failure Modes We See in the Field
- Encryption enabled, never re-encrypted.
identitystill first in the provider list, or old Secrets never rewritten. The config is evidence of intent; etcd is evidence of fact. - Pseudonymisation that shares a credential. Token and mapping in one database, behind one role. One connection string answers whether you have Article 4(5) separation.
- Erasure that stops at the primary. The endpoint works; the log stream, the vector collection, the read replica and the analytics extract were never in the fan-out. Enumerate storage classes, not services.
- Processor evidence taken on trust. Article 28(3) binds the processor to "all measures required pursuant to Article 32" and to "allow for and contribute to audits, including inspections". A clause never exercised is a contract, not an assurance.
Exit Ramps: Evidence That Outlives the Platform
Migrations reset compliance positions, and they should not. The Article 30 record, the mapping and the evidence are yours; the platform that produced them is replaceable. Keep evidence as dated files in open formats — JSON from kube-bench and Trivy, plain text from the drill — in storage you can move.
Write the mapping in capabilities rather than products, so "API-server-level encryption with an external KMS" survives a change of KMS. That is leverage under Article 28(3): a processor that knows you hold first-party evidence is replaceable — the same argument that makes an ISO 27001 scope you control cheaper in its third year than its first.
The Long Game: What Changes, and What Does Not
Build against the stable parts. Articles 17, 25, 30 and 32 have not moved since 2018, and the Commission's Digital Omnibus proposal — COM(2025) 837 final, 19 November 2025, not in force — does not amend Article 30 or Article 32 at all. Article 5 is not on that list: the proposal reopens it, so treat the principles as a moving part and the security-of-processing duties as the fixed point.
What is in motion is narrower than the commentary suggests. The proposal would replace Article 33(1): not merely 72 hours becoming 96, but the trigger narrowing to breaches likely to result in a high risk, notified through a single-entry point. It would add an Article 41a empowering the Commission to specify when pseudonymised data "no longer constitutes personal data for certain entities", and makes identifiability under Article 4 relative to the means reasonably likely to be used by the entity holding the data. Proposed, contested, not law today.
So plan for the boundary to move and the obligation to stay. The measures do not become optional; what counts as still-personal might. Systems that keep key custody separate and evidence portable absorb either outcome without a rebuild — the argument for owning the substrate, in the terms a regulator asks about.
§FAQ/Common questions
Frequently asked
What does GDPR Article 32 actually require in technical terms?
Article 32(1) requires controllers and processors to implement appropriate technical and organisational measures ensuring a level of security appropriate to the risk, and names four: (a) pseudonymisation and encryption of personal data; (b) the ability to ensure ongoing confidentiality, integrity, availability and resilience of processing systems and services; (c) the ability to restore availability and access to personal data in a timely manner after a physical or technical incident; and (d) a process for regularly testing, assessing and evaluating the effectiveness of those measures. On self-hosted infrastructure each maps to something buildable: API-server, volume and column encryption for (a); scoped RBAC, default-deny networking and replica topology for (b); a timed restore drill for (c); and a CI job emitting dated benchmark and scan artefacts for (d). Article 30(1)(g) then requires the record of processing to carry a general description of those measures, so the mapping has to be written down regardless.
Does encrypting personal data mean you do not have to report a breach?
It changes one of the two notifications, not both. Article 33(1) notification to the supervisory authority still applies on its own terms — without undue delay and, where feasible, within 72 hours of becoming aware. What encryption can remove is the Article 34 communication to affected data subjects: Article 34(3)(a) disapplies it where the controller implemented protection measures, applied to the affected data, that render it unintelligible to any unauthorised person, such as encryption. The EDPB's breach-notification guidelines state that data protected by an appropriate level of encryption will be unintelligible without the decryption key, and equally that pseudonymisation techniques alone cannot be regarded as sufficient for that purpose. The practical consequence is that key custody matters as much as the cipher: if the key was in scope of the same compromise, the exemption does not help you.
How do you comply with the right to erasure when backups are immutable?
You do not delete the backup, and you should not pretend you can. Under an S3 or MinIO COMPLIANCE-mode object lock, a protected object version cannot be overwritten or deleted by any user including root, and the retention period cannot be shortened. Two things follow. First, the retention itself needs a justification recorded per class, which is what Article 17(3)(b) is for where processing is necessary for compliance with a legal obligation. Second, design so that personal data in those backups was never written in the clear: encrypt the identifying columns with a per-subject key held in a separate store, and destroy the key when the erasure request arrives. That is crypto-shredding: risk reduction on Article 34(3)(a) reasoning, not erasure — no regulator has ruled that destroying a key satisfies Article 17, so document it as what it is.
Is pseudonymised data still personal data under GDPR?
Yes. Recital 26 states that pseudonymised data which could be attributed to a natural person by the use of additional information should be considered information on an identifiable natural person, with identifiability judged against all the means reasonably likely to be used. Article 4(5) defines pseudonymisation as requiring the additional information to be kept separately and subject to measures preventing attribution — separation is the requirement, not the hash. The EDPB's Guidelines 01/2025, adopted for public consultation and still in that status, add that the data remains personal even where the pseudonymised data and the additional information are not held by the same person. The Commission's Digital Omnibus proposal would revisit exactly this boundary through a new Article 41a and added language in Article 4, but it is a proposal and not in force, so the current position stands.
What GDPR evidence should a self-hosted Kubernetes platform keep?
Dated artefacts, in open formats, in storage you control, including the failures. For encryption: the provider list and key identifiers read back from the running API server, plus the record of the re-encryption pass that let you remove the identity provider. For pseudonymisation: the schema showing separation, and an access record showing no single credential reaches both stores. For ongoing security: policy reports and the admission decisions that were refused, not only those allowed. For restore: each drill's start and end timestamps, the measured recovery time, the target time recovered to, and an integrity verdict. For testing: the benchmark run with its pinned profile version, the scan output and its exit code, and the date each was produced. Those artefacts collectively populate the Article 30(1)(g) description, and because they are files rather than a vendor's dashboard, they survive a change of platform.
Further reading
- Schrems II in practice: keeping EU data out of US jurisdiction
- PostgreSQL on Kubernetes with CloudNativePG: sovereign stateful data
- Audit-grade Kubernetes disaster recovery: proving RPO and RTO
- Kubernetes secrets management with External Secrets and Vault
- Self-hosted vector databases: Qdrant and pgvector for on-prem RAG
- Self-hosted observability with OpenTelemetry, Prometheus, Grafana and Loki
- Policy as code with Kyverno: governance that evidences itself
- ISO 27001 without inherited controls: certification, not attestation
- Sovereign object storage: MinIO, Rook-Ceph, SeaweedFS and Garage
- SOC 2 compliance on self-hosted Kubernetes: own the evidence
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.