Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion client/cmd/ssh_exec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func init() {

// runSSHExec handles the SSH exec subcommand execution.
func runSSHExec(cmd *cobra.Command, _ []string) error {
privilegeDropper := sshserver.NewPrivilegeDropper()
privilegeDropper := sshserver.NewPrivilegeDropper(nil)

var groups []uint32
for _, groupInt := range sshExecGroups {
Expand Down
2 changes: 1 addition & 1 deletion client/cmd/ssh_sftp_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func init() {
}

func sftpMain(cmd *cobra.Command, _ []string) error {
privilegeDropper := sshserver.NewPrivilegeDropper()
privilegeDropper := sshserver.NewPrivilegeDropper(nil)

var groups []uint32
for _, groupInt := range sftpGroupsInt {
Expand Down
26 changes: 21 additions & 5 deletions client/ssh/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,35 @@ func (p *SSHProxy) handleProxyExitCode(session ssh.Session, err error) {
}

func (p *SSHProxy) handleNonInteractiveSession(session ssh.Session, sshClient *cryptossh.Client) {
// Create a backend session to mirror the client's session request.
// This keeps the connection alive on the server side while port forwarding channels operate.
serverSession, err := sshClient.NewSession()
if err != nil {
_, _ = fmt.Fprintf(p.stderr, "create server session: %v\n", err)
return
}
defer func() { _ = serverSession.Close() }()

<-session.Context().Done()
serverSession.Stdin = session
serverSession.Stdout = session
serverSession.Stderr = session.Stderr()

if err := serverSession.Shell(); err != nil {
log.Debugf("start shell: %v", err)
return
}

if err := session.Exit(0); err != nil {
log.Debugf("session exit: %v", err)
done := make(chan error, 1)
go func() {
done <- serverSession.Wait()
}()

select {
case <-session.Context().Done():
return
case err := <-done:
if err != nil {
log.Debugf("shell session: %v", err)
p.handleProxyExitCode(session, err)
}
}
}

Expand Down
25 changes: 12 additions & 13 deletions client/ssh/server/command_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
log "github.com/sirupsen/logrus"
)

// handleCommand executes an SSH command with privilege validation
func (s *Server) handleCommand(logger *log.Entry, session ssh.Session, privilegeResult PrivilegeCheckResult, winCh <-chan ssh.Window) {
// handleExecution executes an SSH command or shell with privilege validation
func (s *Server) handleExecution(logger *log.Entry, session ssh.Session, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) {
hasPty := winCh != nil

commandType := "command"
Expand All @@ -23,7 +23,7 @@ func (s *Server) handleCommand(logger *log.Entry, session ssh.Session, privilege

logger.Infof("executing %s: %s", commandType, safeLogCommand(session.Command()))

execCmd, cleanup, err := s.createCommand(privilegeResult, session, hasPty)
execCmd, cleanup, err := s.createCommand(logger, privilegeResult, session, hasPty)
if err != nil {
logger.Errorf("%s creation failed: %v", commandType, err)

Expand Down Expand Up @@ -51,13 +51,12 @@ func (s *Server) handleCommand(logger *log.Entry, session ssh.Session, privilege

defer cleanup()

ptyReq, _, _ := session.Pty()
if s.executeCommandWithPty(logger, session, execCmd, privilegeResult, ptyReq, winCh) {
logger.Debugf("%s execution completed", commandType)
}
}

func (s *Server) createCommand(privilegeResult PrivilegeCheckResult, session ssh.Session, hasPty bool) (*exec.Cmd, func(), error) {
func (s *Server) createCommand(logger *log.Entry, privilegeResult PrivilegeCheckResult, session ssh.Session, hasPty bool) (*exec.Cmd, func(), error) {
localUser := privilegeResult.User
if localUser == nil {
return nil, nil, errors.New("no user in privilege result")
Expand All @@ -66,28 +65,28 @@ func (s *Server) createCommand(privilegeResult PrivilegeCheckResult, session ssh
// If PTY requested but su doesn't support --pty, skip su and use executor
// This ensures PTY functionality is provided (executor runs within our allocated PTY)
if hasPty && !s.suSupportsPty {
log.Debugf("PTY requested but su doesn't support --pty, using executor for PTY functionality")
cmd, cleanup, err := s.createExecutorCommand(session, localUser, hasPty)
logger.Debugf("PTY requested but su doesn't support --pty, using executor for PTY functionality")
cmd, cleanup, err := s.createExecutorCommand(logger, session, localUser, hasPty)
if err != nil {
return nil, nil, fmt.Errorf("create command with privileges: %w", err)
}
cmd.Env = s.prepareCommandEnv(localUser, session)
cmd.Env = s.prepareCommandEnv(logger, localUser, session)
return cmd, cleanup, nil
}

// Try su first for system integration (PAM/audit) when privileged
cmd, err := s.createSuCommand(session, localUser, hasPty)
cmd, err := s.createSuCommand(logger, session, localUser, hasPty)
if err != nil || privilegeResult.UsedFallback {
log.Debugf("su command failed, falling back to executor: %v", err)
cmd, cleanup, err := s.createExecutorCommand(session, localUser, hasPty)
logger.Debugf("su command failed, falling back to executor: %v", err)
cmd, cleanup, err := s.createExecutorCommand(logger, session, localUser, hasPty)
if err != nil {
return nil, nil, fmt.Errorf("create command with privileges: %w", err)
}
cmd.Env = s.prepareCommandEnv(localUser, session)
cmd.Env = s.prepareCommandEnv(logger, localUser, session)
return cmd, cleanup, nil
}

cmd.Env = s.prepareCommandEnv(localUser, session)
cmd.Env = s.prepareCommandEnv(logger, localUser, session)
return cmd, func() {}, nil
}

Expand Down
7 changes: 4 additions & 3 deletions client/ssh/server/command_execution_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ import (
var errNotSupported = errors.New("SSH server command execution not supported on WASM/JS platform")

// createSuCommand is not supported on JS/WASM
func (s *Server) createSuCommand(_ ssh.Session, _ *user.User, _ bool) (*exec.Cmd, error) {
func (s *Server) createSuCommand(_ *log.Entry, _ ssh.Session, _ *user.User, _ bool) (*exec.Cmd, error) {
return nil, errNotSupported
}

// createExecutorCommand is not supported on JS/WASM
func (s *Server) createExecutorCommand(_ ssh.Session, _ *user.User, _ bool) (*exec.Cmd, func(), error) {
func (s *Server) createExecutorCommand(_ *log.Entry, _ ssh.Session, _ *user.User, _ bool) (*exec.Cmd, func(), error) {
return nil, nil, errNotSupported
}

// prepareCommandEnv is not supported on JS/WASM
func (s *Server) prepareCommandEnv(_ *user.User, _ ssh.Session) []string {
func (s *Server) prepareCommandEnv(_ *log.Entry, _ *user.User, _ ssh.Session) []string {
return nil
}

Expand Down Expand Up @@ -55,3 +55,4 @@ func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, e
}
return false
}

25 changes: 15 additions & 10 deletions client/ssh/server/command_execution_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,24 +99,29 @@ func (s *Server) detectUtilLinuxLogin(ctx context.Context) bool {
return isUtilLinux
}

// createSuCommand creates a command using su -l -c for privilege switching
func (s *Server) createSuCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
// createSuCommand creates a command using su -l for privilege switching
func (s *Server) createSuCommand(logger *log.Entry, session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
if err := validateUsername(localUser.Username); err != nil {
return nil, fmt.Errorf("invalid username %q: %w", localUser.Username, err)
}

suPath, err := exec.LookPath("su")
if err != nil {
return nil, fmt.Errorf("su command not available: %w", err)
}

command := session.RawCommand()
if command == "" {
return nil, fmt.Errorf("no command specified for su execution")
}

args := []string{"-l"}
if hasPty && s.suSupportsPty {
args = append(args, "--pty")
}
args = append(args, localUser.Username, "-c", command)
args = append(args, localUser.Username)

command := session.RawCommand()
if command != "" {
args = append(args, "-c", command)
}

logger.Debugf("creating su command: %s %v", suPath, args)
cmd := exec.CommandContext(session.Context(), suPath, args...)
cmd.Dir = localUser.HomeDir

Expand All @@ -132,7 +137,7 @@ func (s *Server) getShellCommandArgs(shell, cmdString string) []string {
}

// prepareCommandEnv prepares environment variables for command execution on Unix
func (s *Server) prepareCommandEnv(localUser *user.User, session ssh.Session) []string {
func (s *Server) prepareCommandEnv(_ *log.Entry, localUser *user.User, session ssh.Session) []string {
env := prepareUserEnv(localUser, getUserShell(localUser.Uid))
env = append(env, prepareSSHEnv(session)...)
for _, v := range session.Environ() {
Expand All @@ -154,7 +159,7 @@ func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, e
return s.runPtyCommand(logger, session, execCmd, ptyReq, winCh)
}

func (s *Server) handlePty(logger *log.Entry, session ssh.Session, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
func (s *Server) handlePtyLogin(logger *log.Entry, session ssh.Session, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
execCmd, err := s.createPtyCommand(privilegeResult, ptyReq, session)
if err != nil {
logger.Errorf("Pty command creation failed: %v", err)
Expand Down
Loading
Loading