mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-01 04:21:29 +02:00
Fix lint
This commit is contained in:
@@ -77,18 +77,17 @@ func sftpMainDirect(cmd *cobra.Command) error {
|
||||
os.Exit(sshserver.ExitCodeShellExecFail)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := sftpServer.Close(); err != nil {
|
||||
log.Debugf("SFTP server close error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugf("starting SFTP server")
|
||||
exitCode := sshserver.ExitCodeSuccess
|
||||
if err := sftpServer.Serve(); err != nil && !errors.Is(err, io.EOF) {
|
||||
cmd.PrintErrf("SFTP server error: %v\n", err)
|
||||
os.Exit(sshserver.ExitCodeShellExecFail)
|
||||
exitCode = sshserver.ExitCodeShellExecFail
|
||||
}
|
||||
|
||||
os.Exit(sshserver.ExitCodeSuccess)
|
||||
if err := sftpServer.Close(); err != nil {
|
||||
log.Debugf("SFTP server close error: %v", err)
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@@ -150,44 +149,6 @@ func (s *Server) handleCommandIO(logger *log.Entry, stdinPipe io.WriteCloser, se
|
||||
}
|
||||
}
|
||||
|
||||
// createPtyCommandWithPrivileges creates the exec.Cmd for Pty execution respecting privilege check results
|
||||
func (s *Server) createPtyCommandWithPrivileges(cmd []string, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, session ssh.Session) (*exec.Cmd, error) {
|
||||
localUser := privilegeResult.User
|
||||
|
||||
if privilegeResult.RequiresUserSwitching {
|
||||
return s.createPtyUserSwitchCommand(cmd, localUser, ptyReq, session)
|
||||
}
|
||||
|
||||
// No user switching needed - create direct Pty command
|
||||
shell := getUserShell(localUser.Uid)
|
||||
rawCmd := session.RawCommand()
|
||||
args := s.getShellCommandArgs(shell, rawCmd)
|
||||
execCmd := exec.CommandContext(session.Context(), args[0], args[1:]...)
|
||||
|
||||
execCmd.Dir = localUser.HomeDir
|
||||
execCmd.Env = s.preparePtyEnv(localUser, ptyReq, session)
|
||||
return execCmd, nil
|
||||
}
|
||||
|
||||
// preparePtyEnv prepares environment variables for Pty execution
|
||||
func (s *Server) preparePtyEnv(localUser *user.User, ptyReq ssh.Pty, session ssh.Session) []string {
|
||||
termType := ptyReq.Term
|
||||
if termType == "" {
|
||||
termType = "xterm-256color"
|
||||
}
|
||||
|
||||
env := prepareUserEnv(localUser, getUserShell(localUser.Uid))
|
||||
env = append(env, prepareSSHEnv(session)...)
|
||||
env = append(env, fmt.Sprintf("TERM=%s", termType))
|
||||
|
||||
for _, v := range session.Environ() {
|
||||
if acceptEnv(v) {
|
||||
env = append(env, v)
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// waitForCommandCleanup waits for command completion with session disconnect handling
|
||||
func (s *Server) waitForCommandCleanup(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd) bool {
|
||||
ctx := session.Context()
|
||||
|
||||
@@ -21,23 +21,6 @@ import (
|
||||
"github.com/netbirdio/netbird/client/ssh/server/winpty"
|
||||
)
|
||||
|
||||
// createCommandWithUserSwitch creates a command with Windows user switching
|
||||
func (s *Server) createCommandWithUserSwitch(_ []string, localUser *user.User, session ssh.Session) (*exec.Cmd, error) {
|
||||
username, domain := s.parseUsername(localUser.Username)
|
||||
shell := getUserShell(localUser.Uid)
|
||||
rawCmd := session.RawCommand()
|
||||
|
||||
privilegeDropper := NewPrivilegeDropper()
|
||||
cmd, err := privilegeDropper.CreateWindowsShellAsUser(
|
||||
session.Context(), shell, rawCmd, username, domain, localUser.HomeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("Created Windows command with user switching for %s", localUser.Username)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// getUserEnvironment retrieves the Windows environment for the target user.
|
||||
// Follows OpenSSH's resilient approach with graceful degradation on failures.
|
||||
func (s *Server) getUserEnvironment(username, domain string) ([]string, error) {
|
||||
|
||||
@@ -56,7 +56,7 @@ const (
|
||||
|
||||
// Common error messages
|
||||
commandFlag = "-Command"
|
||||
closeTokenError = "close token error: %v"
|
||||
closeTokenErrorMsg = "close token error: %v"
|
||||
convertUsernameError = "convert username to UTF16: %w"
|
||||
convertDomainError = "convert domain to UTF16: %w"
|
||||
)
|
||||
@@ -455,34 +455,6 @@ func (pd *PrivilegeDropper) authenticateDomainUser(username, domain, fullUsernam
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// closeUserToken safely closes a Windows user token handle
|
||||
func (pd *PrivilegeDropper) closeUserToken(token windows.Handle) {
|
||||
if err := windows.CloseHandle(token); err != nil {
|
||||
log.Debugf("close handle error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// buildCommandArgs constructs command arguments based on configuration
|
||||
func (pd *PrivilegeDropper) buildCommandArgs(config WindowsExecutorConfig) []string {
|
||||
shell := config.Shell
|
||||
|
||||
// Use structured args if provided
|
||||
if len(config.Args) > 0 {
|
||||
args := []string{shell}
|
||||
args = append(args, config.Args...)
|
||||
return args
|
||||
}
|
||||
|
||||
// Use command string if provided
|
||||
if config.Command != "" {
|
||||
return []string{shell, commandFlag, config.Command}
|
||||
}
|
||||
if config.Interactive {
|
||||
return []string{shell, "-NoExit"}
|
||||
}
|
||||
return []string{shell}
|
||||
}
|
||||
|
||||
// CreateWindowsProcessAsUserWithArgs creates a process as user with safe argument passing (for SFTP and executables)
|
||||
func (pd *PrivilegeDropper) CreateWindowsProcessAsUserWithArgs(ctx context.Context, executablePath string, args []string, username, domain, workingDir string) (*exec.Cmd, error) {
|
||||
fullUsername := buildUserCpn(username, domain)
|
||||
@@ -515,7 +487,7 @@ func (pd *PrivilegeDropper) CreateWindowsShellAsUser(ctx context.Context, shell,
|
||||
log.Debugf("using S4U authentication for user %s", fullUsername)
|
||||
defer func() {
|
||||
if err := windows.CloseHandle(token); err != nil {
|
||||
log.Debugf(closeTokenError, err)
|
||||
log.Debugf(closeTokenErrorMsg, err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -549,45 +521,6 @@ func (pd *PrivilegeDropper) createProcessWithToken(ctx context.Context, sourceTo
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (pd *PrivilegeDropper) validateCurrentUser(config WindowsExecutorConfig) error {
|
||||
currentUser, err := lookupUser("")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get current user for SSH exec security verification: %v", err)
|
||||
return fmt.Errorf("get current user: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("SSH exec process running as: %s (UID: %s, Name: %s)", currentUser.Username, currentUser.Uid, currentUser.Name)
|
||||
|
||||
if config.Username == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
requestedUsername := config.Username
|
||||
if config.Domain != "" {
|
||||
requestedUsername = fmt.Sprintf(`%s\%s`, config.Domain, config.Username)
|
||||
}
|
||||
|
||||
if !isWindowsSameUser(requestedUsername, currentUser.Username) {
|
||||
return fmt.Errorf("username mismatch: requested user %s but running as %s",
|
||||
requestedUsername, currentUser.Username)
|
||||
}
|
||||
|
||||
log.Debugf("SSH exec process verified running as correct user: %s (UID: %s)", currentUser.Username, currentUser.Uid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pd *PrivilegeDropper) changeWorkingDirectory(workingDir string) error {
|
||||
if workingDir == "" {
|
||||
return nil
|
||||
}
|
||||
return os.Chdir(workingDir)
|
||||
}
|
||||
|
||||
// parseUserCredentials extracts Windows user information
|
||||
func (s *Server) parseUserCredentials(_ *user.User) (uint32, uint32, []uint32, error) {
|
||||
return 0, 0, []uint32{0}, nil
|
||||
}
|
||||
|
||||
// createSuCommand creates a command using su -l -c for privilege switching (Windows stub)
|
||||
func (s *Server) createSuCommand(ssh.Session, *user.User) (*exec.Cmd, error) {
|
||||
return nil, fmt.Errorf("su command not available on Windows")
|
||||
|
||||
@@ -243,3 +243,41 @@ func (s *Server) createDirectCommand(session ssh.Session, localUser *user.User)
|
||||
func enableUserSwitching() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// createPtyCommandWithPrivileges creates the exec.Cmd for Pty execution respecting privilege check results
|
||||
func (s *Server) createPtyCommandWithPrivileges(cmd []string, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, session ssh.Session) (*exec.Cmd, error) {
|
||||
localUser := privilegeResult.User
|
||||
|
||||
if privilegeResult.RequiresUserSwitching {
|
||||
return s.createPtyUserSwitchCommand(cmd, localUser, ptyReq, session)
|
||||
}
|
||||
|
||||
// No user switching needed - create direct Pty command
|
||||
shell := getUserShell(localUser.Uid)
|
||||
rawCmd := session.RawCommand()
|
||||
args := s.getShellCommandArgs(shell, rawCmd)
|
||||
execCmd := exec.CommandContext(session.Context(), args[0], args[1:]...)
|
||||
|
||||
execCmd.Dir = localUser.HomeDir
|
||||
execCmd.Env = s.preparePtyEnv(localUser, ptyReq, session)
|
||||
return execCmd, nil
|
||||
}
|
||||
|
||||
// preparePtyEnv prepares environment variables for Pty execution
|
||||
func (s *Server) preparePtyEnv(localUser *user.User, ptyReq ssh.Pty, session ssh.Session) []string {
|
||||
termType := ptyReq.Term
|
||||
if termType == "" {
|
||||
termType = "xterm-256color"
|
||||
}
|
||||
|
||||
env := prepareUserEnv(localUser, getUserShell(localUser.Uid))
|
||||
env = append(env, prepareSSHEnv(session)...)
|
||||
env = append(env, fmt.Sprintf("TERM=%s", termType))
|
||||
|
||||
for _, v := range session.Environ() {
|
||||
if acceptEnv(v) {
|
||||
env = append(env, v)
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -60,15 +60,6 @@ func validateUsername(username string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSecureUserSwitchCommand creates a command for Windows with user switching support
|
||||
func (s *Server) createSecureUserSwitchCommand(_ []string, localUser *user.User, session ssh.Session) (*exec.Cmd, error) {
|
||||
winCmd, err := s.createUserSwitchCommand(localUser, session, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Windows user switching failed for %s: %w", localUser.Username, err)
|
||||
}
|
||||
return winCmd, nil
|
||||
}
|
||||
|
||||
// createExecutorCommand creates a command using Windows executor for privilege dropping
|
||||
func (s *Server) createExecutorCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
|
||||
log.Debugf("creating Windows executor command for user %s (Pty: %v)", localUser.Username, hasPty)
|
||||
@@ -86,16 +77,6 @@ func (s *Server) createDirectCommand(session ssh.Session, localUser *user.User)
|
||||
return nil, fmt.Errorf("direct command execution not supported on Windows - use user switching with token creation")
|
||||
}
|
||||
|
||||
// createPtyUserSwitchCommand creates a Pty command with user switching for Windows
|
||||
func (s *Server) createPtyUserSwitchCommand(_ []string, localUser *user.User, ptyReq ssh.Pty, session ssh.Session) (*exec.Cmd, error) {
|
||||
return s.createUserSwitchCommand(localUser, session, true)
|
||||
}
|
||||
|
||||
// createSecurePtyUserSwitchCommand creates a Pty command with secure privilege dropping
|
||||
func (s *Server) createSecurePtyUserSwitchCommand([]string, *user.User, ssh.Pty, ssh.Session) (*exec.Cmd, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// createUserSwitchCommand creates a command with Windows user switching
|
||||
func (s *Server) createUserSwitchCommand(localUser *user.User, session ssh.Session, interactive bool) (*exec.Cmd, error) {
|
||||
username, domain := s.parseUsername(localUser.Username)
|
||||
@@ -131,9 +112,7 @@ func (s *Server) parseUsername(fullUsername string) (username, domain string) {
|
||||
}
|
||||
|
||||
// Handle username@domain format
|
||||
if idx := strings.Index(fullUsername, "@"); idx != -1 {
|
||||
username = fullUsername[:idx]
|
||||
domain = fullUsername[idx+1:]
|
||||
if username, domain, ok := strings.Cut(fullUsername, "@"); ok {
|
||||
return username, domain
|
||||
}
|
||||
|
||||
@@ -141,37 +120,6 @@ func (s *Server) parseUsername(fullUsername string) (username, domain string) {
|
||||
return fullUsername, "."
|
||||
}
|
||||
|
||||
// validateUserSwitchingPrivileges validates Windows-specific user switching privileges
|
||||
// This checks for SeAssignPrimaryTokenPrivilege which is required for CreateProcessWithTokenW
|
||||
func validateUserSwitchingPrivileges() error {
|
||||
process := windows.CurrentProcess()
|
||||
|
||||
var token windows.Token
|
||||
err := windows.OpenProcessToken(
|
||||
process,
|
||||
windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY,
|
||||
&token,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open process token: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := windows.CloseHandle(windows.Handle(token)); err != nil {
|
||||
log.Warnf("close process token: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hasAssignToken, err := hasPrivilege(windows.Handle(token), "SeAssignPrimaryTokenPrivilege")
|
||||
if err != nil {
|
||||
return fmt.Errorf("has validation: %w", err)
|
||||
}
|
||||
if !hasAssignToken {
|
||||
return ErrPrivilegeRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasPrivilege checks if the current process has a specific privilege
|
||||
func hasPrivilege(token windows.Handle, privilegeName string) (bool, error) {
|
||||
var luid windows.LUID
|
||||
|
||||
@@ -4,6 +4,7 @@ package winpty
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -16,6 +17,10 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyEnvironment = errors.New("empty environment")
|
||||
)
|
||||
|
||||
const (
|
||||
extendedStartupInfoPresent = 0x00080000
|
||||
createUnicodeEnvironment = 0x00000400
|
||||
@@ -277,7 +282,7 @@ func createConPtyProcess(commandLine string, userToken windows.Handle, userEnv [
|
||||
// convertEnvironmentToUTF16 converts environment variables to Windows UTF16 format.
|
||||
func convertEnvironmentToUTF16(userEnv []string) (*uint16, error) {
|
||||
if len(userEnv) == 0 {
|
||||
return nil, nil
|
||||
return nil, ErrEmptyEnvironment
|
||||
}
|
||||
|
||||
var envUTF16 []uint16
|
||||
@@ -297,7 +302,7 @@ func convertEnvironmentToUTF16(userEnv []string) (*uint16, error) {
|
||||
if len(envUTF16) > 0 {
|
||||
return &envUTF16[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, ErrEmptyEnvironment
|
||||
}
|
||||
|
||||
// duplicateToPrimaryToken converts an impersonation token to a primary token.
|
||||
|
||||
@@ -5,6 +5,7 @@ package winpty
|
||||
import (
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -280,7 +281,9 @@ func BenchmarkConPtyCreation(b *testing.B) {
|
||||
}
|
||||
|
||||
// Clean up
|
||||
procClosePseudoConsole.Call(uintptr(hPty))
|
||||
if ret, _, err := procClosePseudoConsole.Call(uintptr(hPty)); ret == 0 {
|
||||
log.Debugf("ClosePseudoConsole failed: %v", err)
|
||||
}
|
||||
closeHandles(inputRead, inputWrite, outputRead, outputWrite)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user