update SSH SELinux module to work with cilium/ebpf (#65049)

Also ensure the specified Teleport binary has the proper context
restored when using the SELinux module install script.
This commit is contained in:
Andrew LeFevre
2026-04-14 20:44:49 +00:00
committed by GitHub
parent 0ea6a8f6a8
commit cee8aba4cb
3 changed files with 76 additions and 25 deletions
+31 -9
View File
@@ -15,7 +15,9 @@
# This script installs the SELinux module for Teleport SSH.
set -euo pipefail
set -o errexit
set -o nounset
set -o pipefail
if [[ ! -f "/usr/share/selinux/devel/Makefile" ]]; then
echo "SELinux Makefile not found, please install selinux-policy-devel"
@@ -23,35 +25,55 @@ if [[ ! -f "/usr/share/selinux/devel/Makefile" ]]; then
fi
TELEPORT="teleport"
TELEPORT_ARGS=""
TELEPORT_SET=false
TELEPORT_ARGS=()
while getopts "c:t:" opt; do
USAGE="Usage: $0 [-c config_path] [-t teleport_path]"
while getopts "c:t:h" opt; do
case "${opt}" in
c)
TELEPORT_ARGS="-c ${OPTARG}"
TELEPORT_ARGS=(-c "${OPTARG}")
;;
t)
TELEPORT="${OPTARG}"
TELEPORT_SET=true
if ! type -p "${TELEPORT}" >/dev/null; then
echo "Teleport binary ${TELEPORT} not found"
exit 1
fi
;;
h)
echo "${USAGE}"
exit 0
;;
*)
echo "Usage: $0 [-c config_path] [-t teleport_path]"
echo "${USAGE}"
exit 2
;;
esac
done
if ! ${TELEPORT_SET} && ! type -p teleport >/dev/null; then
echo "Teleport binary not found, please specify with -t"
exit 1
fi
# Write SELinux module source to a temporary directory
WORK_DIR="$(mktemp -d -t teleport-selinux.XXXXXXXX)"
trap 'rm -rf "${WORK_DIR}"' EXIT INT TERM
"${TELEPORT}" selinux-ssh module-source > "${WORK_DIR}/teleport_ssh.te"
"${TELEPORT}" selinux-ssh file-contexts ${TELEPORT_ARGS} > "${WORK_DIR}/teleport_ssh.fc"
DIRS=$(${TELEPORT} selinux-ssh dirs ${TELEPORT_ARGS})
"${TELEPORT}" selinux-ssh file-contexts "${TELEPORT_ARGS[@]}" > "${WORK_DIR}/teleport_ssh.fc"
DIRS=$("${TELEPORT}" selinux-ssh dirs "${TELEPORT_ARGS[@]}")
# Build SELinux module
pushd "${WORK_DIR}"
make -f /usr/share/selinux/devel/Makefile teleport_ssh.pp
# Install the module first so that the file contexts are available so
# we can label the directories and binary correctly.
semodule -i teleport_ssh.pp
popd
# Ensure necessary directories exist and are labeled correctly
while IFS= read -r dir; do
@@ -60,5 +82,5 @@ while IFS= read -r dir; do
restorecon -rv "${dir}"
done <<< "$DIRS"
popd
rm -rf "${WORK_DIR}"
# Label the Teleport binary
restorecon -v "$(type -p "${TELEPORT}")"
+21 -6
View File
@@ -173,8 +173,7 @@ func testBPFRecording(t *testing.T, srv Server, bpfSrv bpf.BPF) {
newFilePath := filepath.Join(cmdDir, "newfile")
// Lookup paths of programs that are also shell builtins.
echoPath, err := exec.LookPath("echo")
require.NoError(t, err, "echo command is required for these tests but was not found")
echoPath := lookResolvedPath(t, "echo")
// Create a TCP listener for each IP family. Register the listeners
// to be closed in case the test fails before the connection
@@ -573,8 +572,7 @@ func runBPFTestCase(t *testing.T, srv Server, bpfSrv bpf.BPF, tt bpfTestCase, cl
// free, which can be useful to catch rare bugs in the
// BPF disk tracing program.
if info.cmdInfo.scriptPath == "" {
programPath, err := exec.LookPath(cmdInfo.program)
require.NoError(t, err, "%s command is required for these tests but was not found", cmdInfo.program)
programPath := lookResolvedPath(t, cmdInfo.program)
programPaths[cmdInfo.program] = programPath
_, ok := programLibs[cmdInfo.program]
@@ -586,8 +584,7 @@ func runBPFTestCase(t *testing.T, srv Server, bpfSrv bpf.BPF, tt bpfTestCase, cl
// show the script as the program, but it will act
// like the interpreter.
programPaths[cmdInfo.program] = info.cmdInfo.scriptPath
interpPath, err := exec.LookPath(cmdInfo.interpreter)
require.NoError(t, err)
interpPath := lookResolvedPath(t, cmdInfo.interpreter)
_, ok := programLibs[cmdInfo.program]
if !ok {
@@ -1219,6 +1216,24 @@ func checkEvent[T comparable](program string, eventField T, expectedFields map[s
return false
}
// lookResolvedPath looks up the given file in PATH and resolves any
// symlinks in the resulting path. The resolved path is returned.
// This is necessary when testing on Fedora based distros where
// /bin is symlinked to /usr/bin. This causes exec.LookPath to return
// paths in /bin, but the binaries actually reside in /usr/bin and will
// be correctly reported as being opened from /usr/bin in events.
func lookResolvedPath(t *testing.T, file string) string {
t.Helper()
path, err := exec.LookPath(file)
require.NoError(t, err, "%s command is required for these tests but was not found", file)
resolved, err := filepath.EvalSymlinks(path)
require.NoError(t, err, path)
return resolved
}
func quoteStrings(s []string) string {
var quoted []string
for _, v := range s {
+24 -10
View File
@@ -1,12 +1,12 @@
policy_module(teleport_ssh, 1.0.0)
policy_module(teleport_ssh, 1.0.1)
require {
attribute can_change_process_identity;
attribute can_change_process_role;
attribute userdomain;
type bin_t;
type config_home_t;
type default_t;
type default_context_t;
type devlog_t;
type devpts_t;
@@ -22,7 +22,6 @@ require {
type passwd_exec_t;
type passwd_file_t;
type ptmx_t;
type root_t;
type security_t;
type selinux_config_t;
type semanage_store_t;
@@ -43,11 +42,11 @@ require {
type var_run_t;
type wtmp_t;
class capability { audit_control audit_write chown dac_read_search dac_override fowner fsetid net_admin net_bind_service setgid setuid sys_admin sys_resource };
class capability { audit_control audit_write chown dac_read_search dac_override fowner fsetid kill net_admin net_bind_service setgid setuid sys_admin sys_resource };
class chr_file { append read write };
class dbus { send_msg };
class file { create getattr ioctl map open read remove_name setattr unlink write };
class filesystem { getattr mount unmount };
class filesystem { getattr unmount };
class process { setsched transition };
class service { status };
class sock_file { append create getattr open read setattr unlink write };
@@ -184,6 +183,19 @@ allow teleport_ssh_t http_port_t:tcp_socket name_connect;
##
type_transition teleport_ssh_t teleport_ssh_exec_t:process teleport_ssh_t;
allow teleport_ssh_t self:process { transition setexec setkeycreate setrlimit setsched };
allow teleport_ssh_t self:capability kill;
# Allow Teleport to send SIGKILL to child processes in the user domain,
# as well as unconfined processes. This is needed to ensure that when a
# user exits an SSH session, all child processes are killed properly
# regardless of their SELinux context.
#
# Note that SELinux doesn't allow this to be scoped to child processes
# only, so this allows Teleport to SIGKILL any process in a user or
# unconfined domain. This does not allow Teleport to SIGKILL system
# processes in other domains though.
allow teleport_ssh_t userdomain:process sigkill;
allow teleport_ssh_t unconfined_t:process sigkill;
##
# handling SSH connections
@@ -221,17 +233,19 @@ allow teleport_ssh_t wtmp_t:file rw_file_perms;
allow teleport_ssh_t lastlog_t:file read_file_perms;
# enhanced session recording
allow teleport_ssh_t cgroup_t:filesystem { mount unmount };
# TODO(capnspacehook) REMOVE IN v20: cleaning up cgroup dirs won't be needed by that point
allow teleport_ssh_t cgroup_t:filesystem unmount;
allow teleport_ssh_t cgroup_t:dir manage_dir_perms;
allow teleport_ssh_t default_t:dir mounton;
allow teleport_ssh_t self:bpf { map_create map_read map_write prog_load prog_run };
allow teleport_ssh_t self:perf_event { cpu kernel open write };
allow teleport_ssh_t sysfs_t:file map;
allow teleport_ssh_t tracefs_t:filesystem getattr;
allow teleport_ssh_t tracefs_t:file read_file_perms;
allow teleport_ssh_t root_t:dir { add_name create mounton write };
allow teleport_ssh_t self:perf_event { cpu kernel open tracepoint write };
allow teleport_ssh_t self:capability sys_admin;
# not available on RHEL 8
optional_policy(`
require {
require {
class capability2 { bpf perfmon };
};
allow teleport_ssh_t self:capability2 { bpf perfmon };