mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
747bb34e7d
The reexec process now has a two way wait mechanism to allow the child process to complete any setup operations that may be required before the parent process starts enhanced recording. The old process was: 1) Parent launches child process 2) Child process opens PAM context and blocks on the continue signal 3) Parent sets up enhanced recording 4) Parent sends the continue signal 5) Child executes command/opens shell The new process is: 1) Parent launches child process and waits for child continue signal 2) Child process opens PAM context and then signals it has completed setup 3) Parent receives child continue signal and sets up enhanced recording 4) Parent sends the continue signal 5) Child executes command/opens shell Closes #29030
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
/*
|
|
Copyright 2015-2018 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.
|
|
*/
|
|
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|