Part 3 · Code & Infrastructure

3 · Build

Writing code and provisioning infrastructure: the Art. 32 technical and organisational measures, plus the consent and vendor mechanics that touch the build phase. These are the controls an auditor samples and a breach response hinges on. Three truths run throughout: Art. 32 is risk-based, not prescriptive; defaults dominate; and logs are personal data too.

3.1Access control: IAM, RBAC, ABAC

References

GDPR Art. 32

The ruleDefault deny, least privilege, just-in-time elevation, workload identity over static keys. Most "GDPR incidents" are really access-control failures.

Why we care

An overly broad role, a forgotten test account, a service principal with * on a storage account: that's what DPA investigations actually find. Least privilege is the single highest-impact control you can ship.

Key patterns

  • Default deny, explicit allow. Start every IAM policy, RBAC role, and network policy from zero permissions and add what is needed. Wildcard actions (s3:*, Action: "*" in IAM policies) should require a written exception.
  • Separation of duties. The principal that can read production personal data should not be the same principal that can disable logging, rotate keys, or modify IAM. Split data-plane from control-plane roles; require break-glass for the union.
  • Just-in-time (JIT) elevation. Standing access to personal data is the anti-pattern; time-boxed, reason-logged elevation is the pattern. Tooling: AWS IAM Identity Center session policies, time-bound IAM policy conditions, Kubernetes short-lived ServiceAccount tokens.
  • Attribute-based access (ABAC) for scale. Tag data resources with data-class=personal, purpose=billing, region=eu-west-1; write policies that match on tags rather than enumerating resource ARNs. This survives reorganisations and new accounts.
  • Workload identity, not long-lived keys. Use IRSA or EKS Pod Identity so that compute proves who it is to the control plane. Static access keys in env vars are an Art. 32 finding waiting to happen.

Anti-patterns / gotchas

  • "Just attach PowerUserAccess for now" can read most data stores in the account; for data purposes it is effectively admin.
  • Trust policies that allow sts:AssumeRole from Principal: "*" with a condition only on aws:SourceAccount, easy to mis-scope; require an ExternalId or org-id condition.
  • Group sprawl. If users inherit access via 14 nested AD groups, no one can answer "who can read this dataset?", which is the question Art. 15 (right of access by data subjects, indirectly) and Art. 30 records will eventually force you to answer.
Cloud notes: AWS · K8s
  • AWS: IAM Identity Center for human access, IAM roles with aws:PrincipalTag and aws:ResourceTag conditions for ABAC, SCPs at the OU level to enforce floors (e.g. deny s3:PutBucketPolicy that opens public access). Access Analyzer for unintended external access.
  • Kubernetes: Namespaced Roles over ClusterRoles, ServiceAccount per workload (not default), automountServiceAccountToken: false unless needed, OPA/Gatekeeper or Kyverno policies to forbid privileged pods and host mounts, NetworkPolicies as default-deny per namespace.

3.2Encryption in transit & at rest

References

GDPR Art. 32. Encryption is named explicitly as an example of an appropriate measure.

The ruleTLS 1.2+ everywhere, encrypt at rest by default, and hold the keys yourself (CMK), scoped per data class, not one key for everything.

Why we care

Encryption is the most legible control to regulators, and the breach-notification calculus under Art. 34 changes materially if data was strongly encrypted with keys the attacker did not have. It is also the easiest control to ship by default at the platform layer.

Key patterns

  • TLS floor: 1.2, prefer 1.3. Reject SSLv3, TLS 1.0, TLS 1.1 at load balancers, API gateways, service meshes, and database listeners. Use the cloud provider's "modern" or "intermediate" policy and pin it via IaC.
  • mTLS inside the cluster / between services. Treat the VPC as untrusted. Service mesh (Istio, Linkerd, AWS App Mesh) or SPIFFE/SPIRE issues short-lived workload certificates so every hop is authenticated and encrypted.
  • Envelope encryption for objects and records. Data encryption key (DEK) encrypts the payload; key encryption key (KEK) in KMS wraps the DEK. Rotate the KEK without re-encrypting petabytes; revoke access by revoking KEK use.
  • Encrypt at rest by default at the platform layer. Enforce via org policy: no unencrypted EBS volumes, no unencrypted S3 buckets, no unencrypted RDS instances. Use IaC modules that set this; reject PRs that don't.
  • Field-level / application-layer encryption for the highest-risk fields. National IDs, payment data, health data benefit from an extra envelope at the app layer so DB admins and backup operators cannot read plaintext.

Anti-patterns / gotchas

  • Terminating TLS at the load balancer and then sending plaintext HTTP over a "private" VPC. Internal subnets are not a control.
  • One KMS key for the entire account. Blast-radius of a key compromise or accidental deletion becomes total. Scope keys per data classification or per tenant.
  • Disabling certificate verification (InsecureSkipVerify, verify=False, --insecure) "just for staging" and shipping that into prod.
  • Forgetting backups, snapshots, and read replicas. Encryption of the primary does not encrypt a snapshot in a different region with default settings.

CMK vs BYOK vs HYOK

  • CMK (Customer-Managed Key): Key lives in AWS KMS, you control rotation, IAM, and deletion. Default recommendation for most workloads.
  • BYOK (Bring Your Own Key): You generate key material externally (HSM) and import it into KMS. Useful when policy requires you to attest to key generation.
  • HYOK / XKS (Hold Your Own Key / External Key Store): Key material never leaves your HSM; the cloud service calls out for each unwrap. Highest assurance, highest operational cost, latency and availability implications.
Cloud notes: AWS · K8s
  • AWS: ACM for public certs, ACM Private CA for internal, KMS with key policies + grants, S3 default encryption with aws:kms (SSE-KMS) or aws:kms:dsse, EBS default-encryption flag at the account level, RDS storage encryption (cannot be toggled post-create, so get it right at provision).
  • Kubernetes: Encrypt etcd with a KMS provider (AWS KMS), not just an aescbc key on disk. cert-manager + an internal issuer for workload TLS. Use a service mesh or SPIFFE for mTLS rather than rolling your own.

3.3Secrets & key management

References

GDPR Art. 32. Keys are the control that the encryption control depends on; losing them is losing the data.

The ruleSecrets live in a vault, are fetched at runtime via workload identity, and rotate on a schedule. Never in code, images, or CI variables.

Why we care

A leaked secret is the most common root cause of GDPR-relevant incidents. The fix is structural: secrets should be unguessable, short-lived, scoped, audited, and rotatable without code changes.

Key patterns

  • One source of truth per environment. AWS Secrets Manager or HashiCorp Vault. Pick per environment and route everything through it. No .env files in source control, no secrets in Helm values, no secrets in CI variables that are not also synced from the vault.
  • Short-lived, dynamic credentials over static. Vault dynamic database backends and AWS RDS IAM auth mean the app gets a fresh credential every connection or every hour, not a forever-password.
  • Workload identity to fetch secrets. Pods authenticate to the vault using their cluster identity (IRSA, EKS Pod Identity, Vault Kubernetes auth). No bootstrap secret on disk to fetch other secrets.
  • External Secrets Operator (ESO) or CSI Secrets Store. In Kubernetes, do not write secrets into Git (even encrypted-at-rest with SealedSecrets is a step down from a vault for rotation). ESO syncs from the cloud vault into Kubernetes Secrets with TTLs; CSI driver mounts them as files without persisting to etcd.
  • Rotate by policy, not by panic. Define rotation periods per secret class (e.g. DB creds 30 days, signing keys 90 days, OAuth client secrets 180 days). Automate via Secrets Manager rotation Lambdas or Vault's lease system. Track exceptions.

Anti-patterns / gotchas

  • Secrets in container image layers (ENV API_KEY=... or COPY .env). Once pushed, they are in registry history forever; rotate immediately if found.
  • "Encrypted" in Git via a single team-wide PGP key that everyone has. Functionally a shared password; revocation is impossible without rotating every secret.
  • Logging the secret on startup ("Connecting to DB with user=foo password=bar"). Common in libraries that pretty-print config; redact at log layer.
  • KMS keys with Decrypt granted to broad principals (e.g. an entire account root). The vault is only as strong as its IAM policy.
Cloud notes: AWS · K8s
  • AWS: Secrets Manager (with native rotation Lambdas) for credentials, SSM Parameter Store SecureString for config-shaped values, KMS for raw keys. IRSA for pods to read either.
  • Kubernetes: External Secrets Operator with a ClusterSecretStore per provider; Secrets Store CSI Driver for mount-only (no etcd); avoid kubectl create secret from a developer laptop into prod.

3.4Logging discipline

References

GDPR Art. 5(1)(c) data minimisation and Art. 32

The ruleLogs, traces, and metrics are personal data. Allow-list what you log, redact at the source, and set a retention tier on every sink.

Why we care

Observability stacks tend to be sprawling, multi-region, and shared. A stray log.info(user) can replicate personal data to a SaaS vendor in a third country within seconds. Logs are also where Art. 15 (subject access) and Art. 17 (erasure) requests get awkward, because they were not designed to be query-able by data subject.

Key patterns

  • Structured logging with PII tagging. Use JSON logs with explicit field schemas. Annotate fields that may carry personal data (user.email, request.headers.authorization) so a downstream processor can drop or hash them. Conventions: OpenTelemetry semantic conventions, pii: true custom attributes.
  • Scrub at the source, not just at the sink. Sink-side scrubbing is a backstop; build redaction into the logger/formatter so an outage in the scrubber does not leak. Libraries: pino redact paths, logback / log4j2 pattern converters, Python logging filters, Go zap field masks.
  • No request bodies, no full URLs with query strings, no headers by default. Allow-list what you log, do not deny-list. New endpoints inherit safe defaults.
  • Hash or pseudonymise identifiers when you need correlation. A salted HMAC of user-id is enough to correlate across services without storing the raw id in every log line. Rotate the salt on a schedule consistent with retention.
  • Separate retention tiers. Hot debug logs (7 to 14 days), audit logs (1 to 7 years depending on regulatory regime), security logs (often longer). Each tier has its own access policy and its own minimisation rules.

Anti-patterns / gotchas

  • "Just log the whole request for now, we'll filter later." It never gets filtered, and your APM vendor is now a sub-processor for full payloads.
  • Stack traces that include local variables (Sentry default, some Python frameworks). User objects, tokens, and DB rows end up in error reports. Configure scrubbing on the SDK and at the project level.
  • Trace attributes (http.request.body, db.statement with parameters bound). OpenTelemetry instrumentation will happily attach these unless you turn it off.
  • Metric labels with high cardinality identifiers (user-id, email). Beyond the cost issue, it is personal data in your metrics store, often replicated and long-retained.
Cloud notes: AWS · K8s
  • AWS: CloudWatch Logs data protection policies (managed audit + masking of PII patterns), Kinesis Firehose with Lambda transform for redaction before S3, log group KMS encryption, retention set per group via IaC.
  • Kubernetes: Sidecar or DaemonSet log shippers (Fluent Bit, Vector) with redaction processors before forwarding; never let stdout go straight to a third-party SaaS without a transform stage; turn off verbose audit policy levels in production.

3.5Consent capture & storage

References

GDPR Art. 7, plus Recital 32, Art. 8 for children's consent, and the ePrivacy Directive for cookies.

The ruleIf you rely on consent, keep an append-only ledger of who agreed to what, when, and the exact text shown. Make withdrawal as easy as granting, effective immediately.

Why we care

Consent is the legal basis that fails most often in audits and DPA fines because the engineering record does not match the marketing claim. The technical bar is high: a freely given, specific, informed, unambiguous indication, demonstrable on demand, withdrawable as easily as given.

Key patterns

  • Immutable consent ledger. Each consent event is a row with: subject id (or pseudonym), purpose id, policy version, timestamp (server-side, monotonic), source (IP / user agent / channel), method (checkbox / SMS / verbal-with-recording-id), and the verbatim text shown. Append-only; withdrawals are new rows, not updates.
  • Purpose and policy versioning. Each consent points to a specific version of a purpose definition and a specific version of the privacy notice. When the purpose changes materially, the old consent is no longer valid for the new use; re-prompt.
  • Propagation, not polling. When consent is withdrawn, push the change to downstream systems (CDP, ESP, ad platforms, ML feature stores) via events. Do not rely on consumers to poll a "current state" table; lags become violations.
  • Default to no. Until the consent event lands, the legal basis does not exist. Pipelines and tags must be off by default and switch on per signal, not the other way around.
  • Treat consent state as a first-class input to data pipelines. Joining a consent table at query time is fragile; materialise an "allowed-purposes" attribute on the subject and gate ETL on it.

Anti-patterns / gotchas

  • Storing only the latest state (marketing_consent = true). Loses provenance; cannot answer "what did the user agree to in March 2023?"
  • Capturing consent client-side and trusting the client. Always confirm server-side with an authenticated event.
  • Sharing one "marketing consent" flag across brands or jurisdictions in the same group. Each controller, each purpose, each jurisdiction may have different requirements.

Validity criteria (engineering-relevant)

  • Freely given - no pre-ticked boxes, no "consent or no service" unless service genuinely requires it.
  • Specific - one consent per purpose. "Analytics and marketing" is two consents.
  • Informed - link to the specific purpose, controller, retention, recipients at the point of capture.
  • Unambiguous - affirmative action. Continued scrolling is not consent (EDPB guidance).
  • Withdrawable - same number of clicks as granting; effective immediately and propagated.

Consent vs cookie banners (ePrivacy)

  • ePrivacy (the "cookie law," national transpositions of Directive 2002/58/EC) governs storing or reading information on a user's device: cookies, localStorage, fingerprinting, SDK identifiers. It requires consent for non-essential storage regardless of whether the data is personal under GDPR.
  • GDPR governs the subsequent processing of any personal data resulting from that storage.
  • Consequence: the banner is an ePrivacy requirement; the downstream consent record is also a GDPR record. They are linked but not identical. A "necessary cookies only" choice is an ePrivacy decision; the resulting analytics pipeline still needs a GDPR legal basis (usually legitimate interest with strict measurement scope, or it doesn't run).
  • Banner anti-patterns flagged by regulators: "Accept all" prominent, "Reject all" buried two clicks deep; cookie walls; legitimate interest pre-ticked alongside consent; dark patterns in colour and contrast.

Children & parental consent (Art. 8)

  • If under-16s can use the service (member states may set the floor as low as 13, so check the jurisdiction), consent for information-society services must come from a holder of parental responsibility, not the child.
  • A workable pattern: an age gate at signup → if under the threshold, capture consent via a linked parental account whose email is verified and must differ from the child's. The parent grants, sees, and can withdraw consent; record it against the parental identity.
  • Art. 8 requires reasonable efforts to verify the parent given available technology. A verified, distinct parental email clears that bar far better than a self-declared birthday alone.
Cloud notes: AWS · K8s
  • AWS: DynamoDB or Aurora for the ledger with point-in-time recovery, EventBridge for fan-out to downstream consumers, S3 Object Lock for immutable archival of consent receipts.
  • Kubernetes: The consent service is high-criticality, low-latency, and on the request path; treat it like auth: multi-AZ, cached at the edge with strict TTLs (cache invalidation is the hard part on withdrawal; prefer push invalidation over time-based).

3.6Vendor / sub-processor controls

References

GDPR Art. 28, with onward references to Chapter V (Arts. 44-49) for international transfers.

The ruleEvery vendor touching personal data needs a DPA on file before traffic flows, a place on your inventory, and an exit path. Treat LLM APIs as sub-processors.

Why we care

Every SaaS dependency a build pipeline adds is a potential processor or sub-processor. The legal contract is the controller's job; surfacing the technical reality (what was added, where it processes, what it touches) is engineering's job, and it is increasingly automated.

Key patterns

  • Vendor inventory as code. A machine-readable catalog (Backstage, a Git repo of YAML, a CMDB) of every external service that receives personal data, with: legal entity, processing location(s), data categories, purpose, DPA status, sub-processor list URL, last review date. Wire into CI so adding a new dependency requires updating the inventory.
  • DPA on file before traffic flows. Network egress to a new vendor without a signed DPA (or SCCs / adequacy decision for transfers) is a procurement violation and a build-time policy violation. Block at the egress proxy or service mesh until cleared.
  • Sub-processor change notifications. Track the vendor's sub-processor page (most major SaaS publish one). Subscribe via RSS, email, or scrape; route notifications to the DPO or privacy ops. Your DPA usually gives you a window (often 30 days) to object.
  • Egress controls and SNI logging. Engineers add vendors faster than procurement can keep up. An egress proxy (Squid, mitmproxy in dev, cloud-native NAT gateway flow logs, Cilium FQDN policies) gives you the ground truth of which domains your workloads talk to.
  • Re-evaluate on each renewal and on each major architecture change. A vendor that processed pseudonymous events last year may now process plaintext PII because your team added a field. The DPA does not auto-update.

Anti-patterns / gotchas

  • Free-tier SaaS used "just for staging" with real production data copied in. The free tier almost never has a DPA; staging data is production data if it contains real people.
  • "We have an MSA" is not a DPA. The Art. 28 clauses must be explicit; check for the specific elements (subject matter, duration, nature and purpose, type of personal data, categories of data subjects, controller obligations and rights).
  • One-time vendor review at onboarding with no re-review. Vendors change ownership, hosting, and sub-processors; an annual sweep is the floor.
  • Browser-side third-party scripts (chat widgets, analytics, A/B testing) added by marketing without engineering visibility. They are processors too, often loaded before consent, often in third countries.

LLM / AI vendor specifics (generic)

  • Confirm at procurement whether prompts and completions are used to train the vendor's models, and the opt-out mechanism. Default assumptions change frequently; verify the current contractual terms for your tier (free vs paid vs enterprise often differ).
  • Confirm retention of prompts and outputs on the vendor side, abuse-monitoring retention separately, and whether enterprise zero-retention modes exist.
  • Confirm processing location and whether it can be pinned to the EEA. Many LLM APIs route requests globally by default; regional endpoints may exist but require explicit selection.
  • Treat the LLM provider as a sub-processor in your Art. 30 record. Update the controller's external privacy notice if end-user data flows there.
  • Pseudonymise inputs where feasible. Strip direct identifiers before the prompt; rejoin downstream. Useful even where a DPA is in place, as a defence-in-depth and a minimisation argument.
  • Log prompts and completions on your side with the same discipline as the rest of section 3.4. They are easy to over-collect because they look like "just text."
  • Track model versions. A model deprecation or behaviour change can be a material processing change worthy of DPIA review under Art. 35.
Cloud notes: AWS · K8s
  • AWS: VPC Flow Logs + Route 53 Resolver query logs to discover egress destinations; AWS Network Firewall with stateful FQDN rules to allow-list vendors; AWS Artifact for the AWS-side DPA and audit reports.
  • Kubernetes: Cilium or Calico FQDN-based egress policies; service mesh egress gateways with explicit ServiceEntry (Istio) or equivalent; admission controller policy that forbids NetworkPolicy-bypassing pods (hostNetwork, privileged).

Cross-cutting build-time checklist (attach to PR templates)

A condensed list to attach to PR templates and IaC module READMEs. Each item maps back to a section above; failing one is not automatically a GDPR violation, but it is a question you will have to answer.

  • No * in IAM actions or resources on this change (3.1)
  • No standing human access to production personal data added (3.1)
  • Workload identity used; no static cloud keys introduced (3.1, 3.3)
  • TLS 1.2+ enforced on every new listener, mTLS where in-cluster (3.2)
  • All new data stores encrypted with a CMK scoped to the data class (3.2)
  • No secrets in code, images, Helm values, or CI variables (3.3)
  • Secret rotation policy defined for any new secret (3.3)
  • No personal data in new log lines, traces, or metric labels (3.4)
  • Log retention configured explicitly on any new log group / workspace (3.4)
  • If consent is the legal basis: ledger writes are server-side, versioned, append-only (3.5)
  • If consent withdrawal is supported: downstream propagation tested (3.5)
  • New external vendor: inventory updated, DPA confirmed, egress allow-listed (3.6)
  • New LLM/AI dependency: training opt-out, retention, region verified at procurement (3.6)

Where this part hands off

  • To Part 4 (Operate): rotation schedules, log-retention enforcement, vendor re-review cadence, consent-withdrawal SLAs.
  • To Part 5 (Respond): the audit trails (access logs, consent ledger, KMS key usage) that you built here are the evidence base for breach forensics and data-subject requests.
  • Back to Part 2 (Design): if a build-time control is impossible (e.g., a chosen vendor cannot meet residency), escalate to a design review rather than waive the control.

At a glance

6  control areas to ship

Art. 32  technical & organisational measures

Art. 7  consent: demonstrable, withdrawable

The series

Part 3 of 5

Foundations → Design → Build → Operate → Respond. This is where the design decisions become code, IaC, and the audit trails everything downstream relies on.