mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
This change fixes a bug with client idle timeouts that could cause sessions to end too early.
1266 lines
39 KiB
Go
1266 lines
39 KiB
Go
/*
|
|
Copyright 2017 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 forward
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/agent"
|
|
|
|
"github.com/gravitational/teleport"
|
|
apidefaults "github.com/gravitational/teleport/api/defaults"
|
|
"github.com/gravitational/teleport/api/observability/tracing"
|
|
tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh"
|
|
"github.com/gravitational/teleport/api/types"
|
|
apievents "github.com/gravitational/teleport/api/types/events"
|
|
"github.com/gravitational/teleport/lib/auth"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/pam"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/srv"
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
|
"github.com/gravitational/teleport/lib/sshutils/x11"
|
|
"github.com/gravitational/teleport/lib/teleagent"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/sirupsen/logrus"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
|
|
oteltrace "go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// Server is a forwarding server. Server is used to create a single in-memory
|
|
// SSH server that will forward connections to a remote server. It's used along
|
|
// with the recording proxy to allow Teleport to record sessions with OpenSSH
|
|
// nodes at the proxy level.
|
|
//
|
|
// To create a forwarding server and serve a single SSH connection on it:
|
|
//
|
|
// serverConfig := forward.ServerConfig{
|
|
// ...
|
|
// }
|
|
// remoteServer, err := forward.New(serverConfig)
|
|
// if err != nil {
|
|
// return nil, trace.Wrap(err)
|
|
// }
|
|
// go remoteServer.Serve()
|
|
//
|
|
// conn, err := remoteServer.Dial()
|
|
// if err != nil {
|
|
// return nil, trace.Wrap(err)
|
|
// }
|
|
type Server struct {
|
|
log *logrus.Entry
|
|
|
|
id string
|
|
|
|
// targetConn is the TCP connection to the remote host.
|
|
targetConn net.Conn
|
|
|
|
// clientConn is the client half of the pipe used to connect the client
|
|
// and server.
|
|
clientConn net.Conn
|
|
|
|
// serverConn is the server half of the pipe used to connect the client and
|
|
// server.
|
|
serverConn net.Conn
|
|
|
|
// sconn is an authenticated SSH connection from the server perspective.
|
|
sconn *ssh.ServerConn
|
|
|
|
// remoteClient exposes an API to SSH functionality like shells, port
|
|
// forwarding, subsystems.
|
|
remoteClient *tracessh.Client
|
|
|
|
// connectionContext is used to construct ServerContext instances
|
|
// and supports registration of connection-scoped resource closers.
|
|
connectionContext *sshutils.ConnectionContext
|
|
|
|
// identityContext holds identity information about the user that has
|
|
// authenticated on sconn (like system login, Teleport username, roles).
|
|
identityContext srv.IdentityContext
|
|
|
|
// userAgent is the SSH user agent that was forwarded to the proxy.
|
|
userAgent teleagent.Agent
|
|
|
|
// hostCertificate is the SSH host certificate this in-memory server presents
|
|
// to the client.
|
|
hostCertificate ssh.Signer
|
|
|
|
// StreamEmitter points to the auth service and emits audit events
|
|
events.StreamEmitter
|
|
|
|
// authHandlers are common authorization and authentication handlers shared
|
|
// by the regular and forwarding server.
|
|
authHandlers *srv.AuthHandlers
|
|
// termHandlers are common terminal handlers shared by the regular and
|
|
// forwarding server.
|
|
termHandlers *srv.TermHandlers
|
|
|
|
// useTunnel indicates of this server is connected over a reverse tunnel.
|
|
useTunnel bool
|
|
|
|
// address is the name of the host certificate.
|
|
address string
|
|
|
|
// ciphers is a list of ciphers that the server supports. If omitted,
|
|
// the defaults will be used.
|
|
ciphers []string
|
|
// kexAlgorithms is a list of key exchange (KEX) algorithms that the
|
|
// server supports. If omitted, the defaults will be used.
|
|
kexAlgorithms []string
|
|
// macAlgorithms is a list of message authentication codes (MAC) that
|
|
// the server supports. If omitted the defaults will be used.
|
|
macAlgorithms []string
|
|
|
|
authClient auth.ClientI
|
|
authService srv.AccessPoint
|
|
sessionRegistry *srv.SessionRegistry
|
|
dataDir string
|
|
|
|
clock clockwork.Clock
|
|
|
|
// hostUUID is the UUID of the underlying proxy that the forwarding server
|
|
// is running in.
|
|
hostUUID string
|
|
|
|
// closeContext and closeCancel are used to signal to the outside
|
|
// world that this server is closed
|
|
closeContext context.Context
|
|
closeCancel context.CancelFunc
|
|
|
|
// parentContext is used to signal server closure
|
|
parentContext context.Context
|
|
|
|
// lockWatcher is the server's lock watcher.
|
|
lockWatcher *services.LockWatcher
|
|
|
|
// tracerProvider is used to create tracers capable
|
|
// of starting spans.
|
|
tracerProvider oteltrace.TracerProvider
|
|
|
|
targetID, targetAddr, targetHostname string
|
|
}
|
|
|
|
// ServerConfig is the configuration needed to create an instance of a Server.
|
|
type ServerConfig struct {
|
|
AuthClient auth.ClientI
|
|
UserAgent teleagent.Agent
|
|
TargetConn net.Conn
|
|
SrcAddr net.Addr
|
|
DstAddr net.Addr
|
|
HostCertificate ssh.Signer
|
|
|
|
// UseTunnel indicates of this server is connected over a reverse tunnel.
|
|
UseTunnel bool
|
|
|
|
// Address is the name of the host certificate.
|
|
Address string
|
|
|
|
// Ciphers is a list of ciphers that the server supports. If omitted,
|
|
// the defaults will be used.
|
|
Ciphers []string
|
|
|
|
// KEXAlgorithms is a list of key exchange (KEX) algorithms that the
|
|
// server supports. If omitted, the defaults will be used.
|
|
KEXAlgorithms []string
|
|
|
|
// MACAlgorithms is a list of message authentication codes (MAC) that
|
|
// the server supports. If omitted the defaults will be used.
|
|
MACAlgorithms []string
|
|
|
|
// DataDir is a local data directory used for local server storage
|
|
DataDir string
|
|
|
|
// Clock is an optoinal clock to override default real time clock
|
|
Clock clockwork.Clock
|
|
|
|
// FIPS mode means Teleport started in a FedRAMP/FIPS 140-2 compliant
|
|
// configuration.
|
|
FIPS bool
|
|
|
|
// HostUUID is the UUID of the underlying proxy that the forwarding server
|
|
// is running in.
|
|
HostUUID string
|
|
|
|
// Emitter is audit events emitter
|
|
Emitter events.StreamEmitter
|
|
|
|
// ParentContext is a parent context, used to signal global
|
|
// closure
|
|
ParentContext context.Context
|
|
|
|
// LockWatcher is a lock watcher.
|
|
LockWatcher *services.LockWatcher
|
|
|
|
// TracerProvider is used to create tracers capable
|
|
// of starting spans.
|
|
TracerProvider oteltrace.TracerProvider
|
|
|
|
TargetID, TargetAddr, TargetHostname string
|
|
}
|
|
|
|
// CheckDefaults makes sure all required parameters are passed in.
|
|
func (s *ServerConfig) CheckDefaults() error {
|
|
if s.AuthClient == nil {
|
|
return trace.BadParameter("auth client required")
|
|
}
|
|
if s.DataDir == "" {
|
|
return trace.BadParameter("missing parameter DataDir")
|
|
}
|
|
if s.UserAgent == nil {
|
|
return trace.BadParameter("user agent required to connect to remote host")
|
|
}
|
|
if s.TargetConn == nil {
|
|
return trace.BadParameter("connection to target connection required")
|
|
}
|
|
if s.SrcAddr == nil {
|
|
return trace.BadParameter("source address required to identify client")
|
|
}
|
|
if s.DstAddr == nil {
|
|
return trace.BadParameter("destination address required to connect to remote host")
|
|
}
|
|
if s.HostCertificate == nil {
|
|
return trace.BadParameter("host certificate required to act on behalf of remote host")
|
|
}
|
|
if s.Clock == nil {
|
|
s.Clock = clockwork.NewRealClock()
|
|
}
|
|
if s.Emitter == nil {
|
|
return trace.BadParameter("missing parameter Emitter")
|
|
}
|
|
if s.ParentContext == nil {
|
|
s.ParentContext = context.TODO()
|
|
}
|
|
if s.LockWatcher == nil {
|
|
return trace.BadParameter("missing parameter LockWatcher")
|
|
}
|
|
if s.TracerProvider == nil {
|
|
s.TracerProvider = tracing.DefaultProvider()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// New creates a new unstarted Server.
|
|
func New(c ServerConfig) (*Server, error) {
|
|
// Check and make sure we everything we need to build a forwarding node.
|
|
err := c.CheckDefaults()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Build a pipe connection to hook up the client and the server. we save both
|
|
// here and will pass them along to the context when we create it so they
|
|
// can be closed by the context.
|
|
serverConn, clientConn := utils.DualPipeNetConn(c.SrcAddr, c.DstAddr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
s := &Server{
|
|
log: logrus.WithFields(logrus.Fields{
|
|
trace.Component: teleport.ComponentForwardingNode,
|
|
trace.ComponentFields: map[string]string{
|
|
"src-addr": c.SrcAddr.String(),
|
|
"dst-addr": c.DstAddr.String(),
|
|
},
|
|
}),
|
|
id: uuid.New().String(),
|
|
targetConn: c.TargetConn,
|
|
serverConn: utils.NewTrackingConn(serverConn),
|
|
clientConn: clientConn,
|
|
userAgent: c.UserAgent,
|
|
hostCertificate: c.HostCertificate,
|
|
useTunnel: c.UseTunnel,
|
|
address: c.Address,
|
|
authClient: c.AuthClient,
|
|
authService: c.AuthClient,
|
|
dataDir: c.DataDir,
|
|
clock: c.Clock,
|
|
hostUUID: c.HostUUID,
|
|
StreamEmitter: c.Emitter,
|
|
parentContext: c.ParentContext,
|
|
lockWatcher: c.LockWatcher,
|
|
tracerProvider: c.TracerProvider,
|
|
targetID: c.TargetID,
|
|
targetAddr: c.TargetAddr,
|
|
targetHostname: c.TargetHostname,
|
|
}
|
|
|
|
// Set the ciphers, KEX, and MACs that the in-memory server will send to the
|
|
// client in its SSH_MSG_KEXINIT.
|
|
s.ciphers = c.Ciphers
|
|
s.kexAlgorithms = c.KEXAlgorithms
|
|
s.macAlgorithms = c.MACAlgorithms
|
|
|
|
s.sessionRegistry, err = srv.NewSessionRegistry(srv.SessionRegistryConfig{
|
|
Srv: s,
|
|
SessionTrackerService: s.authClient,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Common auth handlers.
|
|
authHandlerConfig := srv.AuthHandlerConfig{
|
|
Server: s,
|
|
Component: teleport.ComponentForwardingNode,
|
|
Emitter: c.Emitter,
|
|
AccessPoint: c.AuthClient,
|
|
FIPS: c.FIPS,
|
|
Clock: c.Clock,
|
|
}
|
|
|
|
s.authHandlers, err = srv.NewAuthHandlers(&authHandlerConfig)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Common term handlers.
|
|
s.termHandlers = &srv.TermHandlers{
|
|
SessionRegistry: s.sessionRegistry,
|
|
}
|
|
|
|
// Create a close context that is used internally to signal when the server
|
|
// is closing and for any blocking goroutines to unblock.
|
|
s.closeContext, s.closeCancel = context.WithCancel(c.ParentContext)
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// TargetMetadata returns metadata about the forwarding target.
|
|
func (s *Server) TargetMetadata() apievents.ServerMetadata {
|
|
return apievents.ServerMetadata{
|
|
ServerNamespace: s.GetNamespace(),
|
|
ServerID: s.targetID,
|
|
ServerAddr: s.targetAddr,
|
|
ServerHostname: s.targetHostname,
|
|
ForwardedBy: s.hostUUID,
|
|
}
|
|
}
|
|
|
|
// Context returns parent context, used to signal
|
|
// that parent server has been closed
|
|
func (s *Server) Context() context.Context {
|
|
return s.parentContext
|
|
}
|
|
|
|
// GetDataDir returns server local storage
|
|
func (s *Server) GetDataDir() string {
|
|
return s.dataDir
|
|
}
|
|
|
|
// ID returns the ID of the proxy that creates the in-memory forwarding server.
|
|
func (s *Server) ID() string {
|
|
return s.id
|
|
}
|
|
|
|
// HostUUID is the UUID of the underlying proxy that the forwarding server
|
|
// is running in.
|
|
func (s *Server) HostUUID() string {
|
|
return s.hostUUID
|
|
}
|
|
|
|
// GetNamespace returns the namespace the forwarding server resides in.
|
|
func (s *Server) GetNamespace() string {
|
|
return apidefaults.Namespace
|
|
}
|
|
|
|
// AdvertiseAddr is the address of the remote host this forwarding server is
|
|
// connected to.
|
|
func (s *Server) AdvertiseAddr() string {
|
|
return s.clientConn.RemoteAddr().String()
|
|
}
|
|
|
|
// Component is the type of node this server is.
|
|
func (s *Server) Component() string {
|
|
return teleport.ComponentForwardingNode
|
|
}
|
|
|
|
// PermitUserEnvironment is always false because it's up the the remote host
|
|
// to decide if the user environment will be read or not.
|
|
func (s *Server) PermitUserEnvironment() bool {
|
|
return false
|
|
}
|
|
|
|
// GetAccessPoint returns a srv.AccessPoint for this cluster.
|
|
func (s *Server) GetAccessPoint() srv.AccessPoint {
|
|
return s.authService
|
|
}
|
|
|
|
// GetPAM returns the PAM configuration for a server. Because the forwarding
|
|
// server runs in-memory, it does not support PAM.
|
|
func (s *Server) GetPAM() (*pam.Config, error) {
|
|
return nil, trace.BadParameter("PAM not supported by forwarding server")
|
|
}
|
|
|
|
// UseTunnel used to determine if this node has connected to this cluster
|
|
// using reverse tunnel.
|
|
func (s *Server) UseTunnel() bool {
|
|
return s.useTunnel
|
|
}
|
|
|
|
// OpenBPFSession is a nop since the session must be run on the actual node
|
|
func (s *Server) OpenBPFSession(ctx *srv.ServerContext) (uint64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
// CloseBPFSession is a nop since the session must be run on the actual node
|
|
func (s *Server) CloseBPFSession(ctx *srv.ServerContext) error {
|
|
return nil
|
|
}
|
|
|
|
// OpenRestrictedSession is a nop since the session must be run on the actual node
|
|
func (s *Server) OpenRestrictedSession(ctx *srv.ServerContext, cgroupID uint64) {}
|
|
|
|
// CloseRestrictedSession is a nop since the session must be run on the actual node
|
|
func (s *Server) CloseRestrictedSession(ctx *srv.ServerContext, cgroupID uint64) {}
|
|
|
|
// GetCreateHostUser determines whether users should be created on the
|
|
// host automatically
|
|
func (s *Server) GetCreateHostUser() bool {
|
|
return false
|
|
}
|
|
|
|
// GetHostUsers returns the HostUsers instance being used to manage
|
|
// host user provisioning, unimplemented for the forwarder server.
|
|
func (s *Server) GetHostUsers() srv.HostUsers {
|
|
return nil
|
|
}
|
|
|
|
// GetInfo returns a services.Server that represents this server.
|
|
func (s *Server) GetInfo() types.Server {
|
|
return &types.ServerV2{
|
|
Kind: types.KindNode,
|
|
Version: types.V2,
|
|
Metadata: types.Metadata{
|
|
Name: s.ID(),
|
|
Namespace: s.GetNamespace(),
|
|
},
|
|
Spec: types.ServerSpecV2{
|
|
Addr: s.AdvertiseAddr(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Dial returns the client connection created by pipeAddrConn.
|
|
func (s *Server) Dial() (net.Conn, error) {
|
|
return s.clientConn, nil
|
|
}
|
|
|
|
// GetClock returns server clock implementation
|
|
func (s *Server) GetClock() clockwork.Clock {
|
|
return s.clock
|
|
}
|
|
|
|
// GetUtmpPath returns the optional override of the utmp and wtmp path.
|
|
// These values are never set for the forwarding server because utmp and wtmp
|
|
// are updated by the target server and not the forwarding server.
|
|
func (s *Server) GetUtmpPath() (string, string) {
|
|
return "", ""
|
|
}
|
|
|
|
// GetLockWatcher gets the server's lock watcher.
|
|
func (s *Server) GetLockWatcher() *services.LockWatcher {
|
|
return s.lockWatcher
|
|
}
|
|
|
|
func (s *Server) Serve() {
|
|
config := &ssh.ServerConfig{}
|
|
|
|
// Configure callback for user certificate authentication.
|
|
config.PublicKeyCallback = s.authHandlers.UserKeyAuth
|
|
|
|
// Set host certificate the in-memory server will present to clients.
|
|
config.AddHostKey(s.hostCertificate)
|
|
|
|
// Set the ciphers, KEX, and MACs that the client will send to the target
|
|
// server in its SSH_MSG_KEXINIT.
|
|
config.Ciphers = s.ciphers
|
|
config.KeyExchanges = s.kexAlgorithms
|
|
config.MACs = s.macAlgorithms
|
|
|
|
netConfig, err := s.GetAccessPoint().GetClusterNetworkingConfig(s.Context())
|
|
if err != nil {
|
|
s.log.Errorf("Unable to fetch cluster config: %v.", err)
|
|
return
|
|
}
|
|
|
|
s.log.Debugf("Supported ciphers: %q.", s.ciphers)
|
|
s.log.Debugf("Supported KEX algorithms: %q.", s.kexAlgorithms)
|
|
s.log.Debugf("Supported MAC algorithms: %q.", s.macAlgorithms)
|
|
|
|
sconn, chans, reqs, err := ssh.NewServerConn(s.serverConn, config)
|
|
if err != nil {
|
|
s.userAgent.Close()
|
|
s.targetConn.Close()
|
|
s.clientConn.Close()
|
|
s.serverConn.Close()
|
|
|
|
s.log.Errorf("Unable to create server connection: %v.", err)
|
|
return
|
|
}
|
|
s.sconn = sconn
|
|
|
|
ctx := context.Background()
|
|
ctx, s.connectionContext = sshutils.NewConnectionContext(ctx, s.serverConn, s.sconn, sshutils.SetConnectionContextClock(s.clock))
|
|
|
|
// Take connection and extract identity information for the user from it.
|
|
s.identityContext, err = s.authHandlers.CreateIdentityContext(sconn)
|
|
if err != nil {
|
|
s.userAgent.Close()
|
|
s.targetConn.Close()
|
|
s.clientConn.Close()
|
|
s.serverConn.Close()
|
|
|
|
s.log.Errorf("Unable to create server connection: %v.", err)
|
|
return
|
|
}
|
|
|
|
// Connect and authenticate to the remote node.
|
|
s.log.Debugf("Creating remote connection to %v@%v", sconn.User(), s.clientConn.RemoteAddr().String())
|
|
s.remoteClient, err = s.newRemoteClient(ctx, sconn.User())
|
|
if err != nil {
|
|
// Reject the connection with an error so the client doesn't hang then
|
|
// close the connection.
|
|
s.rejectChannel(chans, err.Error())
|
|
sconn.Close()
|
|
|
|
s.userAgent.Close()
|
|
s.targetConn.Close()
|
|
s.clientConn.Close()
|
|
s.serverConn.Close()
|
|
|
|
s.log.Errorf("Unable to create remote connection: %v", err)
|
|
return
|
|
}
|
|
|
|
// The keep-alive loop will keep pinging the remote server and after it has
|
|
// missed a certain number of keep-alive requests it will cancel the
|
|
// closeContext which signals the server to shutdown.
|
|
go srv.StartKeepAliveLoop(srv.KeepAliveParams{
|
|
Conns: []srv.RequestSender{
|
|
s.sconn,
|
|
s.remoteClient.Client,
|
|
},
|
|
Interval: netConfig.GetKeepAliveInterval(),
|
|
MaxCount: netConfig.GetKeepAliveCountMax(),
|
|
CloseContext: ctx,
|
|
CloseCancel: func() { s.connectionContext.Close() },
|
|
})
|
|
|
|
go s.handleConnection(ctx, chans, reqs)
|
|
}
|
|
|
|
// Close will close all underlying connections that the forwarding server holds.
|
|
func (s *Server) Close() error {
|
|
conns := []io.Closer{
|
|
s.userAgent,
|
|
s.sconn,
|
|
s.clientConn,
|
|
s.serverConn,
|
|
s.targetConn,
|
|
s.remoteClient,
|
|
s.connectionContext,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for _, c := range conns {
|
|
if c == nil {
|
|
continue
|
|
}
|
|
|
|
err := c.Close()
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
// Signal to the outside world that this server is closed
|
|
s.closeCancel()
|
|
|
|
return trace.NewAggregate(errs...)
|
|
}
|
|
|
|
// newRemoteSession will create and return a *ssh.Client and *ssh.Session
|
|
// with a remote host.
|
|
func (s *Server) newRemoteClient(ctx context.Context, systemLogin string) (*tracessh.Client, error) {
|
|
// the proxy will use the agent that has been forwarded to it as the auth
|
|
// method when connecting to the remote host
|
|
if s.userAgent == nil {
|
|
return nil, trace.AccessDenied("agent must be forwarded to proxy")
|
|
}
|
|
authMethod := ssh.PublicKeysCallback(s.userAgent.Signers)
|
|
|
|
clientConfig := &ssh.ClientConfig{
|
|
User: systemLogin,
|
|
Auth: []ssh.AuthMethod{
|
|
authMethod,
|
|
},
|
|
HostKeyCallback: s.authHandlers.HostKeyAuth,
|
|
Timeout: apidefaults.DefaultDialTimeout,
|
|
}
|
|
|
|
// Ciphers, KEX, and MACs preferences are honored by both the in-memory
|
|
// server as well as the client in the connection to the target node.
|
|
clientConfig.Ciphers = s.ciphers
|
|
clientConfig.KeyExchanges = s.kexAlgorithms
|
|
clientConfig.MACs = s.macAlgorithms
|
|
|
|
// Destination address is used to validate a connection was established to
|
|
// the correct host. It must occur in the list of principals presented by
|
|
// the remote server.
|
|
dstAddr := net.JoinHostPort(s.address, "0")
|
|
client, err := tracessh.NewClientConnWithDeadline(ctx, s.targetConn, dstAddr, clientConfig)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (s *Server) handleConnection(ctx context.Context, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
|
defer s.log.Debugf("Closing forwarding server connected to %v and releasing resources.", s.sconn.LocalAddr())
|
|
defer s.Close()
|
|
|
|
for {
|
|
select {
|
|
// Global out-of-band requests.
|
|
case req := <-reqs:
|
|
if req == nil {
|
|
return
|
|
}
|
|
reqCtx := tracessh.ContextFromRequest(req)
|
|
ctx, span := s.tracerProvider.Tracer("ssh").Start(
|
|
oteltrace.ContextWithRemoteSpanContext(ctx, oteltrace.SpanContextFromContext(reqCtx)),
|
|
fmt.Sprintf("ssh.Forward.GlobalRequest/%s", req.Type),
|
|
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
|
oteltrace.WithAttributes(
|
|
semconv.RPCServiceKey.String("ssh.ForwardServer"),
|
|
semconv.RPCMethodKey.String("GlobalRequest"),
|
|
semconv.RPCSystemKey.String("ssh"),
|
|
),
|
|
)
|
|
|
|
go func(span oteltrace.Span) {
|
|
defer span.End()
|
|
s.handleGlobalRequest(ctx, req)
|
|
}(span)
|
|
// Channel requests.
|
|
case nch := <-chans:
|
|
if nch == nil {
|
|
return
|
|
}
|
|
chanCtx, nch := tracessh.ContextFromNewChannel(nch)
|
|
ctx, span := s.tracerProvider.Tracer("ssh").Start(
|
|
oteltrace.ContextWithRemoteSpanContext(ctx, oteltrace.SpanContextFromContext(chanCtx)),
|
|
fmt.Sprintf("ssh.Forward.OpenChannel/%s", nch.ChannelType()),
|
|
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
|
oteltrace.WithAttributes(
|
|
semconv.RPCServiceKey.String("ssh.ForwardServer"),
|
|
semconv.RPCMethodKey.String("OpenChannel"),
|
|
semconv.RPCSystemKey.String("ssh"),
|
|
),
|
|
)
|
|
|
|
go func(span oteltrace.Span) {
|
|
defer span.End()
|
|
s.handleChannel(ctx, nch)
|
|
}(span)
|
|
// If the server is closing (either the heartbeat failed or Close() was
|
|
// called, exit out of the connection handler loop.
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) rejectChannel(chans <-chan ssh.NewChannel, errMessage string) {
|
|
newChannel, ok := <-chans
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := newChannel.Reject(ssh.ConnectionFailed, errMessage); err != nil {
|
|
s.log.Errorf("Unable to reject and close connection.")
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleGlobalRequest(ctx context.Context, req *ssh.Request) {
|
|
// Version requests are internal Teleport requests, they should not be
|
|
// forwarded to the remote server.
|
|
if req.Type == teleport.VersionRequest {
|
|
err := req.Reply(true, []byte(teleport.Version))
|
|
if err != nil {
|
|
s.log.Debugf("Failed to reply to version request: %v.", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
ok, payload, err := s.remoteClient.SendRequest(ctx, req.Type, req.WantReply, req.Payload)
|
|
if err != nil {
|
|
s.log.Warnf("Failed to forward global request %v: %v", req.Type, err)
|
|
return
|
|
}
|
|
|
|
if req.WantReply {
|
|
err = req.Reply(ok, payload)
|
|
if err != nil {
|
|
s.log.Warnf("Failed to reply to global request: %v: %v", req.Type, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleChannel(ctx context.Context, nch ssh.NewChannel) {
|
|
channelType := nch.ChannelType()
|
|
|
|
switch channelType {
|
|
// Channels of type "tracing-request" are sent to determine if ssh tracing envelopes
|
|
// are supported. Accepting the channel indicates to clients that they may wrap their
|
|
// ssh payload with tracing context.
|
|
case tracessh.TracingChannel:
|
|
ch, _, err := nch.Accept()
|
|
if err != nil {
|
|
s.log.Warnf("Unable to accept channel: %v", err)
|
|
if err := nch.Reject(ssh.ConnectionFailed, fmt.Sprintf("unable to accept channel: %v", err)); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := ch.Close(); err != nil {
|
|
s.log.Warnf("Unable to close %q channel: %v", nch.ChannelType(), err)
|
|
}
|
|
return
|
|
// Channels of type "session" handle requests that are involved in running
|
|
// commands on a server, subsystem requests, and agent forwarding.
|
|
case teleport.ChanSession:
|
|
go s.handleSessionChannel(ctx, nch)
|
|
|
|
// Channels of type "direct-tcpip" handles request for port forwarding.
|
|
case teleport.ChanDirectTCPIP:
|
|
req, err := sshutils.ParseDirectTCPIPReq(nch.ExtraData())
|
|
if err != nil {
|
|
s.log.Errorf("Failed to parse request data: %v, err: %v", string(nch.ExtraData()), err)
|
|
if err := nch.Reject(ssh.UnknownChannelType, "failed to parse direct-tcpip request"); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
ch, _, err := nch.Accept()
|
|
if err != nil {
|
|
s.log.Warnf("Unable to accept channel: %v", err)
|
|
if err := nch.Reject(ssh.ConnectionFailed, fmt.Sprintf("unable to accept channel: %v", err)); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
go s.handleDirectTCPIPRequest(ctx, ch, req)
|
|
default:
|
|
if err := nch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %v", channelType)); err != nil {
|
|
s.log.Warnf("Failed to reject channel of unknown type: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleDirectTCPIPRequest handles port forwarding requests.
|
|
func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ch ssh.Channel, req *sshutils.DirectTCPIPReq) {
|
|
// Create context for this channel. This context will be closed when
|
|
// forwarding is complete.
|
|
ctx, scx, err := srv.NewServerContext(ctx, s.connectionContext, s, s.identityContext)
|
|
if err != nil {
|
|
s.log.Errorf("Unable to create connection context: %v.", err)
|
|
s.stderrWrite(ch, "Unable to create connection context.")
|
|
return
|
|
}
|
|
scx.RemoteClient = s.remoteClient
|
|
scx.ChannelType = teleport.ChanDirectTCPIP
|
|
scx.SrcAddr = fmt.Sprintf("%v:%d", req.Orig, req.OrigPort)
|
|
scx.DstAddr = fmt.Sprintf("%v:%d", req.Host, req.Port)
|
|
defer scx.Close()
|
|
|
|
ch = scx.TrackActivity(ch)
|
|
|
|
// Check if the role allows port forwarding for this user.
|
|
err = s.authHandlers.CheckPortForward(scx.DstAddr, scx)
|
|
if err != nil {
|
|
s.stderrWrite(ch, err.Error())
|
|
return
|
|
}
|
|
|
|
s.log.Debugf("Opening direct-tcpip channel from %v to %v in context %v.", scx.SrcAddr, scx.DstAddr, scx.ID())
|
|
defer s.log.Debugf("Completing direct-tcpip request from %v to %v in context %v.", scx.SrcAddr, scx.DstAddr, scx.ID())
|
|
|
|
// Create "direct-tcpip" channel from the remote host to the target host.
|
|
conn, err := s.remoteClient.DialContext(ctx, "tcp", scx.DstAddr)
|
|
if err != nil {
|
|
scx.Infof("Failed to connect to: %v: %v", scx.DstAddr, err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
if err := s.EmitAuditEvent(s.closeContext, &apievents.PortForward{
|
|
Metadata: apievents.Metadata{
|
|
Type: events.PortForwardEvent,
|
|
Code: events.PortForwardCode,
|
|
},
|
|
UserMetadata: s.identityContext.GetUserMetadata(),
|
|
ConnectionMetadata: apievents.ConnectionMetadata{
|
|
LocalAddr: s.sconn.LocalAddr().String(),
|
|
RemoteAddr: s.sconn.RemoteAddr().String(),
|
|
},
|
|
Addr: scx.DstAddr,
|
|
Status: apievents.Status{
|
|
Success: true,
|
|
},
|
|
}); err != nil {
|
|
scx.WithError(err).Warn("Failed to emit port forward event.")
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wch := make(chan struct{})
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := io.Copy(ch, conn); err != nil {
|
|
scx.Warningf("failed proxying data for port forwarding connection: %v", err)
|
|
}
|
|
ch.Close()
|
|
}()
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := io.Copy(conn, ch); err != nil {
|
|
scx.Warningf("failed proxying data for port forwarding connection: %v", err)
|
|
}
|
|
conn.Close()
|
|
}()
|
|
// block on wg in separate goroutine so that we
|
|
// can select on wg and context cancellation.
|
|
go func() {
|
|
defer close(wch)
|
|
wg.Wait()
|
|
}()
|
|
select {
|
|
case <-wch:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
|
|
// handleSessionChannel handles accepting and forwarding a session channel from the client to
|
|
// the remote host. Once the session channel has been established, this function's loop handles
|
|
// all the "exec", "subsystem" and "shell" requests.
|
|
func (s *Server) handleSessionChannel(ctx context.Context, nch ssh.NewChannel) {
|
|
// Create context for this channel. This context will be closed when the
|
|
// session request is complete.
|
|
// There is no need for the forwarding server to initiate disconnects,
|
|
// based on teleport business logic, because this logic is already
|
|
// done on the server's terminating side.
|
|
ctx, scx, err := srv.NewServerContext(ctx, s.connectionContext, s, s.identityContext)
|
|
if err != nil {
|
|
s.log.Warnf("Server context setup failed: %v", err)
|
|
if err := nch.Reject(ssh.ConnectionFailed, fmt.Sprintf("server context setup failed: %v", err)); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
scx.RemoteClient = s.remoteClient
|
|
scx.ChannelType = teleport.ChanSession
|
|
defer scx.Close()
|
|
|
|
// Create a "session" channel on the remote host. Note that we
|
|
// create the remote session channel before accepting the local
|
|
// channel request; this allows us to propagate the rejection
|
|
// reason/message in the event the channel is rejected.
|
|
remoteSession, err := s.remoteClient.NewSession(ctx)
|
|
if err != nil {
|
|
s.log.Warnf("Remote session open failed: %v", err)
|
|
reason, msg := ssh.ConnectionFailed, fmt.Sprintf("remote session open failed: %v", err)
|
|
if e, ok := trace.Unwrap(err).(*ssh.OpenChannelError); ok {
|
|
reason, msg = e.Reason, e.Message
|
|
}
|
|
if err := nch.Reject(reason, msg); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
scx.RemoteSession = remoteSession
|
|
|
|
// Accept the session channel request
|
|
ch, in, err := nch.Accept()
|
|
if err != nil {
|
|
s.log.Warnf("Unable to accept channel: %v", err)
|
|
if err := nch.Reject(ssh.ConnectionFailed, fmt.Sprintf("unable to accept channel: %v", err)); err != nil {
|
|
s.log.Warnf("Failed to reject channel: %v", err)
|
|
}
|
|
return
|
|
}
|
|
scx.AddCloser(ch)
|
|
|
|
ch = scx.TrackActivity(ch)
|
|
|
|
s.log.Debugf("Opening session request to %v in context %v.", s.sconn.RemoteAddr(), scx.ID())
|
|
defer s.log.Debugf("Closing session request to %v in context %v.", s.sconn.RemoteAddr(), scx.ID())
|
|
|
|
for {
|
|
// Update the context with the session ID.
|
|
err := scx.CreateOrJoinSession(s.sessionRegistry)
|
|
if err != nil {
|
|
errorMessage := fmt.Sprintf("unable to update context: %v", err)
|
|
scx.Errorf("%v", errorMessage)
|
|
|
|
// Write the error to channel and close it.
|
|
s.stderrWrite(ch, errorMessage)
|
|
_, err := ch.SendRequest("exit-status", false, ssh.Marshal(struct{ C uint32 }{C: teleport.RemoteCommandFailure}))
|
|
if err != nil {
|
|
scx.Errorf("Failed to send exit status %v", errorMessage)
|
|
}
|
|
return
|
|
}
|
|
|
|
select {
|
|
case result := <-scx.SubsystemResultCh:
|
|
// Subsystem has finished executing, close the channel and session.
|
|
scx.Debugf("Subsystem execution result: %v", result.Err)
|
|
return
|
|
case req := <-in:
|
|
if req == nil {
|
|
// The client has closed or dropped the connection.
|
|
scx.Debugf("Client %v disconnected", s.sconn.RemoteAddr())
|
|
return
|
|
}
|
|
|
|
reqCtx := tracessh.ContextFromRequest(req)
|
|
ctx, span := s.tracerProvider.Tracer("ssh").Start(
|
|
oteltrace.ContextWithRemoteSpanContext(ctx, oteltrace.SpanContextFromContext(reqCtx)),
|
|
fmt.Sprintf("ssh.Forward.SessionRequest/%s", req.Type),
|
|
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
|
oteltrace.WithAttributes(
|
|
semconv.RPCServiceKey.String("ssh.ForwardServer"),
|
|
semconv.RPCMethodKey.String("SessionRequest"),
|
|
semconv.RPCSystemKey.String("ssh"),
|
|
),
|
|
)
|
|
|
|
if err := s.dispatch(ctx, ch, req, scx); err != nil {
|
|
s.replyError(ch, req, err)
|
|
span.End()
|
|
return
|
|
}
|
|
if req.WantReply {
|
|
if err := req.Reply(true, nil); err != nil {
|
|
scx.Errorf("failed sending OK response on %q request: %v", req.Type, err)
|
|
}
|
|
}
|
|
span.End()
|
|
case result := <-scx.ExecResultCh:
|
|
scx.Debugf("Exec request (%q) complete: %v", result.Command, result.Code)
|
|
|
|
// The exec process has finished and delivered the execution result, send
|
|
// the result back to the client, and close the session and channel.
|
|
_, err := ch.SendRequest("exit-status", false, ssh.Marshal(struct{ C uint32 }{C: uint32(result.Code)}))
|
|
if err != nil {
|
|
scx.Infof("Failed to send exit status for %v: %v", result.Command, err)
|
|
}
|
|
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) dispatch(ctx context.Context, ch ssh.Channel, req *ssh.Request, scx *srv.ServerContext) error {
|
|
scx.Debugf("Handling request %v, want reply %v.", req.Type, req.WantReply)
|
|
|
|
// Certs with a join-only principal can only use a
|
|
// subset of all the possible request types.
|
|
if scx.JoinOnly {
|
|
switch req.Type {
|
|
case tracessh.TracingRequest:
|
|
return s.handleTracingRequest(ctx, req, scx)
|
|
case sshutils.PTYRequest:
|
|
return s.termHandlers.HandlePTYReq(ctx, ch, req, scx)
|
|
case sshutils.ShellRequest:
|
|
return s.termHandlers.HandleShell(ctx, ch, req, scx)
|
|
case sshutils.WindowChangeRequest:
|
|
return s.termHandlers.HandleWinChange(ctx, ch, req, scx)
|
|
case teleport.ForceTerminateRequest:
|
|
return s.termHandlers.HandleForceTerminate(ch, req, scx)
|
|
case sshutils.EnvRequest:
|
|
// We ignore all SSH setenv requests for join-only principals.
|
|
// SSH will send them anyway but it seems fine to silently drop them.
|
|
case sshutils.SubsystemRequest:
|
|
return s.handleSubsystem(ctx, ch, req, scx)
|
|
case sshutils.AgentForwardRequest:
|
|
// to maintain interoperability with OpenSSH, agent forwarding requests
|
|
// should never fail, all errors should be logged and we should continue
|
|
// processing requests.
|
|
err := s.handleAgentForward(ch, req, scx)
|
|
if err != nil {
|
|
s.log.Debug(err)
|
|
}
|
|
return nil
|
|
default:
|
|
return trace.AccessDenied("attempted %v request in join-only mode", req.Type)
|
|
}
|
|
}
|
|
|
|
switch req.Type {
|
|
case tracessh.TracingRequest:
|
|
return s.handleTracingRequest(ctx, req, scx)
|
|
case sshutils.ExecRequest:
|
|
return s.termHandlers.HandleExec(ctx, ch, req, scx)
|
|
case sshutils.PTYRequest:
|
|
return s.termHandlers.HandlePTYReq(ctx, ch, req, scx)
|
|
case sshutils.ShellRequest:
|
|
return s.termHandlers.HandleShell(ctx, ch, req, scx)
|
|
case sshutils.WindowChangeRequest:
|
|
return s.termHandlers.HandleWinChange(ctx, ch, req, scx)
|
|
case teleport.ForceTerminateRequest:
|
|
return s.termHandlers.HandleForceTerminate(ch, req, scx)
|
|
case sshutils.EnvRequest:
|
|
return s.handleEnv(ctx, ch, req, scx)
|
|
case sshutils.SubsystemRequest:
|
|
return s.handleSubsystem(ctx, ch, req, scx)
|
|
case sshutils.X11ForwardRequest:
|
|
return s.handleX11Forward(ctx, ch, req, scx)
|
|
case sshutils.AgentForwardRequest:
|
|
// to maintain interoperability with OpenSSH, agent forwarding requests
|
|
// should never fail, all errors should be logged and we should continue
|
|
// processing requests.
|
|
err := s.handleAgentForward(ch, req, scx)
|
|
if err != nil {
|
|
s.log.Debug(err)
|
|
}
|
|
return nil
|
|
default:
|
|
return trace.BadParameter(
|
|
"%v doesn't support request type '%v'", s.Component(), req.Type)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleAgentForward(ch ssh.Channel, req *ssh.Request, ctx *srv.ServerContext) error {
|
|
// Check if the user's RBAC role allows agent forwarding.
|
|
err := s.authHandlers.CheckAgentForward(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Route authentication requests to the agent that was forwarded to the proxy.
|
|
err = agent.ForwardToAgent(ctx.RemoteClient.Client, s.userAgent)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// Make an "auth-agent-req@openssh.com" request on the remote host.
|
|
err = agent.RequestAgentForwarding(ctx.RemoteSession.Session)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleX11ChannelRequest accepts an X11 channel and forwards it back to the client.
|
|
// Servers which support X11 forwarding request a separate channel for serving each
|
|
// inbound connection on the X11 socket of the remote session.
|
|
func (s *Server) handleX11ChannelRequest(ctx context.Context, nch ssh.NewChannel) {
|
|
// accept inbound X11 channel from server
|
|
sch, sin, err := nch.Accept()
|
|
if err != nil {
|
|
s.log.Errorf("X11 channel fwd failed: %v", err)
|
|
return
|
|
}
|
|
defer sch.Close()
|
|
|
|
// setup outbound X11 channel to client
|
|
cch, cin, err := s.sconn.OpenChannel(sshutils.X11ChannelRequest, nch.ExtraData())
|
|
if err != nil {
|
|
s.log.Errorf("X11 channel fwd failed: %v", err)
|
|
return
|
|
}
|
|
defer cch.Close()
|
|
|
|
// Forward ssh requests on the X11 channels until X11 forwarding is complete
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
go func() {
|
|
err := sshutils.ForwardRequests(ctx, cin, tracessh.NewTraceChannel(sch, tracing.WithTracerProvider(s.tracerProvider)))
|
|
if err != nil {
|
|
s.log.WithError(err).Debug("Failed to forward ssh request from client during X11 forwarding")
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
err := sshutils.ForwardRequests(ctx, sin, tracessh.NewTraceChannel(cch, tracing.WithTracerProvider(s.tracerProvider)))
|
|
if err != nil {
|
|
s.log.WithError(err).Debug("Failed to forward ssh request from server during X11 forwarding")
|
|
}
|
|
}()
|
|
|
|
if err := x11.Forward(ctx, cch, sch); err != nil {
|
|
s.log.WithError(err).Debug("Encountered error during x11 forwarding")
|
|
}
|
|
}
|
|
|
|
// handleX11Forward handles an X11 forwarding request from the client.
|
|
func (s *Server) handleX11Forward(ctx context.Context, ch ssh.Channel, req *ssh.Request, scx *srv.ServerContext) (err error) {
|
|
event := &apievents.X11Forward{
|
|
Status: apievents.Status{
|
|
Success: true,
|
|
},
|
|
Metadata: apievents.Metadata{
|
|
Type: events.X11ForwardEvent,
|
|
Code: events.X11ForwardCode,
|
|
},
|
|
UserMetadata: s.identityContext.GetUserMetadata(),
|
|
ConnectionMetadata: apievents.ConnectionMetadata{
|
|
LocalAddr: s.sconn.LocalAddr().String(),
|
|
RemoteAddr: s.sconn.RemoteAddr().String(),
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
event.Metadata.Code = events.X11ForwardFailureCode
|
|
event.Status.Success = false
|
|
event.Status.Error = err.Error()
|
|
}
|
|
if trace.IsAccessDenied(err) {
|
|
// denied X11 requests are ok from a protocol perspective so we
|
|
// don't return them, just reply over ssh and emit the audit log.
|
|
s.replyError(ch, req, err)
|
|
err = nil
|
|
}
|
|
if err := s.EmitAuditEvent(ctx, event); err != nil {
|
|
s.log.WithError(err).Warn("Failed to emit x11-forward event.")
|
|
}
|
|
}()
|
|
|
|
// Check if the user's RBAC role allows X11 forwarding.
|
|
if err := s.authHandlers.CheckX11Forward(scx); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// send X11 forwarding request to remote
|
|
ok, err := sshutils.ForwardRequest(ctx, scx.RemoteSession, req)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
} else if !ok {
|
|
return trace.AccessDenied("X11 forwarding request denied by server")
|
|
}
|
|
|
|
err = x11.ServeChannelRequests(ctx, s.remoteClient.Client, s.handleX11ChannelRequest)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleSubsystem(ctx context.Context, ch ssh.Channel, req *ssh.Request, serverContext *srv.ServerContext) error {
|
|
subsystem, err := parseSubsystemRequest(req, serverContext)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// start the requested subsystem, if it fails to start return result right away
|
|
err = subsystem.Start(ctx, ch)
|
|
if err != nil {
|
|
serverContext.SendSubsystemResult(srv.SubsystemResult{
|
|
Name: subsystem.subsytemName,
|
|
Err: trace.Wrap(err),
|
|
})
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// wait for the subsystem to finish and return that result
|
|
go func() {
|
|
err := subsystem.Wait()
|
|
serverContext.SendSubsystemResult(srv.SubsystemResult{
|
|
Name: subsystem.subsytemName,
|
|
Err: trace.Wrap(err),
|
|
})
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleTracingRequest(ctx context.Context, req *ssh.Request, scx *srv.ServerContext) error {
|
|
if _, err := scx.RemoteSession.SendRequest(ctx, req.Type, false, req.Payload); err != nil {
|
|
s.log.WithError(err).Debugf("Unable to set forward tracing context")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleEnv(ctx context.Context, ch ssh.Channel, req *ssh.Request, scx *srv.ServerContext) error {
|
|
var e sshutils.EnvReqParams
|
|
if err := ssh.Unmarshal(req.Payload, &e); err != nil {
|
|
scx.Error(err)
|
|
return trace.Wrap(err, "failed to parse env request")
|
|
}
|
|
|
|
err := scx.RemoteSession.Setenv(ctx, e.Name, e.Value)
|
|
if err != nil {
|
|
s.log.Debugf("Unable to set environment variable: %v: %v", e.Name, e.Value)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) replyError(ch ssh.Channel, req *ssh.Request, err error) {
|
|
s.log.Error(err)
|
|
// Terminate the error with a newline when writing to remote channel's
|
|
// stderr so the output does not mix with the rest of the output if the remote
|
|
// side is not doing additional formatting for extended data.
|
|
// See github.com/gravitational/teleport/issues/4542
|
|
message := utils.FormatErrorWithNewline(err)
|
|
s.stderrWrite(ch, message)
|
|
if req.WantReply {
|
|
if err := req.Reply(false, []byte(message)); err != nil {
|
|
s.log.Errorf("sending error reply on SSH channel: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) stderrWrite(ch ssh.Channel, message string) {
|
|
if _, err := ch.Stderr().Write([]byte(message)); err != nil {
|
|
s.log.Errorf("failed writing to SSH stderr channel: %v", err)
|
|
}
|
|
}
|
|
|
|
func parseSubsystemRequest(req *ssh.Request, ctx *srv.ServerContext) (*remoteSubsystem, error) {
|
|
var r sshutils.SubsystemReq
|
|
err := ssh.Unmarshal(req.Payload, &r)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("failed to parse subsystem request: %v", err)
|
|
}
|
|
|
|
return parseRemoteSubsystem(context.Background(), r.Name, ctx), nil
|
|
}
|