Files
teleport/lib/srv/exec_linux_test.go
T
Mike Jensen fcc97de9f9 Better control on user injected environment values (#36132)
* 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 of a9055bccd9 it 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 to a9055bccd9.

* Update filter to exclude all DYLD_ and LD_ prefixed variables
2023-12-29 19:25:53 +00:00

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)
}
}