Files
netbird/client/ssh/login.go
2025-07-02 19:34:36 +02:00

108 lines
3.0 KiB
Go

package ssh
import (
"fmt"
"net"
"net/netip"
"os"
"os/exec"
"os/user"
"runtime"
"github.com/netbirdio/netbird/util"
)
func isRoot() bool {
return os.Geteuid() == 0
}
func getLoginCmd(username string, remoteAddr net.Addr) (loginPath string, args []string, err error) {
// First, validate the user exists
if err := validateUser(username); err != nil {
return "", nil, err
}
if runtime.GOOS == "windows" {
return getWindowsLoginCmd(username)
}
if !isRoot() {
return getNonRootLoginCmd(username)
}
return getRootLoginCmd(username, remoteAddr)
}
// validateUser checks if the requested user exists and is valid
func validateUser(username string) error {
if username == "" {
return fmt.Errorf("username cannot be empty")
}
// Check if user exists
if _, err := userNameLookup(username); err != nil {
return fmt.Errorf("user %s not found: %w", username, err)
}
return nil
}
// getWindowsLoginCmd handles Windows login (currently limited)
func getWindowsLoginCmd(username string) (string, []string, error) {
currentUser, err := user.Current()
if err != nil {
return "", nil, fmt.Errorf("get current user: %w", err)
}
// Check if requesting a different user
if currentUser.Username != username {
// TODO: Implement Windows user impersonation using CreateProcessAsUser
return "", nil, fmt.Errorf("Windows user switching not implemented: cannot switch from %s to %s", currentUser.Username, username)
}
shell := getUserShell(currentUser.Uid)
return shell, []string{}, nil
}
// getNonRootLoginCmd handles non-root process login
func getNonRootLoginCmd(username string) (string, []string, error) {
// Non-root processes can only SSH as themselves
currentUser, err := user.Current()
if err != nil {
return "", nil, fmt.Errorf("get current user: %w", err)
}
if username != "" && currentUser.Username != username {
return "", nil, fmt.Errorf("non-root process cannot switch users: requested %s but running as %s", username, currentUser.Username)
}
shell := getUserShell(currentUser.Uid)
return shell, []string{"-l"}, nil
}
// getRootLoginCmd handles root-privileged login with user switching
func getRootLoginCmd(username string, remoteAddr net.Addr) (string, []string, error) {
// Require login command to be available
loginPath, err := exec.LookPath("login")
if err != nil {
return "", nil, fmt.Errorf("login command not available: %w", err)
}
addrPort, err := netip.ParseAddrPort(remoteAddr.String())
if err != nil {
return "", nil, fmt.Errorf("parse remote address: %w", err)
}
switch runtime.GOOS {
case "linux":
if util.FileExists("/etc/arch-release") && !util.FileExists("/etc/pam.d/remote") {
return loginPath, []string{"-f", username, "-p"}, nil
}
return loginPath, []string{"-f", username, "-h", addrPort.Addr().String(), "-p"}, nil
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), username}, nil
default:
return "", nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS)
}
}