NIS2 Compliance#
This documentation applies to Piler enterprise edition 2.1+
Revision #1
Publication date: 2026-MAR-16
Database Immutability for NIS2 Compliance (MySQL/MariaDB/Percona)#
None of the three offer a native WORM (Write Once Read Many) storage engine, but true immutability can be achieved through a layered approach. Here's what's practical:
Layer 1: Database Triggers (Most Effective Native Approach)#
Block modifications at the DB engine level regardless of who connects:
-- Prevent updates on archived messages
CREATE TRIGGER prevent_update_metadata
BEFORE UPDATE ON metadata FOR EACH ROW
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Immutable: updates not permitted on archived messages';
CREATE TRIGGER prevent_delete_metadata
BEFORE DELETE ON metadata FOR EACH ROW
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Immutable: deletes not permitted on archived messages';
Note: Piler's deletion flow (
internal/message/remove.go) only UPDATEsmetadata.retainedand INSERTs into thedeletedtable — it never hard-deletes rows frommetadata,attachment, orrcpt. So theprevent_delete_*triggers are compatible with the current codebase. Theprevent_update_metadatatrigger would conflict with theretainedfield update, so skip it or limit it to specific columns if your DB version supports column-level trigger conditions.
Apply to all critical tables: metadata, attachment, rcpt, etc.
Caveat: A DBA with SUPER or TRIGGER privilege can drop triggers. Mitigate by:
- Restricting who can DROP TRIGGER (revoke from app users)
- Auditing DDL changes (see Layer 3)
Layer 2: Privilege Separation#
Important caveat for Piler: Blanket revocation of UPDATE/DELETE is not feasible because:
- Admin functionality (user management, settings, etc.) legitimately requires
UPDATE/DELETEon non-archive tables - Even the message removal flow in Piler does not hard-delete archive tables — it uses a soft-delete pattern:
INSERT INTO deleted ...+UPDATE meta SET retained=?. The only hardDELETEis against the Manticore search index, not MySQL metadata.
Piler uses a single MySQL user (DSN in .env) for all operations, and admin functionality legitimately requires UPDATE/DELETE on non-archive tables. The practical approach is to rely on triggers (Layer 1) for archive table protection rather than privilege revocation.
The soft-delete pattern Piler already uses is inherently compliance-friendly: archive metadata is never hard-deleted from MySQL, only flagged in the deleted table.
Layer 3: Audit Logging#
All three databases support audit plugins:
| Database | Plugin | Cost |
|---|---|---|
| MariaDB | server_audit (built-in, free) |
Free |
| Percona | audit_log plugin |
Free |
| MySQL Enterprise | MySQL Enterprise Audit | Commercial |
-- MariaDB example
INSTALL PLUGIN server_audit SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
SET GLOBAL server_audit_file_path = '/var/log/mysql/audit.log';
Audit logs themselves must be write-protected (send to syslog/SIEM, not local-only).
Layer 4: Binary Logging + Read-Only Replica#
# my.cnf
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW # Records actual row changes
expire_logs_days = 0 # Never auto-purge
sync_binlog = 1 # Durable writes
A downstream read-only replica serves as an independent integrity witness — any tampering on the primary that somehow bypassed triggers would be visible in binlog divergence.
Layer 5: Storage-Level WORM (Strongest Guarantee)#
For the actual email files Piler stores in /var/piler/store:
- S3 Object Lock (if using MinIO/AWS S3): Compliance mode prevents deletion even by root
- NetApp SnapLock / Dell EMC DataDomain: Hardware WORM
- ZFS snapshots: Immutable point-in-time snapshots with
zfs set readonly=on
This is the most auditor-friendly approach since it's enforced below the application layer.
Layer 6: Row Integrity Checksums (Optional — for extra paranoid setups)#
This is not recommended for most deployments. Piler already stores a digest column in metadata — a cryptographic hash of the email content computed at archive time — which covers content integrity. A row-level hash adds marginal benefit at the cost of ~64 bytes per row and slower verification queries at scale.
If you still want it:
ALTER TABLE metadata ADD COLUMN row_hash CHAR(64) AS (
SHA2(CONCAT(id, piler_id, message_id, digest, `from`, arrived), 256)
) STORED;
MySQL/MariaDB STORED generated columns are computed automatically at INSERT time with no application changes required. Periodic verification can then detect any out-of-band row tampering that bypassed triggers and audit logging.
Beyond Database Immutability#
Multi-Factor Authentication (NIS2 Art. 21.2.j)#
NIS2 explicitly requires MFA for access to network and information systems. Piler supports TOTP (6-digit, 30s window) and WebAuthn/passkeys.
Enforcing MFA for admin accounts: Piler has a totp_blocked flag per user. When set, the user cannot complete login without passing TOTP verification. Set this for all admin and auditor accounts:
-- Force MFA for all admin and auditor accounts
UPDATE user SET totp_blocked = 1 WHERE isadmin = 1 OR role_id IN (1, 2, 4, 5);
Enable MFA support in .env:
ENABLE_AUTHENTICATOR=true
ENABLE_PASSKEY=true # optional WebAuthn
Regular users accessing sensitive archives should also be encouraged (or required) to enroll.
Encryption in Transit (NIS2 Art. 21.2.h)#
All communication channels must be encrypted. Piler supports TLS across the board — ensure it is actually configured:
Web UI: Deploy behind a reverse proxy (nginx/Caddy) with a valid TLS certificate. Never expose Piler's HTTP port directly.
Database connection (.env):
MYSQL_USE_TLS=true
MYSQL_TLS_CA_CERT=/path/to/ca.pem # for self-signed CA
MYSQL_TLS_SKIP_VERIFY=false # never true in production
LDAP:
LDAP_USE_TLS=true
SMTP ingest:
SMTP_USE_TLS=true
S3/MinIO:
S3_USE_SSL=true
Audit Log Integrity (NIS2 Art. 21.2.b)#
Piler's application-level audit events are stored in Manticore on the same server. For NIS2, audit logs must be protected against tampering by a compromised administrator.
Enable SIEM offloading — Piler supports a SIEM exporter (internal/audit/logger.go). Configure it to ship audit events off-system in real time so a local compromise cannot erase the trail.
Additionally, enable the MariaDB database-level audit plugin to capture SQL-level events (see Layer 3 above) and ship those to syslog/SIEM as well.
Password Policy (NIS2 Art. 21.2.i)#
Piler stores passwords as bcrypt hashes but enforces no complexity or rotation policy for local accounts.
Recommended approach: - Use LDAP/Active Directory or SAML as the authentication backend — password policy is then enforced by the directory service, which typically supports minimum length, complexity, expiry, and lockout - If local auth is required, enforce a strong password at the identity provider level or implement an external password policy tool - MFA (see above) compensates for weak password policies
Retention vs. GDPR Right to Erasure#
NIS2 requires data retention for incident investigation; GDPR Art. 17 requires erasure on request. These can conflict. Piler resolves this correctly:
- A GDPR erasure request is blocked while a legal hold is active on the custodian
- Only after the legal hold is released can the GDPR request proceed to approval and execution
- A signed deletion certificate with SHA256 hash is generated upon execution for legal records
This means compliance officers can safely run both frameworks simultaneously without risk of destroying evidence under hold.
Incident Notification (NIS2 Art. 23)#
NIS2 requires a 24-hour early warning and 72-hour full notification to the competent authority after becoming aware of a significant incident.
Piler's audit logs (login events, failed logins, message access, deletions) provide the forensic trail needed to: - Establish the timeline of a breach - Identify affected data subjects and scope - Demonstrate what was accessed or exfiltrated
Ensure audit logs are retained for at least 12 months (the NIS2 recommended minimum) and are accessible for incident response without depending on the potentially compromised system.
NIS2 Mapping#
| NIS2 Art. 21 Requirement | Piler Control |
|---|---|
| Data integrity (Art. 21.2.e) | DB triggers + soft-delete + digest column |
| Cryptography (Art. 21.2.h) | TLS for all channels, AES-256 for stored emails |
| Access control (Art. 21.2.i) | RBAC (6 roles, 7 permissions), LDAP/SAML integration |
| MFA (Art. 21.2.j) | TOTP + WebAuthn, enforceable per user |
| Audit trails (Art. 21.2.b) | Application audit log + DB audit plugin + binlog |
| Availability (Art. 21.2.c) | Replication + backups (see BACKUP-DISASTER-RECOVERY.md) |
| Legal hold / evidence preservation | Legal hold feature blocks GDPR erasure |
| Incident notification (Art. 23) | Audit logs provide forensic timeline |
Recommendation for Piler specifically#
- Triggers on
metadata,attachment,rcpttables to block hard DELETEs — compatible with Piler's existing soft-delete flow - Enable MariaDB
server_auditplugin — audit all DML/DDL, ship to syslog/SIEM - Enable binary logging with row format, ship to a SIEM or read-only replica
- Enforce MFA (
totp_blocked=1) for all admin, auditor, and data officer accounts - Configure TLS for DB, LDAP, SMTP, and S3 connections — do not rely on defaults
- Use LDAP/SAML for authentication to inherit enterprise password policies
- Configure SIEM offloading for Piler's application audit log
- S3 Object Lock if using MinIO/S3 storage (strongest compliance story for email files)
The combination of triggers + audit logging + MFA enforcement + TLS is what auditors typically accept as NIS2-compliant controls for an email archive. True hardware WORM is the gold standard but the above is defensible and practical.