_selinux
_selinux
¶
SELinux helpers for socket labeling and policy management.
Terok services listen on Unix sockets that rootless Podman containers
must connect() to. SELinux blocks this by default — the kernel's
connectto check uses the socket object's SID (inherited from the
creating process, typically unconfined_t), not the file inode's label.
To work around this without disabling confinement:
- A custom policy module defines
terok_socket_tand grantscontainer_t → terok_socket_t:unix_stream_socket connectto. - Services call
setsockcreateconbeforesocket()so the kernel assignsterok_socket_tto the socket object. - After
bind(), the socket object carriesterok_socket_tand containers can connect.
The sock_file { write } check (file-level access) is separately
handled by Podman's :z volume relabeling.
libselinux is loaded via ctypes at call time, so this module has
no runtime dependency on the python3-libselinux distribution package
— libselinux.so.1 from the base libselinux package is sufficient.
All functions degrade gracefully on non-SELinux systems.
SELINUX_SOCKET_TYPE = 'terok_socket_t'
module-attribute
¶
Custom SELinux type applied to terok service sockets.
SelinuxStatus
¶
Bases: Enum
Outcome of check_status — the single decision tree behind
both terok setup's prereq check and terok sickbay's health check.
NOT_APPLICABLE_TCP_MODE = 'not_applicable_tcp_mode'
class-attribute
instance-attribute
¶
Transport is tcp; the terok_socket_t policy is irrelevant.
NOT_APPLICABLE_PERMISSIVE = 'not_applicable_permissive'
class-attribute
instance-attribute
¶
Socket transport, but SELinux is disabled or permissive.
POLICY_MISSING = 'policy_missing'
class-attribute
instance-attribute
¶
Enforcing host, socket transport, but terok_socket module is not loaded.
POLICY_OUTDATED = 'policy_outdated'
class-attribute
instance-attribute
¶
Enforcing host, socket transport, terok_socket loaded — but an
older revision missing the container_runtime_t rule the per-container
supervisor needs. Re-running the installer rebuilds + upgrades it.
LIBSELINUX_MISSING = 'libselinux_missing'
class-attribute
instance-attribute
¶
Policy is loaded but libselinux.so.1 cannot be dlopen'd — silent-
failure case where sockets would bind as unconfined_t regardless.
OK = 'ok'
class-attribute
instance-attribute
¶
Enforcing, policy installed, libselinux loadable — all good.
SelinuxCheckResult(status, missing_policy_tools=tuple())
dataclass
¶
Structured outcome of check_status.
Callers decide how to present the result; this struct only carries
the decision tree's output so that terok setup (printed multi-
line warnings) and terok sickbay (tuple-based check result) can
share one source of truth for the branching.
is_selinux_enforcing()
¶
Return True if SELinux is in enforcing mode.
Reads /sys/fs/selinux/enforce directly — no external commands.
Returns False on non-SELinux systems or if the file is unreadable.
Source code in src/terok_sandbox/_util/_selinux.py
is_selinux_enabled()
¶
is_policy_installed()
¶
Return True if terok_socket_t is a valid type in the loaded policy.
Uses libselinux's security_check_context(), which succeeds
iff the context (and therefore the custom type) is known to the
currently loaded policy — a pure userspace query requiring no
subprocess and no privileges.
The previous semodule -l subprocess approach silently failed
for non-root callers on Fedora, where /var/lib/selinux/.../active/
is root-readable only. terok sickbay and terok setup
both run as the user, so they would always report the policy as
missing even right after a successful install.
Source code in src/terok_sandbox/_util/_selinux.py
is_supervisor_socket_rule_loaded()
¶
Whether the loaded policy lets container_runtime_t create terok_socket_t sockets.
The per-container supervisor binds its sockets from
container_runtime_t (crun's OCI-hook domain), so this rule —
added in policy v1.1 — must be in the loaded policy or the
supervisor dies with EACCES on its first socket(). Probing
it (via libselinux's unprivileged selinux_check_access query
against the kernel AVC) distinguishes a stale v1.0 install (type
present, rule absent) from a current one — which the bare
type-presence check in
is_policy_installed
cannot see.
Returns True / False when the policy can be queried, or
None when it can't be determined (libselinux absent or too old,
or container_runtime_t undefined) — callers must treat None
as "no opinion", never as "stale".
Source code in src/terok_sandbox/_util/_selinux.py
is_libselinux_available()
¶
Return True if libselinux.so.1 can be loaded via ctypes.
On SELinux-enforcing hosts, a False return is a silent-failure
risk: service sockets would bind without terok_socket_t labeling,
and container clients would be denied connectto even when the
terok_socket policy module is installed.
Source code in src/terok_sandbox/_util/_selinux.py
missing_policy_tools()
¶
Return names of policy-compilation tools not found on PATH.
The terok_socket policy is compiled from its .te source at
install time by install_policy, which requires all three of
checkmodule, semodule_package, and semodule. An empty
list means install_policy() will not fail with SystemExit for
missing tools. Names are returned in invocation order so callers
can surface the first one a user would hit.
Source code in src/terok_sandbox/_util/_selinux.py
policy_source_path()
¶
Return the path to the bundled terok_socket.te policy source.
install_script_path()
cached
¶
Return the path to the bundled install_policy.sh installer.
Installation is delegated to this short, inspectable shell script —
which users run with sudo bash <path> — rather than a Python
wrapper. Running Python as root imports a large dependency graph;
a dedicated shell script can be cat-ed and audited in seconds
before the privilege escalation.
Source code in src/terok_sandbox/_util/_selinux.py
install_command()
¶
Return the full sudo bash <path> shell command for the installer.
Single source for the command string so the setup hint, the sickbay check, and any future caller all render the same invocation.
Source code in src/terok_sandbox/_util/_selinux.py
socket_selinux_context(selinux_type=SELINUX_SOCKET_TYPE)
¶
Apply selinux_type as the creation context for sockets bound in this block.
Any socket() call within the with body produces a socket
whose kernel SID is selinux_type, enabling container_t clients
to connectto it once the matching policy is installed. The
previous context is restored on exit.
No-op on non-SELinux systems or when libselinux.so.1 is absent.
Usage::
with socket_selinux_context():
sock = socket.socket(AF_UNIX, SOCK_STREAM)
sock.bind(str(path))
# socket object now carries terok_socket_t
Source code in src/terok_sandbox/_util/_selinux.py
check_status(*, services_mode)
¶
Evaluate SELinux readiness for socket-transport services.
services_mode is the caller's configured transport (tcp or
socket) — passed in rather than read from sandbox config so the
helper stays free of cross-package config plumbing. Consumers
(terok setup, terok sickbay) call
terok_sandbox.config.services_mode themselves.