mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
* Better control on user injected environment values This commit includes two changes: * In `environment` we expanded the list of MacOS environment values which should be filtered. It was demonstrated that these can be used to get code execution in MacOS. * In `reexec` we no longer provide the cmdmsg.Environment for the `teleport exec`. As part of the development ofa9055bccd9it was attempted to fully clear out the environment, but testing showed that to be potentially problematic. It was believed the safest option was to use the cmd environment, however this introduces a new source of environment variables. Because this exec happens under `root` this is particularly dangerous (even more so when combined with the missed OSX values mentioned above). As such we now are only providing the filtered exec environment, which is a closer (but safer) option to the functionality prior toa9055bccd9. * Update filter to exclude all DYLD_ and LD_ prefixed variables
189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 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 srv
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestOSCommandPrep(t *testing.T) {
|
|
srv := newMockServer(t)
|
|
scx := newExecServerContext(t, srv)
|
|
|
|
usr, err := user.Current()
|
|
require.NoError(t, err)
|
|
|
|
expectedEnv := []string{
|
|
"LANG=en_US.UTF-8",
|
|
getDefaultEnvPath(strconv.Itoa(os.Geteuid()), defaultLoginDefsPath),
|
|
fmt.Sprintf("HOME=%s", usr.HomeDir),
|
|
fmt.Sprintf("USER=%s", usr.Username),
|
|
"SHELL=/bin/sh",
|
|
"SSH_CLIENT=10.0.0.5 4817 3022",
|
|
"SSH_CONNECTION=10.0.0.5 4817 127.0.0.1 3022",
|
|
"TERM=xterm",
|
|
fmt.Sprintf("SSH_TTY=%v", scx.session.term.TTY().Name()),
|
|
"SSH_SESSION_ID=xxx",
|
|
"SSH_TELEPORT_HOST_UUID=testID",
|
|
"SSH_TELEPORT_CLUSTER_NAME=localhost",
|
|
"SSH_TELEPORT_USER=teleportUser",
|
|
}
|
|
|
|
// Empty command (simple shell).
|
|
execCmd, err := scx.ExecCommand()
|
|
require.NoError(t, err)
|
|
|
|
cmd, err := buildCommand(execCmd, usr, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, cmd)
|
|
require.Equal(t, "/bin/sh", cmd.Path)
|
|
require.Equal(t, []string{"-sh"}, cmd.Args)
|
|
require.Equal(t, usr.HomeDir, cmd.Dir)
|
|
require.Equal(t, expectedEnv, cmd.Env)
|
|
require.Equal(t, syscall.SIGKILL, cmd.SysProcAttr.Pdeathsig)
|
|
|
|
// Non-empty command (exec a prog).
|
|
scx.execRequest.SetCommand("ls -lh /etc")
|
|
execCmd, err = scx.ExecCommand()
|
|
require.NoError(t, err)
|
|
|
|
cmd, err = buildCommand(execCmd, usr, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, cmd)
|
|
require.Equal(t, "/bin/sh", cmd.Path)
|
|
require.Equal(t, []string{"/bin/sh", "-c", "ls -lh /etc"}, cmd.Args)
|
|
require.Equal(t, usr.HomeDir, cmd.Dir)
|
|
require.Equal(t, expectedEnv, cmd.Env)
|
|
require.Equal(t, syscall.SIGKILL, cmd.SysProcAttr.Pdeathsig)
|
|
|
|
// Command without args.
|
|
scx.execRequest.SetCommand("top")
|
|
execCmd, err = scx.ExecCommand()
|
|
require.NoError(t, err)
|
|
|
|
cmd, err = buildCommand(execCmd, usr, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "/bin/sh", cmd.Path)
|
|
require.Equal(t, []string{"/bin/sh", "-c", "top"}, cmd.Args)
|
|
require.Equal(t, syscall.SIGKILL, cmd.SysProcAttr.Pdeathsig)
|
|
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("skipping portion of test which must run as root")
|
|
}
|
|
|
|
// Missing home directory - HOME should still be set to the given
|
|
// home dir, but the command should set it's CWD to root instead.
|
|
usr.HomeDir = "/wrong/place"
|
|
root := string(os.PathSeparator)
|
|
expectedEnv[2] = "HOME=/wrong/place"
|
|
cmd, err = buildCommand(execCmd, usr, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, root, cmd.Dir)
|
|
require.Equal(t, expectedEnv, cmd.Env)
|
|
}
|
|
|
|
func TestConfigureCommand(t *testing.T) {
|
|
srv := newMockServer(t)
|
|
scx := newExecServerContext(t, srv)
|
|
|
|
unexpectedKey := "FOO"
|
|
unexpectedValue := "BAR"
|
|
// environment values in the server context should not be forwarded
|
|
scx.SetEnv(unexpectedKey, unexpectedValue)
|
|
|
|
cmd, err := ConfigureCommand(scx)
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, cmd)
|
|
require.Equal(t, "/proc/self/exe", cmd.Path)
|
|
require.NotContains(t, cmd.Env, unexpectedKey+"="+unexpectedValue)
|
|
}
|
|
|
|
// TestContinue tests if the process hangs if a continue signal is not sent
|
|
// and makes sure the process continues once it has been sent.
|
|
func TestContinue(t *testing.T) {
|
|
srv := newMockServer(t)
|
|
scx := newExecServerContext(t, srv)
|
|
|
|
// Configure Session Context to re-exec "ls".
|
|
var err error
|
|
lsPath, err := exec.LookPath("ls")
|
|
require.NoError(t, err)
|
|
scx.execRequest.SetCommand(lsPath)
|
|
|
|
// Create an exec.Cmd to execute through Teleport.
|
|
cmd, err := ConfigureCommand(scx)
|
|
require.NoError(t, err)
|
|
|
|
// Create a channel that will be used to signal that execution is complete.
|
|
cmdDone := make(chan error, 1)
|
|
|
|
// Re-execute Teleport and run "ls". Signal over the context when execution
|
|
// is complete.
|
|
go func() {
|
|
if err := cmd.Start(); err != nil {
|
|
cmdDone <- err
|
|
}
|
|
|
|
// Close the read half of the pipe to unblock the ready signal.
|
|
closeErr := scx.readyw.Close()
|
|
cmdDone <- trace.NewAggregate(closeErr, cmd.Wait())
|
|
}()
|
|
|
|
// Wait for the process. Since the continue pipe has not been closed, the
|
|
// process should not have exited yet.
|
|
select {
|
|
case err := <-cmdDone:
|
|
t.Fatalf("Process exited before continue with error %v", err)
|
|
case <-time.After(5 * time.Second):
|
|
}
|
|
|
|
// Wait for the child process to indicate its completed initialization.
|
|
require.NoError(t, scx.execRequest.WaitForChild())
|
|
|
|
// Signal to child that it may execute the requested program.
|
|
scx.execRequest.Continue()
|
|
|
|
// Program should have executed now. If the complete signal has not come
|
|
// over the context, something failed.
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("Timed out waiting for process to finish.")
|
|
case err := <-cmdDone:
|
|
require.NoError(t, err)
|
|
}
|
|
}
|