mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
To prevent users from being able to directly connect to registered OpenSSH (Agentless) nodes, the OpenSSH CA was introduced. When connecting to Agentless nodes, the Proxy will forward the connection so the session can be recorded. The forwarding SSH server is configured to authenticate to the target node with a certificate signed by OpenSSH CA, which users do not have access to.
702 lines
17 KiB
Go
702 lines
17 KiB
Go
/*
|
|
Copyright 2015 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 (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/creack/pty"
|
|
"github.com/gravitational/trace"
|
|
"github.com/moby/term"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/gravitational/teleport"
|
|
tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
rsession "github.com/gravitational/teleport/lib/session"
|
|
)
|
|
|
|
// LookupUser is used to mock the value returned by user.Lookup(string).
|
|
type LookupUser func(string) (*user.User, error)
|
|
|
|
// LookupGroup is used to mock the value returned by user.LookupGroup(string).
|
|
type LookupGroup func(string) (*user.Group, error)
|
|
|
|
// Terminal defines an interface of handy functions for managing a (local or
|
|
// remote) PTY, such as resizing windows, executing commands with a PTY, and
|
|
// cleaning up.
|
|
type Terminal interface {
|
|
// AddParty adds another participant to this terminal. We will keep the
|
|
// Terminal open until all participants have left.
|
|
AddParty(delta int)
|
|
|
|
// Run will run the terminal.
|
|
Run(ctx context.Context) error
|
|
|
|
// Wait will block until the terminal is complete.
|
|
Wait() (*ExecResult, error)
|
|
|
|
// Continue will resume execution of the process after it completes its
|
|
// pre-processing routine (placed in a cgroup).
|
|
Continue()
|
|
|
|
// KillUnderlyingShell tries to gracefully stop the terminal process.
|
|
KillUnderlyingShell(ctx context.Context) error
|
|
|
|
// Kill will force kill the terminal.
|
|
Kill(ctx context.Context) error
|
|
|
|
// PTY returns the PTY backing the terminal.
|
|
PTY() io.ReadWriter
|
|
|
|
// TTY returns the TTY backing the terminal.
|
|
TTY() *os.File
|
|
|
|
// PID returns the PID of the Teleport process that was re-execed.
|
|
PID() int
|
|
|
|
// Close will free resources associated with the terminal.
|
|
Close() error
|
|
|
|
// GetWinSize returns the window size of the terminal.
|
|
GetWinSize() (*term.Winsize, error)
|
|
|
|
// SetWinSize sets the window size of the terminal.
|
|
SetWinSize(ctx context.Context, params rsession.TerminalParams) error
|
|
|
|
// GetTerminalParams is a fast call to get cached terminal parameters
|
|
// and avoid extra system call.
|
|
GetTerminalParams() rsession.TerminalParams
|
|
|
|
// SetTerminalModes sets the terminal modes from "pty-req"
|
|
SetTerminalModes(ssh.TerminalModes)
|
|
|
|
// GetTermType gets the terminal type set in "pty-req"
|
|
GetTermType() string
|
|
|
|
// SetTermType sets the terminal type from "pty-req"
|
|
SetTermType(string)
|
|
}
|
|
|
|
// NewTerminal returns a new terminal. Terminal can be local or remote
|
|
// depending on cluster configuration.
|
|
func NewTerminal(ctx *ServerContext) (Terminal, error) {
|
|
// It doesn't matter what mode the cluster is in, if this is a Teleport node
|
|
// return a local terminal.
|
|
if ctx.srv.Component() == teleport.ComponentNode {
|
|
return newLocalTerminal(ctx)
|
|
}
|
|
|
|
// If this is not a Teleport node, find out what mode the cluster is in and
|
|
// return the correct terminal.
|
|
if ctx.ServerSubKind == types.SubKindOpenSSHNode || services.IsRecordAtProxy(ctx.SessionRecordingConfig.GetMode()) {
|
|
return newRemoteTerminal(ctx)
|
|
}
|
|
return newLocalTerminal(ctx)
|
|
}
|
|
|
|
// terminal is a local PTY created by Teleport nodes.
|
|
type terminal struct {
|
|
wg sync.WaitGroup
|
|
mu sync.Mutex
|
|
|
|
log *log.Entry
|
|
|
|
cmd *exec.Cmd
|
|
serverContext *ServerContext
|
|
|
|
pty *os.File
|
|
tty *os.File
|
|
|
|
// terminateFD when closed informs the terminal that
|
|
// the process running in the shell should be killed.
|
|
terminateFD *os.File
|
|
|
|
pid int
|
|
|
|
termType string
|
|
params rsession.TerminalParams
|
|
}
|
|
|
|
// NewLocalTerminal creates and returns a local PTY.
|
|
func newLocalTerminal(ctx *ServerContext) (*terminal, error) {
|
|
var err error
|
|
|
|
t := &terminal{
|
|
log: log.WithFields(log.Fields{
|
|
trace.Component: teleport.ComponentLocalTerm,
|
|
}),
|
|
serverContext: ctx,
|
|
terminateFD: ctx.killShellw,
|
|
}
|
|
|
|
// Open PTY and corresponding TTY.
|
|
t.pty, t.tty, err = pty.Open()
|
|
if err != nil {
|
|
log.Warnf("Could not start PTY: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
// Set the TTY owner. Failure is not fatal, for example Teleport is running
|
|
// on a read-only filesystem, but logging is useful for diagnostic purposes.
|
|
err = t.setOwner()
|
|
if err != nil {
|
|
log.Debugf("Unable to set TTY owner: %v.\n", err)
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// AddParty adds another participant to this terminal. We will keep the
|
|
// Terminal open until all participants have left.
|
|
func (t *terminal) AddParty(delta int) {
|
|
t.wg.Add(delta)
|
|
}
|
|
|
|
// Run will run the terminal.
|
|
func (t *terminal) Run(ctx context.Context) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
var err error
|
|
// Create the command that will actually execute.
|
|
t.cmd, err = ConfigureCommand(t.serverContext)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// we need the lock here to protect from concurrent calls to Close()
|
|
t.mu.Lock()
|
|
pty, tty := t.pty, t.tty
|
|
t.mu.Unlock()
|
|
|
|
// Pass PTY and TTY to child as well since a terminal is attached.
|
|
t.cmd.ExtraFiles = append(t.cmd.ExtraFiles, pty)
|
|
t.cmd.ExtraFiles = append(t.cmd.ExtraFiles, tty)
|
|
|
|
// Start the process.
|
|
err = t.cmd.Start()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Save off the PID of the Teleport process under which the shell is executing.
|
|
t.pid = t.cmd.Process.Pid
|
|
|
|
return nil
|
|
}
|
|
|
|
// Wait will block until the terminal is complete.
|
|
func (t *terminal) Wait() (*ExecResult, error) {
|
|
err := t.cmd.Wait()
|
|
if err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
status := exitErr.Sys().(syscall.WaitStatus)
|
|
return &ExecResult{Code: status.ExitStatus(), Command: t.cmd.Path}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
status, ok := t.cmd.ProcessState.Sys().(syscall.WaitStatus)
|
|
if !ok {
|
|
return nil, trace.Errorf("unknown exit status: %T(%v)", t.cmd.ProcessState.Sys(), t.cmd.ProcessState.Sys())
|
|
}
|
|
|
|
return &ExecResult{
|
|
Code: status.ExitStatus(),
|
|
Command: t.cmd.Path,
|
|
}, nil
|
|
}
|
|
|
|
// Continue will resume execution of the process after it completes its
|
|
// pre-processing routine (placed in a cgroup).
|
|
func (t *terminal) Continue() {
|
|
if err := t.serverContext.contw.Close(); err != nil {
|
|
t.log.Warnf("failed to close server context")
|
|
}
|
|
}
|
|
|
|
// KillUnderlyingShell tries to kill the shell/bash process and waits for the process PID to be released.
|
|
func (t *terminal) KillUnderlyingShell(ctx context.Context) error {
|
|
if err := t.terminateFD.Close(); err != nil {
|
|
if !errors.Is(err, os.ErrClosed) {
|
|
t.log.WithError(err).Debug("Failed to close the shell file descriptor")
|
|
}
|
|
}
|
|
|
|
pid := t.PID()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
proc, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return trace.Errorf("failed to find the shell process: %w", err)
|
|
}
|
|
|
|
if err := proc.Signal(syscall.Signal(0)); errors.Is(err, os.ErrProcessDone) {
|
|
t.log.Debugf("Terminal child process has been stopped")
|
|
return nil
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Kill will force kill the child Teleport process.
|
|
func (t *terminal) Kill(_ context.Context) error {
|
|
if t.cmd != nil && t.cmd.Process != nil {
|
|
if err := t.cmd.Process.Kill(); err != nil {
|
|
if err.Error() != "os: process already finished" {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PTY returns the PTY backing the terminal.
|
|
func (t *terminal) PTY() io.ReadWriter {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.pty
|
|
}
|
|
|
|
// TTY returns the TTY backing the terminal.
|
|
func (t *terminal) TTY() *os.File {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.tty
|
|
}
|
|
|
|
// PID returns the PID of the Teleport process that was re-execed.
|
|
func (t *terminal) PID() int {
|
|
return t.pid
|
|
}
|
|
|
|
// Close will free resources associated with the terminal.
|
|
func (t *terminal) Close() error {
|
|
err := t.closeTTY()
|
|
// note, pty is closed in the copying goroutine,
|
|
// not here to avoid data races
|
|
go t.closePTY()
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
func (t *terminal) closeTTY() error {
|
|
t.log.Debugf("Closing TTY")
|
|
defer t.log.Debugf("Closed TTY")
|
|
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
if t.tty == nil {
|
|
t.log.Debugf("TTY already closed")
|
|
return nil
|
|
}
|
|
|
|
err := t.tty.Close()
|
|
t.tty = nil
|
|
|
|
if err != nil {
|
|
t.log.Warnf("Failed to close TTY: %v", err)
|
|
}
|
|
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
func (t *terminal) closePTY() {
|
|
defer t.log.Debugf("Closed PTY")
|
|
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
// wait until all copying is over (all participants have left)
|
|
t.wg.Wait()
|
|
|
|
if t.pty == nil {
|
|
return
|
|
}
|
|
|
|
if err := t.pty.Close(); err != nil {
|
|
t.log.Warnf("Failed to close PTY: %v", err)
|
|
}
|
|
t.pty = nil
|
|
}
|
|
|
|
// GetWinSize returns the window size of the terminal.
|
|
func (t *terminal) GetWinSize() (*term.Winsize, error) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
if t.pty == nil {
|
|
return nil, trace.NotFound("no pty")
|
|
}
|
|
ws, err := term.GetWinsize(t.pty.Fd())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return ws, nil
|
|
}
|
|
|
|
// SetWinSize sets the window size of the terminal.
|
|
func (t *terminal) SetWinSize(ctx context.Context, params rsession.TerminalParams) error {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
if t.pty == nil {
|
|
return trace.NotFound("no pty")
|
|
}
|
|
if err := term.SetWinsize(t.pty.Fd(), params.Winsize()); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
t.params = params
|
|
return nil
|
|
}
|
|
|
|
// GetTerminalParams is a fast call to get cached terminal parameters
|
|
// and avoid extra system call.
|
|
func (t *terminal) GetTerminalParams() rsession.TerminalParams {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.params
|
|
}
|
|
|
|
// GetTermType gets the terminal type set in "pty-req"
|
|
func (t *terminal) GetTermType() string {
|
|
return t.termType
|
|
}
|
|
|
|
// SetTermType sets the terminal type from "req-pty" request.
|
|
func (t *terminal) SetTermType(term string) {
|
|
if term == "" {
|
|
term = defaultTerm
|
|
}
|
|
t.termType = term
|
|
}
|
|
|
|
func (t *terminal) SetTerminalModes(termModes ssh.TerminalModes) {}
|
|
|
|
// getOwner determines the uid, gid, and mode of the TTY similar to OpenSSH:
|
|
// https://github.com/openssh/openssh-portable/blob/ddc0f38/sshpty.c#L164-L215
|
|
func getOwner(login string, lookupUser LookupUser, lookupGroup LookupGroup) (int, int, os.FileMode, error) {
|
|
var err error
|
|
var uid int
|
|
var gid int
|
|
var mode os.FileMode
|
|
|
|
// Lookup the Unix login for the UID and fallback GID.
|
|
u, err := lookupUser(login)
|
|
if err != nil {
|
|
return 0, 0, 0, trace.Wrap(err)
|
|
}
|
|
uid, err = strconv.Atoi(u.Uid)
|
|
if err != nil {
|
|
return 0, 0, 0, trace.Wrap(err)
|
|
}
|
|
|
|
// If the tty group exists, use that as the gid of the TTY and set mode to
|
|
// be u+rw. Otherwise use the group of the user with mode u+rw g+w.
|
|
group, err := lookupGroup("tty")
|
|
if err != nil {
|
|
gid, err = strconv.Atoi(u.Gid)
|
|
if err != nil {
|
|
return 0, 0, 0, trace.Wrap(err)
|
|
}
|
|
mode = 0o620
|
|
} else {
|
|
gid, err = strconv.Atoi(group.Gid)
|
|
if err != nil {
|
|
return 0, 0, 0, trace.Wrap(err)
|
|
}
|
|
mode = 0o600
|
|
}
|
|
|
|
return uid, gid, mode, nil
|
|
}
|
|
|
|
// setOwner changes the owner and mode of the TTY.
|
|
func (t *terminal) setOwner() error {
|
|
uid, gid, mode, err := getOwner(t.serverContext.Identity.Login, user.Lookup, user.LookupGroup)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = os.Chown(t.tty.Name(), uid, gid)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
err = os.Chmod(t.tty.Name(), mode)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
log.Debugf("Set permissions on %v to %v:%v with mode %v.", t.tty.Name(), uid, gid, mode)
|
|
|
|
return nil
|
|
}
|
|
|
|
type remoteTerminal struct {
|
|
wg sync.WaitGroup
|
|
mu sync.Mutex
|
|
|
|
log *log.Entry
|
|
|
|
ctx *ServerContext
|
|
|
|
session *tracessh.Session
|
|
params rsession.TerminalParams
|
|
termModes ssh.TerminalModes
|
|
ptyBuffer *ptyBuffer
|
|
termType string
|
|
}
|
|
|
|
func newRemoteTerminal(ctx *ServerContext) (*remoteTerminal, error) {
|
|
if ctx.RemoteSession == nil {
|
|
return nil, trace.BadParameter("remote session required")
|
|
}
|
|
|
|
t := &remoteTerminal{
|
|
log: log.WithFields(log.Fields{
|
|
trace.Component: teleport.ComponentRemoteTerm,
|
|
}),
|
|
ctx: ctx,
|
|
session: ctx.RemoteSession,
|
|
ptyBuffer: &ptyBuffer{},
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (t *remoteTerminal) AddParty(delta int) {
|
|
t.wg.Add(delta)
|
|
}
|
|
|
|
type ptyBuffer struct {
|
|
r io.Reader
|
|
w io.Writer
|
|
}
|
|
|
|
func (b *ptyBuffer) Read(p []byte) (n int, err error) {
|
|
return b.r.Read(p)
|
|
}
|
|
|
|
func (b *ptyBuffer) Write(p []byte) (n int, err error) {
|
|
return b.w.Write(p)
|
|
}
|
|
|
|
func (t *remoteTerminal) Run(ctx context.Context) error {
|
|
// prepare the remote session by setting environment variables
|
|
t.prepareRemoteSession(ctx, t.session, t.ctx)
|
|
|
|
// combine stdout and stderr
|
|
stdout, err := t.session.StdoutPipe()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
t.session.Stderr = t.session.Stdout
|
|
stdin, err := t.session.StdinPipe()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// create a pty buffer that stdin and stdout are hooked up to
|
|
t.ptyBuffer = &ptyBuffer{
|
|
r: stdout,
|
|
w: stdin,
|
|
}
|
|
|
|
// if a specific term type was not requested, then pick the default one and request a pty
|
|
if t.termType == "" {
|
|
t.termType = defaultTerm
|
|
}
|
|
|
|
if err := t.session.RequestPty(ctx, t.termType, t.params.H, t.params.W, t.termModes); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// we want to run a "exec" command within a pty
|
|
if execRequest, err := t.ctx.GetExecRequest(); err == nil && execRequest.GetCommand() != "" {
|
|
t.log.Debugf("Running exec request within a PTY")
|
|
|
|
if err := t.session.Start(ctx, execRequest.GetCommand()); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// we want an interactive shell
|
|
t.log.Debugf("Requesting an interactive terminal of type %v", t.termType)
|
|
if err := t.session.Shell(ctx); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *remoteTerminal) Wait() (*ExecResult, error) {
|
|
execRequest, err := t.ctx.GetExecRequest()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = t.session.Wait()
|
|
if err != nil {
|
|
if exitErr, ok := err.(*ssh.ExitError); ok {
|
|
return &ExecResult{
|
|
Code: exitErr.ExitStatus(),
|
|
Command: execRequest.GetCommand(),
|
|
}, err
|
|
}
|
|
|
|
return &ExecResult{
|
|
Code: teleport.RemoteCommandFailure,
|
|
Command: execRequest.GetCommand(),
|
|
}, err
|
|
}
|
|
|
|
return &ExecResult{
|
|
Code: teleport.RemoteCommandSuccess,
|
|
Command: execRequest.GetCommand(),
|
|
}, nil
|
|
}
|
|
|
|
// Continue does nothing for remote command execution.
|
|
func (t *remoteTerminal) Continue() {}
|
|
|
|
// Terminate does nothing for remote command execution.
|
|
func (t *remoteTerminal) KillUnderlyingShell(_ context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *remoteTerminal) Kill(ctx context.Context) error {
|
|
err := t.session.Signal(ctx, ssh.SIGKILL)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *remoteTerminal) PTY() io.ReadWriter {
|
|
return t.ptyBuffer
|
|
}
|
|
|
|
func (t *remoteTerminal) TTY() *os.File {
|
|
return nil
|
|
}
|
|
|
|
// PID returns the PID of the Teleport process that was re-execed. Always
|
|
// returns 0 for remote terminals.
|
|
func (t *remoteTerminal) PID() int {
|
|
return 0
|
|
}
|
|
|
|
func (t *remoteTerminal) Close() error {
|
|
t.wg.Wait()
|
|
// this closes the underlying stdin,stdout,stderr which is what ptyBuffer is
|
|
// hooked to directly
|
|
err := t.session.Close()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
t.log.Debugf("Closed remote terminal and underlying SSH session")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *remoteTerminal) GetWinSize() (*term.Winsize, error) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
return t.params.Winsize(), nil
|
|
}
|
|
|
|
func (t *remoteTerminal) SetWinSize(ctx context.Context, params rsession.TerminalParams) error {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
err := t.windowChange(ctx, params.W, params.H)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
t.params = params
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *remoteTerminal) GetTerminalParams() rsession.TerminalParams {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
return t.params
|
|
}
|
|
|
|
// GetTermType gets the terminal type set in "pty-req"
|
|
func (t *remoteTerminal) GetTermType() string {
|
|
return t.termType
|
|
}
|
|
|
|
func (t *remoteTerminal) SetTermType(term string) {
|
|
if term == "" {
|
|
term = defaultTerm
|
|
}
|
|
t.termType = term
|
|
}
|
|
|
|
func (t *remoteTerminal) SetTerminalModes(termModes ssh.TerminalModes) {
|
|
t.termModes = termModes
|
|
}
|
|
|
|
func (t *remoteTerminal) windowChange(ctx context.Context, w int, h int) error {
|
|
return trace.Wrap(t.session.WindowChange(ctx, h, w))
|
|
}
|
|
|
|
// prepareRemoteSession prepares the more session for execution.
|
|
func (t *remoteTerminal) prepareRemoteSession(ctx context.Context, session *tracessh.Session, scx *ServerContext) {
|
|
envs := map[string]string{
|
|
teleport.SSHTeleportUser: scx.Identity.TeleportUser,
|
|
teleport.SSHTeleportHostUUID: scx.srv.ID(),
|
|
teleport.SSHTeleportClusterName: scx.ClusterName,
|
|
teleport.SSHSessionID: string(scx.SessionID()),
|
|
}
|
|
|
|
for k, v := range envs {
|
|
if err := session.Setenv(ctx, k, v); err != nil {
|
|
t.log.Debugf("Unable to set environment variable: %v: %v", k, v)
|
|
}
|
|
}
|
|
}
|