mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
add SELinux module for SSH service (#53424)
This commit is contained in:
Executable
+64
@@ -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}"
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
')
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{}
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user