Encryption & compliance
Overview & shared responsibility
Vaks PM is a project-management web application designed to run entirely on infrastructure the organization controls (on-premise: on its own servers rather than on a third-party online service). Encryption is applied at four independent layers, each protecting a different surface:
- Application fields. A narrow set of sensitive columns (for example email-relay passwords, multi-factor seeds and single-sign-on credentials) are encrypted inside the database by the application itself, using envelope encryption with per-tenant keys.
- Data-store volumes. The disks that hold the database and the object store are encrypted as a whole, so the entire dataset (not just the few secret fields) is protected at rest.
- In transit. Traffic is encrypted at the public edge (HTTPS) and on the internal hops between the application and its dependencies.
- Backups. Backup artifacts are encrypted before they leave the host.
- The product provides, on by default: field-level envelope encryption of secrets, the public-edge TLS termination at the reverse proxy, the append-only tamper-evident audit log, and the data-protection (GDPR) features.
- The product provides, opt-in / configurable: internal TLS between services, the choice of key-custody provider (local key ring, cloud KMS, on-prem HSM), and key rotation controls.
- The operator must provide from the infrastructure: at-rest volume encryption of the data disks, secure custody and escrow of long-lived keys off-cluster, and (on Kubernetes) datastore-secret encryption and pod-network encryption.
Glossary
Acronyms and terms used throughout this guide, defined once here for reference.
| Term | Meaning |
|---|---|
| DORA | Digital Operational Resilience Act: EU Regulation 2022/2554, which requires regulated financial entities to manage their information-and-communications-technology (ICT) risk, including encryption of data at rest and in transit and a documented key-management policy. |
| GDPR | General Data Protection Regulation: EU Regulation 2016/679 on the protection of personal data. |
| KEK | Key Encryption Key: a master key whose only job is to encrypt ("wrap") other keys; it never touches the data itself. |
| DEK | Data Encryption Key: the key that actually encrypts the data. Vaks PM uses one DEK per tenant. |
| Envelope encryption | A two-layer scheme in which a DEK encrypts the data and a KEK encrypts the DEK. Changing the KEK re-wraps the small DEK without touching the bulk data. |
| AES-GCM | Advanced Encryption Standard, Galois/Counter Mode: an authenticated symmetric cipher, used here as AES-256-GCM (256-bit key) for field and key data. |
| AES-XTS | The AES mode designed for disk-sector (block-device) encryption; used by LUKS as aes-xts-plain64 with a 512-bit key. |
| KMS | Key Management Service: a managed (typically cloud) service that holds keys and performs encrypt/decrypt without exporting the key. |
| HSM | Hardware Security Module: a tamper-resistant hardware device that holds keys and performs cryptographic operations on-premise. |
| PKCS#11 | A vendor-neutral standard programming interface for talking to an HSM or smart card. |
| LUKS | Linux Unified Key Setup: the standard Linux full-volume disk-encryption format; version 2 (LUKS2) is used here. |
| Clevis | A Linux framework that automatically unlocks an encrypted volume at boot by fetching key material from a network policy, with no human passphrase entry. |
| Tang | A small stateless network server that participates in Clevis "network-bound disk encryption": a volume can unlock only while it can reach the Tang server(s). |
| SSS | Shamir's Secret Sharing: a scheme that splits a secret into shares so that any threshold number of them reconstructs it. Clevis uses it to require, say, 1-of-N reachable Tang servers. |
| mTLS | mutual TLS: Transport Layer Security where both ends present a certificate and verify the other's. |
| TLS | Transport Layer Security: the encryption layer behind HTTPS and behind encrypted service-to-service connections. |
| CA | Certificate Authority: the entity that issues and signs X.509 certificates; its certificate is the trust anchor used to verify peers. |
| CSR | Certificate Signing Request: a file holding a public key and requested attributes, submitted to a CA to be signed into a certificate. |
| SAN | Subject Alternative Name: the certificate field listing the IP addresses and DNS names the certificate is valid for. |
| age | A modern file-encryption tool using asymmetric keys: a public "recipient" encrypts, a matching private key decrypts. Used for backup artifacts. |
| RPO | Recovery Point Objective: the maximum acceptable amount of data loss, measured as time, after an incident. |
| PII | Personally Identifiable Information: personal data such as names and email addresses. |
| Tenant | One isolated organization served by the instance. Each tenant has its own database and its own DEK. |
DORA control mapping
DORA (the Digital Operational Resilience Act, EU Regulation 2022/2554) and its supporting technical standards are risk-based rather than prescriptive of any specific product. They require encryption of data at rest and in transit commensurate with data classification, plus a key-management policy covering the key lifecycle, rotation and separation of duties. A third-party HSM or KMS is not mandatory for compliance.
| DORA expectation | How Vaks PM addresses it | Status |
|---|---|---|
| Encryption of data at rest: application secrets | AES-256-GCM envelope encryption: a per-tenant DEK encrypts the sensitive fields and is itself wrapped by a KEK. Single code seam in the application. | Built-in |
| Encryption of data at rest: the whole data store | LUKS2 (aes-xts-plain64, 512-bit) volumes for the database and the object store, with network-bound key release (Clevis + Tang). On Kubernetes: an encrypted StorageClass delegated to the platform. |
Operator-enabled |
| Encryption in transit: external | TLS at the reverse proxy for all client traffic (HTTPS). | Built-in |
| Encryption in transit: internal | App↔database TLS; coordination-store mutual TLS; encrypted overlay/pod network covering the cache, object store and internal HTTP. | Operator-enabled |
| Key management: lifecycle & rotation | Versioned KEK key ring; lazy KEK re-wrap and admin-triggered rotation; DEK rotation re-encrypts all sensitive columns. See rotation and the runbook. | Built-in |
| Key management: separation of duties | The crypto:manage permission gates all key operations and every operation is audited. Hard custody (the KEK held outside the application) is available by moving the KEK to a cloud KMS or an on-prem HSM (PKCS#11); see Key custody providers. |
Control + KMS/HSM |
| Encrypted backups | Backup artifacts are encrypted with age (asymmetric) at creation; the host holds only the public recipient. |
Operator-enabled |
| Integrity / tamper-evidence of logs | Append-only audit log with a daily chained integrity anchor and a verification endpoint. | Built-in |
Data classification & encryption layers
Not all data is protected the same way. The application encrypts a small set of secrets at the field level; the bulk of the data (business data and personal data) relies on volume and transit encryption, because it must stay queryable. The table below shows which data sits at which classification and which layer protects it.
| Data | Classification | Layer(s) |
|---|---|---|
| Multi-factor (TOTP) seeds, email-relay (SMTP) password, single-sign-on (OIDC/SAML) client secrets, external ticketing-connector secrets, integration-provisioning secrets | Secret | Field-level (envelope) + volume + in-transit |
| API token material | Secret | Stored as a SHA-256 hash (never reversible) + volume |
| User passwords | Secret | Argon2 hash (never reversible) + volume |
| Personal data (names, emails, time entries, comments, attachments) | Confidential | Volume (at rest) + in-transit; attachments in the encrypted object store |
| Project / business data | Confidential | Volume + in-transit |
| Audit log | Confidential | Volume + in-transit; integrity-anchored (tamper-evident) |
Envelope encryption (KEK → DEK → data)
The application encrypts its sensitive fields with envelope encryption, a two-layer key scheme:
- A DEK (Data Encryption Key), AES-256-GCM, 32 bytes, encrypts the actual fields. There is one DEK per tenant; all of that tenant's secrets are encrypted under its single DEK. The DEK is stored in the tenant database only in wrapped (encrypted) form, never in clear.
- A KEK (Key Encryption Key), AES-256-GCM, 32 bytes, encrypts ("wraps") the DEK. The KEK never touches the data itself; its only job is to lock and unlock DEKs. It lives outside the database, as a mounted key file or in an external KMS/HSM.
To read a secret, the application unwraps the tenant's DEK with the KEK, then decrypts the field with the DEK. To write one, it does the reverse. The diagram shows the relationship.
Step-by-step operational detail of the local key ring (generating and activating a KEK, the mounted key material and rotation) lives in the deployment guides: Docker Swarm and Kubernetes.
Key custody providers
The KEK is supplied by a key provider, selected by the CRYPTO_KEY_PROVIDER setting. Three providers exist; each stored key records which provider wrapped it, so providers can coexist during a migration. The default, the local key ring, is fully DORA-compliant on its own; the external providers add hard separation of duties, where no application administrator can export the key.
| Provider | Where the KEK lives | Separation of duties | When to use |
|---|---|---|---|
| Local key ring default | A mounted key file on the hosts (kek_v<N>, base64, 32 bytes), delivered as a Docker secret (Swarm) or a Kubernetes Secret. |
Soft: gated by crypto:manage and audited; a global admin can still reach key operations. |
The default. No external dependency; fully compliant. |
| Cloud KMS | A managed cloud key service (AWS KMS / GCP KMS / Azure Key Vault or compatible). The KEK never leaves the KMS; every wrap/unwrap is performed by the service. | Hard: the application cannot export the key. | The deployment runs in or alongside a cloud with a managed KMS. |
| On-prem HSM | A PKCS#11 hardware device, reached through an internal key-broker sidecar that holds the HSM PIN. The KEK is a non-extractable hardware key. | Hard: the key is non-extractable hardware. | Procurement requires hardware key custody, or an HSM is already operated. |
crypto:manage) is a control hook, not hard custody separation. Hard separation, where the custodian holds the KEK outside the application and it can no longer be exported, means moving the KEK to a cloud KMS or an on-prem HSM. Switching provider is an online re-wrap: no data is re-encrypted.
The provider configuration variables (CRYPTO_KMS_*, CRYPTO_HSM_*), the HSM key-broker sidecar, and the per-orchestrator placement of these settings are configured in the deployment guides: Kubernetes and Docker Swarm.
Migrating custody
Because each stored key records the provider that wrapped it, a migration between providers is online and reversible: no downtime, no data re-encryption. The outline:
- Configure the new provider and set
CRYPTO_KEY_PROVIDERtokmsorhsm, then roll the services. New DEKs are now wrapped by the new provider; existing ones still unwrap via their current provider, so a mix is expected and correct. - In Admin → Security & Compliance → Encryption keys, click Rotate KEK. Every DEK is unwrapped with its current provider and re-wrapped under the new one. Repeat per tenant.
- Watch the data-key distribution until every key shows the new provider's scheme, then run a decrypt check (sign in, or open a settings page that reads an encrypted value).
- Reversible: keep the previous provider's key material available until verified. Rollback is setting
CRYPTO_KEY_PROVIDERback and re-running Rotate KEK.
Key rotation
Two distinct rotations exist, with very different costs. Both are driven from Admin → Security & Compliance → Encryption keys and require the crypto:manage permission; both are written to the audit log.
| Operation | What it does | Cost | When |
|---|---|---|---|
| Rotate KEK (lazy re-wrap) | Stages a new KEK version, activates it, and re-wraps every per-tenant DEK under it. The DEK is unchanged; no field data is re-encrypted. | Fast, no downtime: only tiny DEKs are touched. | On a schedule (annually is a reasonable default) or immediately on suspected KEK compromise. |
| Rotate DEK (re-encryption) | Mints a new active DEK for the tenant and re-encrypts every sensitive column under it, then retires the old DEK. The operation is resumable: if interrupted, running it again completes the migration. | Heavier; touches all of the tenant's encrypted fields. | On suspected exposure of a data key, or per policy. |
Rotation & incident runbook
The response to a suspected key compromise depends on which key is involved. Record every incident; the audit log evidences each key operation with actor, time and outcome.
| Compromise | Response |
|---|---|
| KEK exposed | Rotate the KEK immediately (re-wrap), then rotate the DEKs so the new key material is independent of the exposed KEK. Do not delete the old KEK until every backup predating the rotation has aged out. |
| DEK exposed | Rotate the affected tenant's DEK (re-encrypt). The previous DEK is retired and purged once re-encryption completes with zero failures. |
| TLS / JWT secret exposed | Rotate the secret. Rotating the session-signing (JWT) secret invalidates live sessions, so users re-authenticate. For internal TLS, revoke the certificate at the CA, issue a new one and re-apply the TLS procedure. |
Backup age key exposed |
Generate a new age key pair; subsequent backups use the new recipient. Older artifacts remain encrypted to the old key, so destroy them per retention. |
Volume encryption at rest
Field-level encryption protects only the secrets. To protect the entire dataset on disk (the database files and the object-store files) the data volumes themselves are encrypted. This is the layer that the operator provides; the approach differs by orchestrator.
Docker Swarm: LUKS2 with network-bound unlock (Clevis + Tang)
On the self-managed Swarm topology, the bootstrap provisions LUKS2 file-backed volumes for the data stores. Two volumes are created:
- the database data volume, mounted where PostgreSQL keeps its data (so it is encrypted before the database initializes into it);
- the object-store volume, mounted where the S3 server keeps its objects.
Each volume is formatted with cipher aes-xts-plain64 and a 512-bit key. Only the data volumes are encrypted, not the host OS disk and not other workloads.
The challenge with at-rest encryption is unlocking the volume at boot without a human typing a passphrase. Vaks PM solves this with network-bound disk encryption using Clevis and Tang:
- A Tang server is a tiny, stateless network key server (it holds no per-client secret). On each host it listens on an internal-network port; a host's volumes are bound to the Tang servers running on its peer hosts (never itself).
- Clevis binds the LUKS volume to a policy. The policy uses Shamir's Secret Sharing (SSS) with a threshold of 1 across the peer Tang servers: the volume unlocks automatically as long as at least one peer Tang server is reachable.
- At boot, a small unlock-and-mount helper runs before the database (and before Docker for the object store), waits for a peer Tang server, unlocks the volume via Clevis and mounts it. The application services then start on top of an already-mounted, decrypted volume.
The exact bootstrap commands (installing Tang, building the Clevis SSS policy, formatting and binding each volume, and the ordering relative to the database start-up) are in Docker Swarm deployment. This section describes what those steps do and the policy behind them.
Kubernetes: encrypted StorageClass
On Kubernetes the equivalent is an encrypted StorageClass, delegated to the platform: the database, cache and object-store persistent volumes are provisioned from a class backed by the cloud provider's key-managed block storage, a Container Storage Interface (CSI) driver with encryption, or nodes with encrypted disks. When the dependencies are external/managed (for example a managed database service), use that provider's own at-rest encryption instead. This is an install-time choice: changing it on a running cluster means migrating volume data. See Kubernetes deployment and the encryption-layer detail it links.
Internal TLS & custom CA
Encryption covers the public edge (HTTPS at the reverse proxy) and the product's internal traffic as well. Three internal flows are encrypted:
| Flow | Protection | Certificate |
|---|---|---|
| App ↔ PostgreSQL (and replication between database nodes) | PostgreSQL TLS | Server certificate |
| Cluster coordination store (consensus + failover-manager access) | mutual TLS (mTLS) | Server + client certificate |
| Container overlay / pod network (cache, object store, internal HTTP) | Symmetric-key encryption managed by the orchestrator | None (no certificate) |
A fresh install runs internal TLS out of the box with self-signed certificates: traffic is encrypted, but the peer's identity is not verified. Replacing those with certificates issued by the organization's own CA (Certificate Authority) adds identity verification, which protects against an active man-in-the-middle.
Certificate specification (the key points)
- One shared certificate can cover the internal components, with its SAN (Subject Alternative Name) listing all host IP addresses (Swarm) or the database service name (Kubernetes), plus
127.0.0.1/localhost. The legacy Common Name field is no longer honored, so populating the SAN is mandatory. - Extended Key Usage must be both
serverAuthandclientAuth. The coordination store uses mTLS (each node is both a server, since it listens, and a client, since it calls its peers), so a server-only certificate is rejected. This is the single most common mistake: corporate CAs often issue server-only certificates. - Key type RSA ≥ 2048 bits or ECDSA P-256; SHA-256 minimum signature; validity per the CA's policy (plan for rotation before expiry).
From CSR to deployment
Produce a CSR (Certificate Signing Request) carrying the SAN and the dual EKU, have the internal CA sign it (requiring that it preserves the SAN and EKU and returns the chain), and deploy three PEM files: the leaf certificate, its private key (unencrypted, 600 permissions, owned by the service user), and the CA certificate used as the trust anchor. On Swarm the files go on each host under /etc/vaks/ and the trust anchors are re-pointed at the CA one node at a time, to preserve availability. On Kubernetes the certificate is supplied as a Secret (or issued by cert-manager) and the application verifies the database with PG_TLS / PG_TLS_CA / PG_TLS_MODE.
The per-orchestrator file placement, the supply of a custom Certificate Authority and the verification commands are covered in the deployment guides: Docker Swarm and Kubernetes.
GDPR: erasure & portability
Encryption protects data confidentiality; GDPR (the EU General Data Protection Regulation) governs how personal data is handled. Vaks PM implements two relevant data-subject rights as product features:
- Right to erasure (Art. 17) by pseudonymization. Deleting a person's records would break the time, finance and audit aggregates that reference them, so instead the product overwrites the personal data (name, email and other identifiers are replaced with a non-identifying pseudonym) while keeping the foreign keys intact. The PII is gone; the historical aggregates survive and the audit log's integrity is unaffected. The operation is idempotent and audited.
- Right to data portability (Art. 20) by self-export. A user can export their own data (profile, time entries, allocations, comments, skills and attachment metadata) as a structured file; secrets are excluded. An administrator can perform the export on a user's behalf.
These features and their administration are described in the product guide: see Product: GDPR (anonymization & portability).
Encrypted backups
Backup artifacts are encrypted at creation with age, an asymmetric (public-key) scheme: a public recipient key sits on the host and seals each artifact, while the matching private key is held in escrow off-cluster. The host can therefore create encrypted backups but cannot, on its own, decrypt them.
Because the KEK lives outside the database, a database backup contains only the wrapped per-tenant DEKs plus ciphertext. Restoring it works only if the key ring still holds the KEK that wrapped those DEKs. Two coordination rules follow:
- Back up the key material separately from the database (the KEK material and the backup
ageprivate key), with retention at least as long as the database backups that reference them. A database backup alone cannot be decrypted without its keys. - Snapshot a key inventory alongside each backup set. Record which KEK the DEKs were wrapped under and which
agerecipient the artifacts were sealed to, so a future operator knows exactly which keys a given backup needs.
age recipient configuration and the restore procedure are in Operations.
Lock-out recovery & escrow
age private key, renders the corresponding data unrecoverable. This is by design. The mitigation is escrow: keep a copy of every long-lived key in an off-cluster vault with audited access and retention ≥ the data it protects.
The escrow inventory every operator must maintain off-cluster:
| Key | Protects | Why it must be escrowed |
|---|---|---|
KEK material (every kek_v<N> still referenced, plus the legacy master key) | The wrapped DEKs → all field-encrypted secrets | The KEK lives outside the database; a database backup cannot be decrypted without it. A from-scratch DR cluster generates fresh secrets, so the original KEK must be transplanted to the DR site. |
| LUKS recovery passphrase (per data volume) | The at-rest data volumes | The only way to unlock a volume when no peer Tang server is reachable (cold start / Tang outage). |
Backup age private key | The encrypted backup artifacts | The host holds only the public recipient; without the private key the backups cannot be decrypted at restore time. |
| Internal TLS private key + CA | Internal in-transit verification | Needed to re-establish verified internal TLS on a rebuilt cluster. |
age private key) to the DR site before declaring the restore complete; otherwise business data restores but the encrypted secrets (MFA, SMTP, SSO) are unrecoverable. Rehearse a full DR restore on a test site at least annually.
Verification checklist
Confirm each layer is active before relying on it:
- Field-level encryption.
GET /admin/crypto/status(or the admin panel) returns the active KEK provider, the active KEK and the distribution of DEKs by KEK. After a rotation, confirm every DEK references the intended KEK. The application start-up log also lists the loaded key ring and the active key. - Decrypt check. Sign in, or open an admin settings page that reads an encrypted value (for example the notification / SMTP settings), to confirm secrets decrypt end to end.
- Volume encryption (Swarm). The data volumes appear as LUKS-mapped devices and unlock automatically at boot via Clevis/Tang; confirm the unlock-and-mount unit ran before the database started.
- Volume encryption (Kubernetes). The persistent volumes are bound to an encrypted
StorageClass; confirm the class in use. - External edge TLS. The organization's host name loads over HTTPS with a valid certificate.
- Internal TLS. The database reports
ssl onand shows encrypted connections; the coordination store reports healthy; with a custom CA,openssl verifyagainst the database returns code 0. - Backups. Backup artifacts are encrypted to the configured
agerecipient and shipped off the cluster; a restore drill succeeds against the escrowed keys. - Audit integrity. The daily integrity anchor verifies; the verification endpoint reports no tampering.