_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_MODULE_NAME = 'terok_socket'
module-attribute
¶
Policy module the installer compiles and loads (semodule -l name).
SELINUX_SOCKET_TYPE = f'{SELINUX_MODULE_NAME}_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.
action_needed
property
¶
Whether this status calls for an operator install.
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
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.