Host Hardening
The Riptides kernel module is reachable only by a process holding
CAP_SYS_ADMIN — see Kernel Interface. On
SELinux hosts an optional package narrows that further, so that only the
daemon’s own SELinux domain, and privileged containers, can open its devices
through /dev, whatever capabilities a process holds.
Nothing on this page is required. The driver runs without it, and the capability check is independent of SELinux.
The SELinux policy
Section titled “The SELinux policy”riptides-selinux ships a policy module that:
- gives the daemon its own domain,
riptides_t, entered when systemd executes/usr/bin/riptides - labels the module’s devices,
/dev/riptidesasriptides_device_tand/dev/riptides_proasriptides_pro_device_t - grants access to those device types to
riptides_t, and to privileged containers, which is how the daemon runs on Kubernetes (below)
With it installed and SELinux enforcing, a root shell on the host cannot open
either device through /dev.
It is built per distribution release, because a compiled policy module is tied
to the selinux-policy version it was built against.
sudo dnf install riptides-selinuxVerifying it
Section titled “Verifying it”# the module is loadedsudo semodule -l | grep riptides
# the devices carry the policy's label (needs the driver loaded)sudo ls -Z /dev/riptides /dev/riptides_pro
# and the daemon is in its own domainps -eZ | grep riptidesThe device check is the one that matters, and a working policy makes it fail:
root may not even look at a confined device, so Permission denied is the
result you want. If ls -Z shows device_t instead, the policy is loaded but
confining nothing. That happens when the driver was loaded before the policy was
installed and the node never got relabeled; have udev relabel it:
sudo udevadm trigger --action=add /sys/class/riptides/riptides /sys/class/misc/riptides_proWhat it does not confine
Section titled “What it does not confine”Root with a device node of its own. SELinux checks the label of the node
being opened, not the device behind it. Root can create another node for the
same device outside /dev and open the driver through that, as it could also
load a kernel module of its own. So the policy stops casual and accidental
access by root, not a determined attacker holding root; against that, the
control is the module’s capability check and who holds CAP_SYS_ADMIN.
The daemon itself. It reads /proc of every process on the host to
attribute connections to workloads, and manages the system trust store, so a
policy permitting its real behaviour would be broad enough to carry little
value. The policy confines the device, not the daemon.
Kubernetes. The daemon runs there as a privileged container, which does not
enter riptides_t, so the policy grants privileged containers access to the
device — without that the DaemonSet could not start. On a Kubernetes node the
controls are the module’s capability check and who is permitted to schedule a
privileged pod. A privileged pod can already load its own kernel module.
AppArmor hosts
Section titled “AppArmor hosts”There is no AppArmor profile, and this is a considered position rather than an omission.
AppArmor is path-based and only restricts processes that carry a profile, so it
cannot express “this device is reachable by the daemon alone” against the
unprofiled processes that are the concern. A profile confining the daemon
itself would have to permit reading every process’s memory maps and executables,
ptrace, and a broad capability set — which is not meaningfully different from
running unconfined.
On Debian and Ubuntu hosts the control is therefore the CAP_SYS_ADMIN check in
the module. If your policy requires a profile for third-party daemons, contact
us and we will supply one, with that caveat stated.
Failing closed
Section titled “Failing closed”By default the module fails open: if policy evaluation cannot complete, the connection proceeds unprotected rather than breaking. That keeps a control-plane problem from becoming an application outage, but it means a loss of protection is invisible to the application, which still succeeds.
Deployments that prefer the opposite trade-off should set:
echo Y | sudo tee /sys/module/riptides/parameters/eval_fail_closedConnections are then reset instead, whether the evaluation that failed was the one made when the connection opened or a message’s traffic evaluation (L7 and agent policy). Traffic the daemon receives but cannot parse is still passed through: that is the daemon’s own choice, not a failed evaluation.
It does not cover a daemon that is down. Until the daemon is connected and
has pushed its configuration, trust anchors and a workload identity, the module
evaluates no new connection at all and lets each one through untouched, with or
without eval_fail_closed. That is the state after a daemon crash, a restart,
or a write to /sys/class/riptides/force_close, which kills the daemon.
Monitor for it either way: command timeout lines in dmesg mark evaluations
the daemon did not answer, and /sys/module/riptides/health should read OK.
Alert on anything else: Waiting for daemon, Daemon is connected, but no workload identity defined. and OK (disabled) all mean new connections are not
being protected.
Not yet qualified
Section titled “Not yet qualified”Kernel lockdown and Secure Boot have not been tested. The module is currently unsigned, so on a host enforcing module signatures it will not load. If you run either, talk to us before a deployment rather than after.