Host hardening¶
PEART defends its own surfaces, but some controls are the host's and cannot be imposed by the application. These are the ones the operator owns.
The sandbox posture PEART is supported under¶
The three shipped units run under systemd's sandboxing, and PEART is supported with all of it on. You do not have to relax a directive to make the product work. If you find that you do, that is a defect worth reporting rather than a drop-in worth writing. What each of the four that were found this way printed, and what holds each correction in place, is in When a hardening directive is the cause.
All three units carry NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes,
PrivateDevices=yes, ProtectKernelTunables=yes, ProtectKernelModules=yes, ProtectControlGroups=yes,
RestrictNamespaces=yes, RestrictRealtime=yes, RestrictSUIDSGID=yes, LockPersonality=yes,
SystemCallArchitectures=native and SystemCallFilter=@system-service.
The renderer carries more, because it is the one that parses content an attacker wrote:
PrivateNetwork=yes, IPAddressDeny=any, MemoryDenyWriteExecute=yes, a second SystemCallFilter
subtracting @privileged, @resources and @obsolete, and the MemoryMax=1G and TasksMax=256 that
bound a runaway render.
What each service may write differs, and the difference is the design. peart writes anywhere under
/var/lib/peart. peart-portal writes only db, reads renditions and reports, and cannot see
evidence or the master key at all. peart-render writes nowhere in the data tree: /var/lib/peart/work
is mounted read-only to it, so the process that interprets hostile HTML has only its own PrivateTmp to
write to.
Two parts of this posture are the host's rather than the unit's, and are supported the same way: the three
service accounts are system users with no home directory created, and the data volume may be mounted
nosuid.
Read the list as a promise. A change that needs one of these relaxed is a break of it, and it should reach you as a decision with a reason, not as a drop-in you write on your own host.
Full-disk encryption for the database¶
The database is cleartext by design (see Encryption). Full-disk encryption on the host, LUKS, is the recommended and documented control for the database at rest. It is the operator's responsibility, because it is a host-level control, and it is the compensating control the whole cleartext-database decision rests on. If you take one hardening step, take this one.
Unlocking without someone at the console¶
On a headless production VM, plain LUKS means typing a passphrase at the console on every boot, which does not scale and in practice quietly stops happening. The pattern that makes encryption at rest survivable is network-bound unlocking: the host opens the volume by itself when, and only when, it can reach a key server on the trusted network. Your distribution documents the mechanism and its commands, and this page deliberately does not repeat them, because a handbook that transcribes another project's command line inherits that project's changes with no way to notice them.
Three things about it are PEART's, and they are the ones nobody else will write down for you.
The service inherits the key server's boot latency. A volume that unlocks over the network cannot mount
until the network is up and the key server answers, and /var/lib/peart is that volume. This is why the
RequiresMountsFor=/var/lib/peart the shipped units carry matters more here than on a local volume:
without it, or with the mount arriving late, the service starts against an empty mount point on the root
filesystem, writes there, and the data vanishes when the volume mounts over the top. Running as a
service states the general case; network unlocking is what widens the window.
A break-glass keyslot is not optional. Bind the volume to the key server in a keyslot of its own and keep a passphrase in another. Without it, a key server that is down does not delay your data, it removes it. That is the trade this pattern makes, and it should be made with open eyes: you exchange a passphrase nobody types for a key server that, unreachable, holds the boot.
Prove that it refuses. Take the key server away and reboot: the volume must not open on its own, and must ask for the break-glass passphrase. Put the key server back and reboot: it opens by itself again. Until that test has been run, nobody knows whether encryption at rest is in force or whether the volume merely happens to open, and an untested control is the one discovered at the worst possible moment.
Network exposure¶
The two surfaces have different exposure by design, and the host should enforce it. The portal
(peart-portal) is reachable by the client over the internet; the internal surface (peart) should be
reachable only from the consultancy's restricted network or VPN, never from where the client reaches the
portal. Both bind to loopback and sit behind a TLS-terminating reverse proxy, so the firewall and the proxy
are what actually decide who reaches which surface.
The rendering sidecar's isolation¶
The renderer processes HTML derived from hostile content and must not reach the internet or the cloud
metadata endpoint. On systemd this is PrivateNetwork=yes and IPAddressDeny=any in
peart-render.service; in the container path it is the internal network with no route out. This is a
requirement, not a tuning knob, and removing it silently reopens a server-side request forgery surface. Do
not relax it.
Key file and permissions¶
The master key lives outside the data directory, mode 0600, owned by the running user, so a backup of the
data can never contain it. Keep it that way, and keep the exported copy (see
Backup and restore) somewhere separate from the data backup. A key in the same place
as the data it protects makes the encryption theatre.