Skip to content

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, with one exception: on a host that enforces module signatures (Secure Boot, or module.sig_enforce=1), the driver does not load until its signing certificate is enrolled. Otherwise the driver runs without it, and the capability check is independent of SELinux.

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/riptides as riptides_device_t and /dev/riptides_pro as riptides_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.

Terminal window
sudo dnf install riptides-selinux
Terminal window
# the module is loaded
sudo 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 domain
ps -eZ | grep riptides

The 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:

Terminal window
sudo udevadm trigger --action=add /sys/class/riptides/riptides /sys/class/misc/riptides_pro

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.

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.

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:

Terminal window
echo Y | sudo tee /sys/module/riptides/parameters/eval_fail_closed

Connections 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.

The driver modules (riptides, bearssl and riptides_pro) are signed with the Riptides module signing key. Every driver package installs its certificate at /usr/share/riptides/riptides-module-signing.der, but nothing enrolls it, so on a host enforcing module signatures (Secure Boot, or module.sig_enforce=1) the modules will not load until you do.

The certificate is also published here, so you can enroll a host before the driver is installed, or build it into an image:

-----BEGIN CERTIFICATE-----
MIIFjzCCA3egAwIBAgIUatq7ho6YUnE566bvrVBfcNZDTfowDQYJKoZIhvcNAQEL
BQAwRjEsMCoGA1UEAwwjUmlwdGlkZXMgTGFicyBrZXJuZWwgbW9kdWxlIHNpZ25p
bmcxFjAUBgNVBAoMDVJpcHRpZGVzIExhYnMwHhcNMjYwOTIyMTQwNTM3WhcNMzYw
OTE5MTQwNTM3WjBGMSwwKgYDVQQDDCNSaXB0aWRlcyBMYWJzIGtlcm5lbCBtb2R1
bGUgc2lnbmluZzEWMBQGA1UECgwNUmlwdGlkZXMgTGFiczCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBANI7J5CGf5JcL6B3mjHtow/Ll1UfqrKOQNf6dFO3
w4OvGbAWOfh0yn42XGyGX8SJbZJMdwVtpRz/zl8dS1Knwi5OLD+5gwg0TxznTCTA
H/WMZYVcQ9ZNj0aLJvy5VkT+SC6Uq7WJ3rnliNUghfGS7C60umJIIYJtHLCRnEfm
PnMWc5WNZk9bupfC8uSkwS0t8m/VAuRrEAMW8UBgbV3gjqxN8lf5X8AOW+NQt6wC
FEpSKRFnQzQ7MCEzP2/eswWUic1vykNAzaaJeQ6wSz58p8ALXG8v5A/qaqzBlRsk
bge3Rqlk+Ql3Xl7lRLCc/UO3mZHL5KwNOxrKuNAH3qw+SYm5sAEnGPYTVDuhvrVi
7RZf/A0qxT82DsEPGsWfJ5Skgxhg0srClGnGLxu6cJ2PApyb/h8fWgiEBlBRHmaz
tlLzLumZAlU88kwF9rDu+p1QxDir+b11S2Zp5560A8qAU/nVVPxlUJYbT9KGy2RY
nQ+4iQhkk0QRNCzWVp1S3V3HCTxYREPYaofyodwkrSgs5ULSvdTfiA6fqgLIYtjY
xwhx10fIuEq16yOLEv5dhpJC3brkRABY5RDae3sPH0zg3AyDJ9Y/mrTPtNZM2r1X
NtDAK9rpg/vsDKm4ucID1hVwpOQco3wKI4m2GcQ6K1NCshy//0Ri9o9ik24kK0sw
jdVrAgMBAAGjdTBzMB8GA1UdIwQYMBaAFNqT+f+3qEmshelMTkbKL6Js3YmcMAwG
A1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMD
MB0GA1UdDgQWBBTak/n/t6hJrIXpTE5Gyi+ibN2JnDANBgkqhkiG9w0BAQsFAAOC
AgEAo0QIh/mXwcY3NXXCSpOJGZiiG7FrnXVOnUD4GIaCybDfbuCMBUsQVEgwoM9j
bam0YNWSRoqi4+6CyECp83d4+PMEg2BfXJjuzznm++SQE4nQCLJ/o/TcLy4t/OK3
yIzMyGAUMHKQJy7rm9SQ2ZdWUJ5VEMZRxhxkMw2CU3CQaQwSX4BXHAtP8b2XSywo
4v+ZcoeRSfrvkPyxyM5weGQqvXzNRe0ZmBQmXmjlyt2PZnxyMQhEUgi23Vw8nO90
HHN2WqVER8pCuHvSq3Ui0jc+/lhkW4NSOxS0fvfwsT1LqrVcSqg9LSHnw5dKK9oZ
Ee5qNZmny9Q3WRppI8wb+gfFHYvA8vTR2nYkhbbdge5n/4HPxGW4NjUFKxZVNVS2
9taG+vkphcP6sVKDif0ScKgbkQDvp3jvE8JhuKn33FOJX81B6x4Yk9ub3q0nMphE
758RQyz4OZi5bIYDPfu19217YfCUmhfRwmOhxCFnKAqmwHbqBA8MZ9uVKwkGFMLp
S90kgXNXBAN83h/KzWUwrpstWcbBHIXaOwbjKWClUVoc0+SjgmVU81ypkjQPgXii
9j1kRrFPlu7d5DojHN129dSajkfnjr0HfQCYWW93n+u1SDv0V4vmOD+hd2rMMbgn
SrwTRavV26h8TLG1yxHmE9WuUdZwW0tEt51fG90XDUNKLYE=
-----END CERTIFICATE-----

Check the certificate before you trust it, wherever it came from. Its SHA-256 fingerprint is:

37:A5:43:0A:17:19:A5:30:44:46:15:D5:15:56:0D:19:97:FB:6A:D4:D5:76:6C:2C:62:54:78:E6:B5:16:58:CA

Enrolling needs access to the host’s console at the next boot. mokutil ships with Ubuntu and Debian; on RHEL-family hosts install it with sudo dnf install mokutil.

Use the installed copy, or download the published one if the driver is not installed yet:

Terminal window
CERT=/usr/share/riptides/riptides-module-signing.der
# or, before installing:
curl -fsSLO https://docs.riptides.io/keys/riptides-module-signing.der
CERT=./riptides-module-signing.der

Check its fingerprint against the one above, then queue the enrollment:

Terminal window
openssl x509 -inform DER -in "$CERT" -noout -fingerprint -sha256
sudo mokutil --import "$CERT"
sudo mokutil --timeout -1
sudo reboot

mokutil --import asks for a one-time password and only queues the request. mokutil --timeout -1 makes MokManager wait for you at the next boot; by default it gives up after 10 seconds and the request is dropped. At the next boot MokManager shows a blue screen with Continue boot selected: pressing Enter there boots without enrolling and drops the request. Move down to Enroll MOK first, then choose Continue and Yes, enter the password, and reboot.

On Azure, the VM’s Serial Console reaches MokManager, including on Trusted Launch VMs; boot diagnostics must be enabled. Where you cannot reach a boot console at all, disable Secure Boot instead.

Confirm the certificate is enrolled:

Terminal window
mokutil --list-enrolled | grep -i riptides

If that prints nothing, the MokManager screen was missed: queue the enrollment again. If the driver is already installed, load the modules; otherwise install it now, and the installer loads them:

Terminal window
sudo systemctl restart riptides-modules

O (out-of-tree) is expected in /sys/module/riptides/taint. An E means the kernel could not verify the signature. That is normal when Secure Boot is off and the certificate is not enrolled; with Secure Boot on, a module that fails verification is not loaded at all.

Secure Boot with the certificate enrolled has been checked on Ubuntu 24.04 on an Azure Trusted Launch VM, in kernel lockdown (integrity): the modules load, the daemon connects, health reads OK and connections are tracked. Policy enforcement (mTLS, credential injection) has not been tested under lockdown. If you run Secure Boot, talk to us before a deployment rather than after.