Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion lib/srv/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ var (
)
)

var errCannotStartUnattendedSession = trace.AccessDenied("lacking privileges to start unattended session")

func init() {
prometheus.MustRegister(serverTX)
prometheus.MustRegister(serverRX)
Expand Down Expand Up @@ -598,6 +600,29 @@ func (c *ServerContext) getSession() *session {
return c.session
}

// CheckSFTPAllowed returns an error if remote file operations via SCP
// or SFTP are not allowed because the user is not allowed to start
// unattended sessions.
func (c *ServerContext) CheckSFTPAllowed() error {
// ensure moderated session policies allow starting an unattended session
var policySets []*types.SessionTrackerPolicySet
for _, role := range c.Identity.RoleSet {
policySet := role.GetSessionPolicySet()
policySets = append(policySets, &policySet)
}

checker := auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind)
canStart, _, err := checker.FulfilledFor(nil)
if err != nil {
return trace.Wrap(err)
}
if !canStart {
return errCannotStartUnattendedSession
}

return nil
}

// OpenXServerListener opens a new XServer unix listener.
func (c *ServerContext) OpenXServerListener(x11Req x11.ForwardRequestPayload, displayOffset, maxDisplays int) error {
l, display, err := x11.OpenNewXServerListener(displayOffset, maxDisplays, x11Req.ScreenNumber)
Expand Down Expand Up @@ -858,7 +883,7 @@ func (c *ServerContext) SendSubsystemResult(r SubsystemResult) {
// available proxy. if public_address is not set, fall back to the hostname
// of the first proxy we get back.
func (c *ServerContext) ProxyPublicAddress() string {
//TODO(tross): Get the proxy address somehow - types.KindProxy is not replicated to Nodes
// TODO(tross): Get the proxy address somehow - types.KindProxy is not replicated to Nodes
return "<proxyhost>:3080"
}

Expand Down
15 changes: 13 additions & 2 deletions lib/srv/regular/sshserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,7 @@ func (s *Server) handleX11Forward(ch ssh.Channel, req *ssh.Request, ctx *srv.Ser
}

func (s *Server) handleSubsystem(ctx context.Context, ch ssh.Channel, req *ssh.Request, serverContext *srv.ServerContext) error {
sb, err := s.parseSubsystemRequest(req, serverContext)
sb, err := s.parseSubsystemRequest(req, ch, serverContext)
if err != nil {
serverContext.Warnf("Failed to parse subsystem request: %v: %v.", req, err)
return trace.Wrap(err)
Expand Down Expand Up @@ -1902,6 +1902,12 @@ func (s *Server) handleProxyJump(ctx context.Context, ccx *sshutils.ConnectionCo
}
}

// TODO: tsh scp will display neither the message sent in stderr or in
// the reply; github.com/pkg/sftp ignores the SSH channel stderr, and
// golang.org/x/crypto/ssh.channel.SendRequest ignores the message in
// a channel reply. This is bad UX for users, as
// 'ssh: subsystem request failed' will be the only error displayed when
// access is denied.
func (s *Server) replyError(ch ssh.Channel, req *ssh.Request, err error) {
s.Logger.Error(err)
// Terminate the error with a newline when writing to remote channel's
Expand All @@ -1917,7 +1923,7 @@ func (s *Server) replyError(ch ssh.Channel, req *ssh.Request, err error) {
}
}

func (s *Server) parseSubsystemRequest(req *ssh.Request, ctx *srv.ServerContext) (srv.Subsystem, error) {
func (s *Server) parseSubsystemRequest(req *ssh.Request, ch ssh.Channel, ctx *srv.ServerContext) (srv.Subsystem, error) {
var r sshutils.SubsystemReq
if err := ssh.Unmarshal(req.Payload, &r); err != nil {
return nil, trace.BadParameter("failed to parse subsystem request: %v", err)
Expand All @@ -1929,6 +1935,11 @@ func (s *Server) parseSubsystemRequest(req *ssh.Request, ctx *srv.ServerContext)
case s.proxyMode && strings.HasPrefix(r.Name, "proxysites"):
return parseProxySitesSubsys(r.Name, s)
case r.Name == sftpSubsystem:
if err := ctx.CheckSFTPAllowed(); err != nil {
s.replyError(ch, req, err)
return nil, trace.Wrap(err)
}

return newSFTPSubsys()
default:
return nil, trace.BadParameter("unrecognized subsystem: %v", r.Name)
Expand Down
2 changes: 1 addition & 1 deletion lib/srv/sess.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (s *SessionRegistry) OpenExecSession(ctx context.Context, channel ssh.Chann
}

if !canStart {
return trace.AccessDenied("lacking privileges to start unattended session")
return errCannotStartUnattendedSession
}

// Start a non-interactive session (TTY attached). Close the session if an error
Expand Down