add SELinux module for SSH service (#53424)

This commit is contained in:
Andrew LeFevre
2025-05-13 20:54:51 +00:00
committed by GitHub
parent c7dac12095
commit 6bf6aa60e6
22 changed files with 939 additions and 2 deletions
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# Copyright 2025 Gravitational, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script installs the SELinux module for Teleport SSH.
set -euo pipefail
if [[ ! -f "/usr/share/selinux/devel/Makefile" ]]; then
echo "SELinux Makefile not found, please install selinux-policy-devel"
exit 1
fi
TELEPORT="teleport"
TELEPORT_ARGS=""
while getopts "c:t:" opt; do
case "${opt}" in
c)
TELEPORT_ARGS="-c ${OPTARG}"
;;
t)
TELEPORT="${OPTARG}"
;;
*)
echo "Usage: $0 [-c config_path] [-t teleport_path]"
exit 2
;;
esac
done
# 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})
# Build SELinux module
pushd "${WORK_DIR}"
make -f /usr/share/selinux/devel/Makefile teleport_ssh.pp
semodule -i teleport_ssh.pp
# Ensure necessary directories exist and are labeled correctly
while IFS= read -r dir; do
# shellcheck disable=SC2174
mkdir -p -m 0750 "${dir}"
restorecon -rv "${dir}"
done <<< "$DIRS"
popd
rm -rf "${WORK_DIR}"
+2
View File
@@ -171,6 +171,7 @@ require (
github.com/moby/term v0.5.2
github.com/okta/okta-sdk-golang/v2 v2.20.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/selinux v1.12.0
github.com/opensearch-project/opensearch-go/v2 v2.3.0
github.com/oracle/oci-go-sdk/v65 v65.89.3
github.com/parquet-go/parquet-go v0.25.0
@@ -596,6 +597,7 @@ replace (
github.com/julienschmidt/httprouter => github.com/gravitational/httprouter v1.3.1-0.20220408074523-c876c5e705a5
github.com/keys-pub/go-libfido2 => github.com/gravitational/go-libfido2 v1.5.3-teleport.1
github.com/microsoft/go-mssqldb => github.com/gravitational/go-mssqldb v0.11.1-0.20230331180905-0f76f1751cd3
github.com/opencontainers/selinux => github.com/gravitational/selinux v1.13.0-teleport
github.com/redis/go-redis/v9 => github.com/gravitational/redis/v9 v9.6.1-teleport.1
github.com/vulcand/predicate => github.com/gravitational/predicate v1.3.2
)
+2
View File
@@ -1599,6 +1599,8 @@ github.com/gravitational/roundtrip v1.0.2 h1:eOCY0NEKKaB0ksJmvhO6lPMFz1pIIef+vyP
github.com/gravitational/roundtrip v1.0.2/go.mod h1:fuI1booM2hLRA/B/m5MRAPOU6mBZNYcNycono2UuTw0=
github.com/gravitational/saml v0.4.15-teleport.2 h1:ruNtaIkDrCJ5eS+OBcKeER+P5ldDDys7SeCoyFJAX1o=
github.com/gravitational/saml v0.4.15-teleport.2/go.mod h1:wGckhUH/I+hcDR2uo9WC4GyL+o4rid5JUe1Ze/joarI=
github.com/gravitational/selinux v1.13.0-teleport h1:XZQpZK5tteK9ZV2wIC1avhagqoVghXYv+O21GOpG6wU=
github.com/gravitational/selinux v1.13.0-teleport/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/gravitational/trace v1.5.1 h1:CdSymAjkE1VOef+lsC5x29jX9WbgI0fBtnRqeT4Fh+c=
github.com/gravitational/trace v1.5.1/go.mod h1:sJKfJHIQ7IkG8kvYpFPEr6mj3WDEdZ0YAc7xAD8w7lw=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
+2
View File
@@ -1102,6 +1102,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ=
github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
+16
View File
@@ -262,6 +262,14 @@ type CommandLineFlags struct {
// DisableDebugService disables the debug service.
DisableDebugService bool
// EnableSELinux enables SELinux support for the SSH service.
EnableSELinux bool
// EnsureSELinuxEnforcing will cause Teleport to exit if the SELinux module
// is not set to enforcing mode or the global SELinux mode is not set to
// enforcing.
EnsureSELinuxEnforcing bool
}
// IntegrationConfAccessGraphAWSSync contains the arguments of
@@ -2660,6 +2668,14 @@ func Configure(clf *CommandLineFlags, cfg *servicecfg.Config, legacyAppFlags boo
cfg.DebugService.Enabled = false
}
if clf.EnableSELinux {
cfg.SSH.EnableSELinux = true
}
if clf.EnsureSELinuxEnforcing {
cfg.SSH.EnsureSELinuxEnforcing = true
}
if os.Getenv("TELEPORT_UNSTABLE_QUIC_PROXY_PEERING") == "yes" {
cfg.Proxy.QUICProxyPeering = true
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2025 Open Container Initiative
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Original source: https://github.com/opencontainers/selinux/blob/main/go-selinux/selinux_linux.go#L229
//
// Modified to return an error.
package selinux
import (
"bufio"
"bytes"
"os"
"github.com/gravitational/trace"
)
func readConfig(target string) (string, error) {
in, err := os.Open(selinuxConfig)
if err != nil {
return "", trace.Wrap(err)
}
defer in.Close()
scanner := bufio.NewScanner(in)
for scanner.Scan() {
line := bytes.TrimSpace(scanner.Bytes())
if len(line) == 0 {
// Skip blank lines
continue
}
if line[0] == ';' || line[0] == '#' {
// Skip comments
continue
}
fields := bytes.SplitN(line, []byte{'='}, 2)
if len(fields) != 2 {
continue
}
if string(fields[0]) == target {
return string(bytes.Trim(fields[1], `"`)), nil
}
}
return "", nil
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package selinux
type filePaths struct {
InstallDir string
DataDir string
ConfigPath string
UpgradeUnitDir string
}
+182
View File
@@ -0,0 +1,182 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package selinux
import (
"bytes"
"context"
_ "embed"
"log/slog"
"os"
"path/filepath"
"text/template"
"github.com/gravitational/trace"
ocselinux "github.com/opencontainers/selinux/go-selinux"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/versioncontrol"
)
const (
selinuxConfig = "/etc/selinux/config"
selinuxRoot = "/var/lib/selinux"
selinuxTypeTag = "SELINUXTYPE"
moduleName = "teleport_ssh"
domain = "teleport_ssh_t"
permissiveModuleName = "permissive_" + domain
)
//go:embed teleport_ssh.te
var module string
//go:embed teleport_ssh.fc.tmpl
var fileContexts string
// ModuleSource returns the source of the SELinux SSH module.
func ModuleSource() string {
return module
}
// FileContexts returns file contexts for the SELinux SSH module.
func FileContexts(installDir, dataDir, configPath string) (string, error) {
fcTempl, err := template.New("selinux file contexts").Parse(fileContexts)
if err != nil {
return "", trace.Wrap(err, "failed to parse file contexts template")
}
// Generate a file specifying the locations of important dirs so SELinux
// will allow Teleport SSH to be able to access them.
var buf bytes.Buffer
err = fcTempl.Execute(&buf, filePaths{
InstallDir: installDir,
DataDir: dataDir,
ConfigPath: configPath,
UpgradeUnitDir: versioncontrol.UnitConfigDir,
})
if err != nil {
return "", trace.Wrap(err, "failed to expand file contexts template")
}
return buf.String(), nil
}
// CheckConfiguration returns an error if SELinux is not configured to
// enforce the SSH service correctly.
func CheckConfiguration(ensureEnforced bool, logger *slog.Logger) error {
if !ocselinux.GetEnabled() {
return trace.Errorf("SELinux is disabled or not present")
}
if ocselinux.EnforceMode() == ocselinux.Disabled {
return trace.Errorf("SELinux mode is disabled, SELinux will not enforce or log anything")
}
if ocselinux.EnforceMode() == ocselinux.Permissive {
if ensureEnforced {
return trace.Errorf("SELinux mode is permissive, SELinux will not enforce rules only log denials")
} else {
slog.WarnContext(context.TODO(), "The SELinux mode is set to permissive SELinux will not enforce rules, only log denials")
}
}
selinuxType, err := readConfig(selinuxTypeTag)
if err != nil {
return trace.Wrap(err, "failed to find SELinux type")
}
modulesDir := filepath.Join(selinuxRoot, selinuxType, "active/modules")
installed, disabled, permissive, err := moduleStatus(modulesDir)
if err != nil {
return trace.Wrap(err)
}
if !installed {
return trace.Errorf("the SSH SELinux module %s is not installed", moduleName)
}
if disabled {
return trace.Errorf("the SSH SELinux module %s is disabled", moduleName)
}
if permissive {
if ensureEnforced {
return trace.Errorf("the SSH SELinux module %s is permissive, denials will be logged but not enforced", moduleName)
} else {
slog.WarnContext(context.TODO(), "the SSH SELinux module is permissive, denials will be logged but not enforced", "module_name", moduleName)
}
}
return nil
}
func moduleStatus(modulesDir string) (installed bool, disabled bool, permissive bool, err error) {
disabledModPath := filepath.Join(modulesDir, "disabled", moduleName)
// SELinux modules can't be disabled and permissive, so we can
// safely return here
if utils.FileExists(disabledModPath) {
disabled = true
installed = true
return
}
moduleDirs, err := os.ReadDir(modulesDir)
if err != nil {
return false, false, false, trace.Wrap(err, "failed to list modules directory")
}
for _, moduleDir := range moduleDirs {
name := moduleDir.Name()
if name == "disabled" {
continue
}
path := filepath.Join(modulesDir, name)
permModPath := filepath.Join(path, permissiveModuleName)
if utils.FileExists(permModPath) {
// if the module is permissive, we also know it's installed
// so we can safely return
permissive = true
installed = true
return
}
modPath := filepath.Join(path, moduleName)
if utils.FileExists(modPath) {
installed = true
}
}
return
}
// UserContext returns the SELinux context that should be used when
// creating processes as a certain Linux user.
func UserContext(login string) (string, error) {
seUser, level, err := ocselinux.GetSeUserByName(login)
if err != nil {
return "", trace.Wrap(err)
}
curLabel, err := ocselinux.CurrentLabel()
if err != nil {
return "", trace.Wrap(err)
}
seContext, err := ocselinux.GetDefaultContextWithLevel(seUser, level, curLabel)
if err != nil {
return "", trace.Wrap(err)
}
return seContext, nil
}
+51
View File
@@ -0,0 +1,51 @@
//go:build !linux
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package selinux
import (
"log/slog"
"github.com/gravitational/trace"
)
const errPlatformNotSupportedMsg = "platform not supported"
// ModuleSource returns the source of the SELinux SSH module.
func ModuleSource() string {
return ""
}
// FileContexts returns file contexts for the SELinux SSH module.
func FileContexts(installDir, dataDir, configPath string) (string, error) {
return "", trace.Errorf(errPlatformNotSupportedMsg)
}
// CheckConfiguration returns an error if SELinux is not configured to
// enforce the SSH service correctly.
func CheckConfiguration(ensureEnforced bool, logger *slog.Logger) error {
return trace.Errorf(errPlatformNotSupportedMsg)
}
// UserContext returns the SELinux context that should be used when
// creating processes as a certain user.
func UserContext(login string) (string, error) {
return "", trace.Errorf(errPlatformNotSupportedMsg)
}
+44
View File
@@ -0,0 +1,44 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package selinux
import (
"io"
"os"
"testing"
"text/template"
"github.com/stretchr/testify/require"
)
// tests that the file contexts template is well formed
func TestFileContextTemplate(t *testing.T) {
fileConTmpl, err := os.ReadFile("teleport_ssh.fc.tmpl")
require.NoError(t, err)
fcTempl, err := template.New("selinux file contexts").Parse(string(fileConTmpl))
require.NoError(t, err)
err = fcTempl.Execute(io.Discard, &filePaths{
InstallDir: "/dir",
DataDir: "/dir",
ConfigPath: "/dir",
UpgradeUnitDir: "/dir",
})
require.NoError(t, err)
}
+9
View File
@@ -0,0 +1,9 @@
# teleport-update install dir
{{ .InstallDir }}/versions/[0-9\.]+/bin/teleport -- gen_context(system_u:object_r:teleport_ssh_exec_t,s0)
# teleport binary installed by RPM package
{{ .InstallDir }}/system/bin/teleport -- gen_context(system_u:object_r:teleport_ssh_exec_t,s0)
/usr/local/bin/teleport -- gen_context(system_u:object_r:teleport_ssh_exec_t,s0)
/run/.teleport.pid -- gen_context(system_u:object_r:teleport_ssh_pid_t,s0)
{{ .ConfigPath }} -- gen_context(system_u:object_r:teleport_ssh_conf_t,s0)
{{ .DataDir }}(/.*)? gen_context(system_u:object_r:teleport_ssh_data_t,s0)
{{ .UpgradeUnitDir }}(/.*)? gen_context(system_u:object_r:teleport_ssh_upgrade_data_t,s0)
+295
View File
@@ -0,0 +1,295 @@
policy_module(teleport_ssh, 1.0.0)
require {
attribute can_change_process_identity;
attribute can_change_process_role;
type bin_t;
type config_home_t;
type default_t;
type default_context_t;
type devlog_t;
type devpts_t;
type fs_t;
type groupadd_exec_t;
type http_port_t;
type init_t;
type initrc_var_run_t;
type kernel_t;
type lastlog_t;
type machineid_t;
type pam_var_run_t;
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;
type sssd_var_lib_t;
type syslogd_var_run_t;
type systemd_logind_t;
type systemd_logind_sessions_t;
type systemd_unit_file_t;
type systemd_systemctl_exec_t;
type sysctl_net_t;
type sysfs_t;
type tmp_t;
type tracefs_t;
type unconfined_t;
type unreserved_port_t;
type user_devpts_t;
type useradd_exec_t;
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 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 process { setsched transition };
class service { status };
class sock_file { append create getattr open read setattr unlink write };
class tcp_socket { accept listen name_bind name_connect };
}
########################################
#
# Declarations
#
type teleport_ssh_t;
domain_type(teleport_ssh_t);
type teleport_ssh_exec_t;
files_type(teleport_ssh_exec_t);
type teleport_ssh_pid_t;
files_type(teleport_ssh_pid_t);
type teleport_ssh_etc_t;
files_type(teleport_ssh_etc_t);
type teleport_ssh_conf_t;
files_config_file(teleport_ssh_conf_t);
type teleport_ssh_data_t;
files_type(teleport_ssh_data_t);
type teleport_ssh_upgrade_data_t;
files_type(teleport_ssh_upgrade_data_t);
########################################
#
# Local policy
#
##
# creation of initial process
##
init_daemon_domain(teleport_ssh_t, teleport_ssh_exec_t)
# needed if run manually from terminal
allow teleport_ssh_t teleport_ssh_exec_t:file { exec_file_perms lock entrypoint map };
##
# files
##
allow teleport_ssh_t fs_t:filesystem getattr;
allow init_t teleport_ssh_pid_t:file manage_file_perms;
manage_files_pattern(teleport_ssh_t, var_run_t, teleport_ssh_pid_t);
files_pid_filetrans(teleport_ssh_t, teleport_ssh_pid_t, file);
allow teleport_ssh_t teleport_ssh_conf_t:file read_file_perms;
allow teleport_ssh_t teleport_ssh_data_t:dir manage_dir_perms;
allow teleport_ssh_t teleport_ssh_data_t:file manage_file_perms;
allow teleport_ssh_t teleport_ssh_data_t:sock_file manage_sock_file_perms;
allow teleport_ssh_t teleport_ssh_upgrade_data_t:dir manage_dir_perms;
allow teleport_ssh_t teleport_ssh_upgrade_data_t:file manage_file_perms;
allow teleport_ssh_t tmp_t:dir manage_dir_perms;
allow teleport_ssh_t tmp_t:sock_file manage_sock_file_perms;
allow teleport_ssh_t tmp_t:file manage_file_perms;
allow teleport_ssh_t self:fifo_file rw_fifo_file_perms;
allow teleport_ssh_t machineid_t:file read_file_perms;
# needed to check if this module is installed and enabled
allow teleport_ssh_t semanage_store_t:dir list_dir_perms;
# auditd logging
allow teleport_ssh_t self:netlink_audit_socket { r_netlink_socket_perms nlmsg_relay };
allow teleport_ssh_t self:netlink_route_socket create;
allow teleport_ssh_t self:capability { audit_control audit_write };
# Go runtime
kernel_read_fs_sysctls(teleport_ssh_t);
allow teleport_ssh_t sysfs_t:file read_file_perms;
allow teleport_ssh_t sysfs_t:lnk_file read_lnk_file_perms;
# searching for cloud auth files
allow teleport_ssh_t config_home_t:file read_file_perms;
# host user management
allow teleport_ssh_t groupadd_exec_t:file exec_file_perms;
allow teleport_ssh_t passwd_exec_t:file exec_file_perms;
allow teleport_ssh_t useradd_exec_t:file exec_file_perms;
# needed for getent and visudo
allow teleport_ssh_t bin_t:file exec_file_perms;
allow teleport_ssh_t init_t:system status;
allow teleport_ssh_t kernel_t:unix_stream_socket connectto;
##
# write logs to terminal
##
domain_use_interactive_fds(teleport_ssh_t);
allow teleport_ssh_t user_devpts_t:chr_file { rw_chr_file_perms relabelfrom };
##
# systemd
##
systemd_exec_systemctl(teleport_ssh_t);
allow teleport_ssh_t init_t:unix_stream_socket connectto;
allow teleport_ssh_t systemd_logind_sessions_t:fifo_file write;
##
# networking
##
kernel_read_net_sysctls(teleport_ssh_t);
sysnet_dns_name_resolve(teleport_ssh_t);
allow teleport_ssh_t self:capability net_bind_service;
allow teleport_ssh_t unreserved_port_t:tcp_socket { name_bind name_connect };
allow teleport_ssh_t self:tcp_socket { listen accept };
allow teleport_ssh_t self:unix_stream_socket create_stream_socket_perms;
allow teleport_ssh_t self:unix_dgram_socket { connect create getopt getattr read recvfrom write sendto setattr };
allow teleport_ssh_t kernel_t:unix_dgram_socket { recvfrom sendto };
allow teleport_ssh_t http_port_t:tcp_socket name_connect;
##
# reexec
##
type_transition teleport_ssh_t teleport_ssh_exec_t:process teleport_ssh_t;
allow teleport_ssh_t self:process { transition setexec setkeycreate setrlimit setsched };
##
# handling SSH connections
##
term_use_all_ptys(teleport_ssh_t);
term_setattr_all_ptys(teleport_ssh_t);
term_setattr_all_ttys(teleport_ssh_t);
term_relabelto_all_ptys(teleport_ssh_t);
term_use_ptmx(teleport_ssh_t);
allow teleport_ssh_t devpts_t:chr_file { rw_chr_file_perms setattr };
allow teleport_ssh_t self:capability { chown dac_read_search dac_override fowner fsetid setgid setuid sys_resource };
fs_search_cgroup_dirs(teleport_ssh_t);
fs_rw_cgroup_files(teleport_ssh_t);
# allow SSH shell process to start a Teleport process (needed to detirmine home dir)
allow unconfined_t teleport_ssh_exec_t:file entrypoint;
# host auth
auth_use_nsswitch(teleport_ssh_t);
allow teleport_ssh_t passwd_file_t:file read_file_perms;
allow teleport_ssh_t shadow_t:file read_file_perms;
auth_domtrans_chk_passwd(teleport_ssh_t);
allow chkpwd_t user_devpts_t:chr_file { read write };
allow teleport_ssh_t chkpwd_t:process { noatsecure rlimitinh siginh };
allow teleport_ssh_t systemd_logind_t:dbus send_msg;
allow teleport_ssh_t self:capability { net_admin };
# allow Teleport to start child processes with different SELinux users and roles,
# needed so that the shell process is in the correct SELinux context
typeattribute teleport_ssh_t can_change_process_identity;
typeattribute teleport_ssh_t can_change_process_role;
# login data
allow teleport_ssh_t initrc_var_run_t:file rw_file_perms;
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 };
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 tracefs_t:file read_file_perms;
allow teleport_ssh_t root_t:dir { add_name create mounton write };
allow teleport_ssh_t self:capability sys_admin;
# not available on RHEL 8
optional_policy(`
require {
class capability2 { bpf perfmon };
};
allow teleport_ssh_t self:capability2 { bpf perfmon };
')
# PAM
auth_use_pam(teleport_ssh_t);
allow teleport_ssh_t pam_var_run_t:dir list_dir_perms;
allow teleport_ssh_t pam_var_run_t:file read_file_perms;
allow teleport_ssh_t default_context_t:file read_file_perms;
allow teleport_ssh_t devlog_t:lnk_file read_lnk_file_perms;
allow teleport_ssh_t devlog_t:sock_file rw_sock_file_perms;
allow teleport_ssh_t systemd_unit_file_t:service status;
allow systemd_logind_t teleport_ssh_t:dbus send_msg;
allow teleport_ssh_t syslogd_var_run_t:dir list_dir_perms;
allow teleport_ssh_t syslogd_var_run_t:file { read_file_perms map };
allow teleport_ssh_t tmpfs_t:filesystem getattr;
allow teleport_ssh_t selinux_config_t:file read_file_perms;
# these are read-only permissions: https://github.com/SELinuxProject/selinux-notebook/blob/main/src/object_classes_permissions.md#security-object-class
allow teleport_ssh_t security_t:security { check_context compute_av compute_relabel };
# motd
# not available on RHEL 8
optional_policy(`
require {
type motd_var_run_t;
};
allow teleport_ssh_t motd_var_run_t:dir list_dir_perms;
allow teleport_ssh_t motd_var_run_t:file read_file_perms;
')
optional_policy(`
require {
type cockpit_var_run_t;
};
allow teleport_ssh_t cockpit_var_run_t:lnk_file read_lnk_file_perms;
allow teleport_ssh_t cockpit_var_run_t:file read_file_perms;
')
files_search_all(teleport_ssh_t);
files_create_home_dir(teleport_ssh_t);
auth_exec_login_program(teleport_ssh_t);
auth_signal_chk_passwd(teleport_ssh_t);
userdom_read_user_home_content_files(teleport_ssh_t);
userdom_read_user_home_content_symlinks(teleport_ssh_t);
userdom_spec_domtrans_unpriv_users(teleport_ssh_t);
userdom_signal_unpriv_users(teleport_ssh_t);
userdom_dyntransition_unpriv_users(teleport_ssh_t);
userdom_map_tmp_files(teleport_ssh_t);
userdom_signal_all_users(teleport_ssh_t);
userdom_spec_domtrans_admin_users(teleport_ssh_t);
userdom_dyntransition_admin_users(teleport_ssh_t);
unconfined_shell_domtrans(teleport_ssh_t);
kernel_list_all_proc(teleport_ssh_t);
kernel_read_proc_files(teleport_ssh_t);
kernel_read_proc_symlinks(teleport_ssh_t);
# X forwarding
corenet_tcp_bind_xserver_port(teleport_ssh_t);
corenet_tcp_bind_vnc_port(teleport_ssh_t);
corenet_sendrecv_xserver_server_packets(teleport_ssh_t);
optional_policy(`
xserver_domtrans_xauth(teleport_ssh_t);
xserver_xdm_signull(teleport_ssh_t);
')
+19
View File
@@ -146,6 +146,7 @@ import (
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/reversetunnelclient"
secretsscannerproxy "github.com/gravitational/teleport/lib/secretsscanner/proxy"
"github.com/gravitational/teleport/lib/selinux"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/expiry"
@@ -1029,6 +1030,23 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) {
}
}
// If SELinux support is enabled ensure we have a valid configuration,
// and ensure SELinux itself is configured correctly
if cfg.SSH.EnableSELinux {
if runtime.GOOS != "linux" {
return nil, trace.Errorf("SELinux is only supported on Linux")
}
if !cfg.CheckServicesForSELinux() {
return nil, trace.Errorf("SELinux is only supported for the SSH service")
}
if err := selinux.CheckConfiguration(cfg.SSH.EnsureSELinuxEnforcing, cfg.Logger); err != nil {
return nil, trace.Wrap(err)
}
cfg.Logger.InfoContext(context.Background(), "SELinux support is enabled for SSH service")
}
// create the data directory if it's missing
_, err = os.Stat(cfg.DataDir)
if os.IsNotExist(err) {
@@ -3142,6 +3160,7 @@ func (process *TeleportProcess) initSSH() error {
regular.SetSessionController(sessionController),
regular.SetPublicAddrs(cfg.SSH.PublicAddrs),
regular.SetStableUNIXUsers(conn.Client.StableUNIXUsersClient()),
regular.SetSELinuxEnabled(cfg.SSH.EnableSELinux),
)
if err != nil {
return trace.Wrap(err)
+27
View File
@@ -383,6 +383,33 @@ func (c CachePolicy) String() string {
return "in-memory cache"
}
// CheckServicesForSELinux returns false if any services that don't
// support SELinux enforcement are enabled.
func (cfg *Config) CheckServicesForSELinux() bool {
switch {
case cfg.AccessGraph.Enabled:
fallthrough
case cfg.Apps.Enabled:
fallthrough
case cfg.Auth.Enabled:
fallthrough
case cfg.Databases.Enabled:
fallthrough
case cfg.Jamf.Enabled():
fallthrough
case cfg.Kube.Enabled:
fallthrough
case cfg.Okta.Enabled:
fallthrough
case cfg.Proxy.Enabled:
fallthrough
case cfg.WindowsDesktop.Enabled:
return false
}
return true
}
// AuthServerAddresses returns the value of authServers for config versions v1 and v2 and
// will return just the first (as only one should be set) address for config versions v3
// onwards.
+7
View File
@@ -71,4 +71,11 @@ type SSHConfig struct {
// path to hosts that may provide reduced latency if the Proxy is not co-located with
// the user and service.
ForceListen bool
// EnableSELinux indicates that SELinux support will be enabled.
EnableSELinux bool
// EnsureSELinuxEnforcing will exit if SELinux is not configured to
// enforce the SSH service.
EnsureSELinuxEnforcing bool
}
+5
View File
@@ -185,6 +185,10 @@ type Server interface {
// sudoer file provisioning
GetHostSudoers() HostSudoers
// GetSELinuxEnabled returns whether the node should enable SELinux
// support or not.
GetSELinuxEnabled() bool
// TargetMetadata returns metadata about the session target node.
TargetMetadata() apievents.ServerMetadata
}
@@ -1091,6 +1095,7 @@ func (c *ServerContext) ExecCommand() (*ExecCommand, error) {
PAMConfig: pamConfig,
IsTestStub: c.IsTestStub,
UaccMetadata: *uaccMetadata,
SetSELinuxContext: c.srv.GetSELinuxEnabled(),
}, nil
}
+6
View File
@@ -505,6 +505,12 @@ func (s *Server) GetHostSudoers() srv.HostSudoers {
return &srv.HostSudoersNotImplemented{}
}
// GetSELinuxEnabled returns whether the node should enable SELinux
// support or not.
func (s *Server) GetSELinuxEnabled() bool {
return false
}
// GetInfo returns a services.Server that represents this server.
func (s *Server) GetInfo() types.Server {
return &types.ServerV2{
+3
View File
@@ -717,6 +717,9 @@ func (s *ForwardServer) GetHostUsers() srv.HostUsers {
func (s *ForwardServer) GetHostSudoers() srv.HostSudoers {
return nil
}
func (s *ForwardServer) GetSELinuxEnabled() bool {
return false
}
type serverContextKey struct{}
+5
View File
@@ -305,6 +305,11 @@ func (m *mockServer) GetHostSudoers() HostSudoers {
return &HostSudoersNotImplemented{}
}
// GetSELinuxEnabled
func (m *mockServer) GetSELinuxEnabled() bool {
return false
}
// Implementation of ssh.Conn interface.
type mockSSHConn struct {
remoteAddr net.Addr
+33 -2
View File
@@ -39,12 +39,14 @@ import (
"time"
"github.com/gravitational/trace"
ocselinux "github.com/opencontainers/selinux/go-selinux"
"golang.org/x/sys/unix"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auditd"
"github.com/gravitational/teleport/lib/pam"
"github.com/gravitational/teleport/lib/selinux"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/shell"
"github.com/gravitational/teleport/lib/srv/uacc"
@@ -158,6 +160,10 @@ type ExecCommand struct {
// the parent process. These files start at file descriptor 3 of the
// child process, and are only valid for processes without a terminal.
ExtraFilesLen int `json:"extra_files_len"`
// SetSELinuxContext is true when the SELinux context should be set
// for the child.
SetSELinuxContext bool `json:"set_selinux_context"`
}
// PAMConfig represents all the configuration data that needs to be passed to the child.
@@ -192,7 +198,10 @@ type UaccMetadata struct {
}
// RunCommand reads in the command to run from the parent process (over a
// pipe) then constructs and runs the command.
// pipe) then constructs and runs the command. This function may change
// system state related to the process and/or thread for PAM and SELinux.
// The process should exit after this function returns so the potentially
// modified process and/or thread isn't used with a non-standard state.
func RunCommand() (errw io.Writer, code int, err error) {
ctx := context.Background()
@@ -326,6 +335,28 @@ func RunCommand() (errw io.Writer, code int, err error) {
pamEnvironment = pamContext.Environment()
}
// Set SELinux context for the child process if SELinux support is
// enabled so the child process will be running with the correct SELinux
// user, role and domain.
if c.SetSELinuxContext {
seContext, err := selinux.UserContext(c.Login)
if err != nil {
return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err, "failed to get SELinux context of login user")
}
// SetExecLabel changes the SELinux exec context for the
// calling thread only, so we need to ensure that is the
// thread that will create the child. We don't ever unlock
// the thread as we're exiting after the child exits, and
// we want to avoid another goroutine getting denied due to
// running on this thread with a different (likely much more
// restrictive)SELinux context.
runtime.LockOSThread()
if err := ocselinux.SetExecLabel(seContext); err != nil {
return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err, "failed to set SELinux context")
}
}
// Alert the parent process that the child process has completed any setup operations,
// and that we are now waiting for the continue signal before proceeding. This is needed
// to ensure that PAM changing the cgroup doesn't bypass enhanced recording.
@@ -405,7 +436,7 @@ func RunCommand() (errw io.Writer, code int, err error) {
}
}
return io.Discard, exitCode(err), trace.Wrap(err)
return errorWriter, exitCode(err), trace.Wrap(err)
}
// waitForShell waits either for the command to return or the kill signal from the parent Teleport process.
+18
View File
@@ -241,6 +241,9 @@ type Server struct {
// stableUnixUsers is used to obtain fallback UIDs for host user
// provisioning from the control plane.
stableUnixUsers stableunixusersv1.StableUNIXUsersServiceClient
// enableSELinux configures whether SELinux support is enable or not.
enableSELinux bool
}
// TargetMetadata returns metadata about the server.
@@ -326,6 +329,12 @@ func (s *Server) GetHostSudoers() srv.HostSudoers {
return s.sudoers
}
// GetSELinuxEnabled returns whether the node should enable SELinux
// support or not.
func (s *Server) GetSELinuxEnabled() bool {
return s.enableSELinux
}
// ServerOption is a functional option passed to the server
type ServerOption func(s *Server) error
@@ -713,6 +722,15 @@ func SetStableUNIXUsers(stableUNIXUsers stableunixusersv1.StableUNIXUsersService
}
}
// GetSELinuxEnabled returns whether the node should enable SELinux
// support or not.
func SetSELinuxEnabled(enabled bool) ServerOption {
return func(s *Server) error {
s.enableSELinux = enabled
return nil
}
}
// SetPublicAddrs sets the server's public addresses
func SetPublicAddrs(addrs []utils.NetAddr) ServerOption {
return func(s *Server) error {
+66
View File
@@ -29,6 +29,7 @@ import (
"os/user"
"path"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
@@ -48,6 +49,7 @@ import (
dtconfig "github.com/gravitational/teleport/lib/devicetrust/config"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/openssh"
"github.com/gravitational/teleport/lib/selinux"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/srv"
@@ -55,8 +57,11 @@ import (
"github.com/gravitational/teleport/lib/tpm"
"github.com/gravitational/teleport/lib/utils"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/versioncontrol"
)
const selinuxUnsupportedErr = "--enable-selinux is allowed only when the SSH service is the only service enabled"
// Options combines init/start teleport options
type Options struct {
// Args is a list of command-line args passed from main()
@@ -197,6 +202,10 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con
"AWS region AWS hosted database instance is running in.").Hidden().
StringVar(&ccf.DatabaseAWSRegion)
start.Flag("no-debug-service", "Disables debug service.").BoolVar(&ccf.DisableDebugService)
if runtime.GOOS == "linux" {
start.Flag("enable-selinux", "Enables SELinux support for Teleport SSH and exits if SELinux is not configured correctly.").Hidden().BoolVar(&ccf.EnableSELinux)
start.Flag("ensure-selinux-enforcing", "Exits with an error if SELinux is not configured to enforce Teleport SSH.").Hidden().BoolVar(&ccf.EnsureSELinuxEnforcing)
}
// define start's usage info (we use kingpin's "alias" field for this)
start.Alias(usageNotes + usageExamples)
@@ -582,6 +591,15 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con
collectProfilesCmd.Arg("PROFILES", fmt.Sprintf("Comma-separated profile names to be exported. Supported profiles: %s. Default: %s", strings.Join(slices.Collect(maps.Keys(debugclient.SupportedProfiles)), ","), strings.Join(defaultCollectProfiles, ","))).StringVar(&ccf.Profiles)
collectProfilesCmd.Flag("seconds", "For CPU and trace profiles, profile for the given duration (if set to 0, it returns a profile snapshot). For other profiles, return a delta profile. Default: 0").Short('s').Default("0").IntVar(&ccf.ProfileSeconds)
var moduleSourceCmd, fileContextsCmd, selinuxDirsCmd *kingpin.CmdClause
if runtime.GOOS == "linux" {
selinuxCmd := app.Command("selinux-ssh", "Commands related to SSH SELinux module.").Hidden()
selinuxCmd.Flag("config", fmt.Sprintf("Path to a configuration file [%v].", defaults.ConfigFilePath)).Short('c').ExistingFileVar(&ccf.ConfigFile)
moduleSourceCmd = selinuxCmd.Command("module-source", "Export SSH SELinux module source to stdout.").Hidden()
fileContextsCmd = selinuxCmd.Command("file-contexts", "Export SSH SELinux file contexts to stdout.").Hidden()
selinuxDirsCmd = selinuxCmd.Command("dirs", "Export directories that may need to be labeled for SSH SELinux module to work correctly.").Hidden()
}
backendCmd := app.Command("backend", "Commands for managing backend data.")
backendCmd.Hidden()
backendCloneCmd := backendCmd.Command("clone", "Clones data from a source to a destination backend.")
@@ -658,6 +676,11 @@ Examples:
}
}
// Validate SELinux configuration if SELinux support is enabled
if ccf.EnableSELinux && !conf.SSH.Enabled {
utils.FatalError(trace.BadParameter(selinuxUnsupportedErr))
}
if !options.InitOnly {
err = OnStart(ccf, conf)
}
@@ -747,6 +770,13 @@ Examples:
err = onGetLogLevel(ccf.ConfigFile)
case collectProfilesCmd.FullCommand():
err = onCollectProfiles(ccf.ConfigFile, ccf.Profiles, ccf.ProfileSeconds)
case moduleSourceCmd.FullCommand():
moduleSrc := selinux.ModuleSource()
fmt.Printf("%s", moduleSrc)
case fileContextsCmd.FullCommand():
err = onSELinuxFileContexts(ccf.ConfigFile)
case selinuxDirsCmd.FullCommand():
err = onSELinuxDirs(ccf.ConfigFile)
case backendCloneCmd.FullCommand():
err = onClone(context.Background(), ccf.ConfigFile)
}
@@ -1096,3 +1126,39 @@ func onJoinOpenSSH(clf config.CommandLineFlags, conf *servicecfg.Config) error {
}
return nil
}
func onSELinuxFileContexts(configPath string) error {
// Explicitly set the default config path so ReadConfigFile won't
// return (nil, nil) if configPath is unset and the default config
// path doesn't have a file
if configPath == "" {
configPath = defaults.ConfigFilePath
}
cfg, err := config.ReadConfigFile(configPath)
if err != nil {
return trace.Wrap(err)
}
fileContexts, err := selinux.FileContexts("/opt/teleport/default", cfg.DataDir, configPath)
if err != nil {
return trace.Wrap(err)
}
fmt.Println(fileContexts)
return nil
}
func onSELinuxDirs(configPath string) error {
// Explicitly set the default config path so ReadConfigFile won't
// return (nil, nil) if configPath is unset and the default config
// path doesn't have a file
if configPath == "" {
configPath = defaults.ConfigFilePath
}
cfg, err := config.ReadConfigFile(configPath)
if err != nil {
return trace.Wrap(err)
}
fmt.Printf("%s\n%s\n%s\n%s\n", "/opt/teleport/default", cfg.DataDir, filepath.Dir(configPath), versioncontrol.UnitConfigDir)
return nil
}