mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-23 05:49:07 +02:00
Add elevation for dangerous ssh flags
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetSSHConfigCmdName is the hidden subcommand an elevated process runs to apply
|
||||||
|
// the privileged SSH settings.
|
||||||
|
const SetSSHConfigCmdName = "set-ssh-config"
|
||||||
|
|
||||||
|
// sshConfigElevated is set by runInDaemonMode once the dangerous SSH settings
|
||||||
|
// have been applied by an elevated helper, so the (unprivileged) up flow does
|
||||||
|
// not re-send them and re-trip the daemon gate.
|
||||||
|
var sshConfigElevated bool
|
||||||
|
|
||||||
|
// wantsDangerousSSH reports whether this invocation is trying to ENABLE SSH root
|
||||||
|
// login or DISABLE SSH authentication.
|
||||||
|
func wantsDangerousSSH(cmd *cobra.Command) bool {
|
||||||
|
return (cmd.Flags().Changed(enableSSHRootFlag) && enableSSHRoot) ||
|
||||||
|
(cmd.Flags().Changed(disableSSHAuthFlag) && disableSSHAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSetSSHConfigArgs builds the argument list for an elevated
|
||||||
|
// `netbird set-ssh-config` invocation.
|
||||||
|
func buildSetSSHConfigArgs(profileName, username string, enableSSHRoot, disableSSHAuth *bool, daemonAddr string) []string {
|
||||||
|
args := []string{SetSSHConfigCmdName}
|
||||||
|
if profileName != "" {
|
||||||
|
args = append(args, "--profile", profileName)
|
||||||
|
}
|
||||||
|
if username != "" {
|
||||||
|
args = append(args, "--username", username)
|
||||||
|
}
|
||||||
|
if enableSSHRoot != nil && *enableSSHRoot {
|
||||||
|
args = append(args, "--"+enableSSHRootFlag)
|
||||||
|
}
|
||||||
|
if disableSSHAuth != nil && *disableSSHAuth {
|
||||||
|
args = append(args, "--"+disableSSHAuthFlag)
|
||||||
|
}
|
||||||
|
if daemonAddr != "" {
|
||||||
|
args = append(args, "--daemon-addr", daemonAddr)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElevateSSHConfig re-runs the current executable's `set-ssh-config` command with
|
||||||
|
// root/administrator privileges via the platform's prompt (pkexec/UAC/osascript),
|
||||||
|
// so the elevated process connects to the daemon with a privileged identity and
|
||||||
|
// the daemon's requirePrivilegedForDangerousSSH gate passes.
|
||||||
|
func ElevateSSHConfig(profileName, username string, enableSSHRoot, disableSSHAuth *bool) error {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("locate executable for elevation: %w", err)
|
||||||
|
}
|
||||||
|
return runElevated(exe, buildSetSSHConfigArgs(profileName, username, enableSSHRoot, disableSSHAuth, daemonAddr))
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isProcessPrivileged reports whether the current process runs as root.
|
||||||
|
func isProcessPrivileged() bool { return os.Geteuid() == 0 }
|
||||||
|
|
||||||
|
// TODO(ssh-elevation): implement the osascript admin prompt.
|
||||||
|
func runElevated(_ string, _ []string) error {
|
||||||
|
return fmt.Errorf("automatic privilege elevation is not yet implemented on macOS, re-run with sudo")
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isProcessPrivileged reports whether the current process runs as root.
|
||||||
|
func isProcessPrivileged() bool { return os.Geteuid() == 0 }
|
||||||
|
|
||||||
|
// runElevated re-runs exe with args as root. On a graphical session it uses
|
||||||
|
// pkexec, which drives the desktop's polkit authentication agent (GUI prompt).
|
||||||
|
func runElevated(exe string, args []string) error {
|
||||||
|
if !hasGraphicalSession() {
|
||||||
|
return fmt.Errorf("cannot request privilege elevation without a graphical session. re-run as root: sudo %s %s", exe, strings.Join(args, " "))
|
||||||
|
}
|
||||||
|
pkexec, err := exec.LookPath("pkexec")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pkexec not found for privilege elevation, re-run as root: sudo %s %s", exe, strings.Join(args, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := exec.Command(pkexec, append([]string{exe}, args...)...)
|
||||||
|
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return fmt.Errorf("elevated %s failed (elevation cancelled or denied?): %w", SetSSHConfigCmdName, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasGraphicalSession heuristically reports whether a desktop session is present
|
||||||
|
// that polkit can prompt in.
|
||||||
|
func hasGraphicalSession() bool {
|
||||||
|
return os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build !linux && !darwin && !windows
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isProcessPrivileged reports whether the current process runs as root.
|
||||||
|
func isProcessPrivileged() bool { return os.Geteuid() == 0 }
|
||||||
|
|
||||||
|
// runElevated has no automatic elevation mechanism on these platforms
|
||||||
|
func runElevated(exe string, args []string) error {
|
||||||
|
return fmt.Errorf("automatic privilege elevation is not supported on this platform, re-run as root: sudo %s %s", exe, strings.Join(args, " "))
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//go:build !ios && !android
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildSetSSHConfigArgs(t *testing.T) {
|
||||||
|
tr, fa := true, false
|
||||||
|
|
||||||
|
t.Run("enable root only, with daemon-addr", func(t *testing.T) {
|
||||||
|
got := buildSetSSHConfigArgs("prof", "alice", &tr, nil, "unix:///x.sock")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
SetSSHConfigCmdName, "--profile", "prof", "--username", "alice",
|
||||||
|
"--" + enableSSHRootFlag, "--daemon-addr", "unix:///x.sock",
|
||||||
|
}, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable auth only, no daemon-addr", func(t *testing.T) {
|
||||||
|
got := buildSetSSHConfigArgs("prof", "alice", nil, &tr, "")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
SetSSHConfigCmdName, "--profile", "prof", "--username", "alice",
|
||||||
|
"--" + disableSSHAuthFlag,
|
||||||
|
}, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("false pointers omit the flags", func(t *testing.T) {
|
||||||
|
got := buildSetSSHConfigArgs("", "", &fa, &fa, "")
|
||||||
|
assert.Equal(t, []string{SetSSHConfigCmdName}, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("both enabled", func(t *testing.T) {
|
||||||
|
got := buildSetSSHConfigArgs("p", "u", &tr, &tr, "")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
SetSSHConfigCmdName, "--profile", "p", "--username", "u",
|
||||||
|
"--" + enableSSHRootFlag, "--" + disableSSHAuthFlag,
|
||||||
|
}, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildSetSSHConfigRequest(t *testing.T) {
|
||||||
|
tr := true
|
||||||
|
|
||||||
|
req := buildSetSSHConfigRequest("p", "u", &tr, nil)
|
||||||
|
assert.Equal(t, "p", req.ProfileName)
|
||||||
|
assert.Equal(t, "u", req.Username)
|
||||||
|
if assert.NotNil(t, req.EnableSSHRoot) {
|
||||||
|
assert.True(t, *req.EnableSSHRoot)
|
||||||
|
}
|
||||||
|
assert.Nil(t, req.DisableSSHAuth, "an unset flag must leave the daemon value untouched")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWantsDangerousSSH(t *testing.T) {
|
||||||
|
origRoot, origAuth := enableSSHRoot, disableSSHAuth
|
||||||
|
t.Cleanup(func() { enableSSHRoot, disableSSHAuth = origRoot, origAuth })
|
||||||
|
|
||||||
|
newCmd := func() *cobra.Command {
|
||||||
|
enableSSHRoot, disableSSHAuth = false, false
|
||||||
|
c := &cobra.Command{Use: "x"}
|
||||||
|
c.Flags().BoolVar(&enableSSHRoot, enableSSHRootFlag, false, "")
|
||||||
|
c.Flags().BoolVar(&disableSSHAuth, disableSSHAuthFlag, false, "")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// wantsDangerousSSH fires only in the privileged direction.
|
||||||
|
t.Run("enable root true is dangerous", func(t *testing.T) {
|
||||||
|
c := newCmd()
|
||||||
|
require.NoError(t, c.Flags().Set(enableSSHRootFlag, "true"))
|
||||||
|
assert.True(t, wantsDangerousSSH(c))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("enable root false is not dangerous", func(t *testing.T) {
|
||||||
|
c := newCmd()
|
||||||
|
require.NoError(t, c.Flags().Set(enableSSHRootFlag, "false"))
|
||||||
|
assert.False(t, wantsDangerousSSH(c))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable auth true is dangerous", func(t *testing.T) {
|
||||||
|
c := newCmd()
|
||||||
|
require.NoError(t, c.Flags().Set(disableSSHAuthFlag, "true"))
|
||||||
|
assert.True(t, wantsDangerousSSH(c))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable auth false is not dangerous", func(t *testing.T) {
|
||||||
|
c := newCmd()
|
||||||
|
require.NoError(t, c.Flags().Set(disableSSHAuthFlag, "false"))
|
||||||
|
assert.False(t, wantsDangerousSSH(c))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nothing changed is not dangerous", func(t *testing.T) {
|
||||||
|
c := newCmd()
|
||||||
|
assert.False(t, wantsDangerousSSH(c))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isProcessPrivileged reports whether the current process token is elevated
|
||||||
|
// (running as administrator / LocalSystem).
|
||||||
|
func isProcessPrivileged() bool {
|
||||||
|
return windows.GetCurrentProcessToken().IsElevated()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runElevated should re-run exe with args elevated via a UAC prompt
|
||||||
|
// (ShellExecuteEx with the "runas" verb).
|
||||||
|
//
|
||||||
|
// TODO(ssh-elevation): implement ShellExecuteEx("runas") + wait for the child.
|
||||||
|
func runElevated(_ string, _ []string) error {
|
||||||
|
return fmt.Errorf("automatic privilege elevation is not yet implemented on Windows, re-run netbird as administrator")
|
||||||
|
}
|
||||||
@@ -176,6 +176,8 @@ func init() {
|
|||||||
rootCmd.AddCommand(ownerCmd)
|
rootCmd.AddCommand(ownerCmd)
|
||||||
ownerCmd.AddCommand(ownerAddCmd, ownerResetCmd, ownerShareCmd, ownerUnshareCmd)
|
ownerCmd.AddCommand(ownerAddCmd, ownerResetCmd, ownerShareCmd, ownerUnshareCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(setSSHConfigCmd)
|
||||||
|
|
||||||
networksCMD.AddCommand(routesListCmd)
|
networksCMD.AddCommand(routesListCmd)
|
||||||
networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd)
|
networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
setSSHConfigProfile string
|
||||||
|
setSSHConfigUsername string
|
||||||
|
setSSHConfigEnableRoot bool
|
||||||
|
setSSHConfigDisableAuth bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// setSSHConfigCmd applies the privileged SSH settings (enable root login /
|
||||||
|
// disable auth) to a profile over the daemon IPC. Hidden because users
|
||||||
|
// interact via `netbird up` / the UI, not directly.
|
||||||
|
var setSSHConfigCmd = &cobra.Command{
|
||||||
|
Use: SetSSHConfigCmdName,
|
||||||
|
Short: "Apply privileged SSH settings to a profile (internal elevation target)",
|
||||||
|
Hidden: true,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to daemon: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
log.Warnf("failed closing daemon gRPC client connection: %v", cerr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var enableRoot, disableAuth *bool
|
||||||
|
if cmd.Flags().Changed(enableSSHRootFlag) {
|
||||||
|
enableRoot = &setSSHConfigEnableRoot
|
||||||
|
}
|
||||||
|
if cmd.Flags().Changed(disableSSHAuthFlag) {
|
||||||
|
disableAuth = &setSSHConfigDisableAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
req := buildSetSSHConfigRequest(setSSHConfigProfile, setSSHConfigUsername, enableRoot, disableAuth)
|
||||||
|
if _, err := proto.NewDaemonServiceClient(conn).SetConfig(ctx, req); err != nil {
|
||||||
|
return fmt.Errorf("apply SSH config: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSetSSHConfigRequest builds a minimal SetConfigRequest that touches only
|
||||||
|
// the SSH fields that were provided (nil pointers leave the daemon's stored
|
||||||
|
// value untouched, matching setupSetConfigReq's pointer semantics).
|
||||||
|
func buildSetSSHConfigRequest(profileName, username string, enableSSHRoot, disableSSHAuth *bool) *proto.SetConfigRequest {
|
||||||
|
return &proto.SetConfigRequest{
|
||||||
|
ProfileName: profileName,
|
||||||
|
Username: username,
|
||||||
|
EnableSSHRoot: enableSSHRoot,
|
||||||
|
DisableSSHAuth: disableSSHAuth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setSSHConfigCmd.Flags().StringVar(&setSSHConfigProfile, "profile", "", "profile name to apply the SSH settings to")
|
||||||
|
setSSHConfigCmd.Flags().StringVar(&setSSHConfigUsername, "username", "", "owning username of the profile")
|
||||||
|
setSSHConfigCmd.Flags().BoolVar(&setSSHConfigEnableRoot, enableSSHRootFlag, false, "enable root login for the SSH server")
|
||||||
|
setSSHConfigCmd.Flags().BoolVar(&setSSHConfigDisableAuth, disableSSHAuthFlag, false, "disable SSH authentication")
|
||||||
|
}
|
||||||
+23
-5
@@ -21,8 +21,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
nbnet "github.com/netbirdio/netbird/client/net"
|
nbnet "github.com/netbirdio/netbird/client/net"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/server"
|
"github.com/netbirdio/netbird/client/server"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
@@ -321,6 +321,24 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command, pm *profilemanager
|
|||||||
return fmt.Errorf("get current user: %v", err)
|
return fmt.Errorf("get current user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enabling SSH root login / disabling SSH auth is a privileged change the
|
||||||
|
// daemon rejects from a non-privileged caller. Offer to elevate (polkit/UAC/
|
||||||
|
// osascript) so an elevated helper applies just those settings.
|
||||||
|
if wantsDangerousSSH(cmd) && !isProcessPrivileged() {
|
||||||
|
cmd.Println("Enabling SSH root login / disabling SSH authentication requires administrator privileges, requesting elevation...")
|
||||||
|
var enableRoot, disableAuth *bool
|
||||||
|
if cmd.Flag(enableSSHRootFlag).Changed {
|
||||||
|
enableRoot = &enableSSHRoot
|
||||||
|
}
|
||||||
|
if cmd.Flag(disableSSHAuthFlag).Changed {
|
||||||
|
disableAuth = &disableSSHAuth
|
||||||
|
}
|
||||||
|
if err := ElevateSSHConfig(activeProf.ID.String(), username.Username, enableRoot, disableAuth); err != nil {
|
||||||
|
return fmt.Errorf("apply privileged SSH settings: %w", err)
|
||||||
|
}
|
||||||
|
sshConfigElevated = true
|
||||||
|
}
|
||||||
|
|
||||||
// set the new config
|
// set the new config
|
||||||
req := setupSetConfigReq(customDNSAddressConverted, cmd, activeProf.ID.String(), username.Username)
|
req := setupSetConfigReq(customDNSAddressConverted, cmd, activeProf.ID.String(), username.Username)
|
||||||
if _, err := client.SetConfig(ctx, req); err != nil {
|
if _, err := client.SetConfig(ctx, req); err != nil {
|
||||||
@@ -424,7 +442,7 @@ func setupSetConfigReq(customDNSAddressConverted []byte, cmd *cobra.Command, pro
|
|||||||
if cmd.Flag(serverSSHAllowedFlag).Changed {
|
if cmd.Flag(serverSSHAllowedFlag).Changed {
|
||||||
req.ServerSSHAllowed = &serverSSHAllowed
|
req.ServerSSHAllowed = &serverSSHAllowed
|
||||||
}
|
}
|
||||||
if cmd.Flag(enableSSHRootFlag).Changed {
|
if cmd.Flag(enableSSHRootFlag).Changed && !sshConfigElevated {
|
||||||
req.EnableSSHRoot = &enableSSHRoot
|
req.EnableSSHRoot = &enableSSHRoot
|
||||||
}
|
}
|
||||||
if cmd.Flag(enableSSHSFTPFlag).Changed {
|
if cmd.Flag(enableSSHSFTPFlag).Changed {
|
||||||
@@ -436,7 +454,7 @@ func setupSetConfigReq(customDNSAddressConverted []byte, cmd *cobra.Command, pro
|
|||||||
if cmd.Flag(enableSSHRemotePortForwardFlag).Changed {
|
if cmd.Flag(enableSSHRemotePortForwardFlag).Changed {
|
||||||
req.EnableSSHRemotePortForwarding = &enableSSHRemotePortForward
|
req.EnableSSHRemotePortForwarding = &enableSSHRemotePortForward
|
||||||
}
|
}
|
||||||
if cmd.Flag(disableSSHAuthFlag).Changed {
|
if cmd.Flag(disableSSHAuthFlag).Changed && !sshConfigElevated {
|
||||||
req.DisableSSHAuth = &disableSSHAuth
|
req.DisableSSHAuth = &disableSSHAuth
|
||||||
}
|
}
|
||||||
if cmd.Flag(sshJWTCacheTTLFlag).Changed {
|
if cmd.Flag(sshJWTCacheTTLFlag).Changed {
|
||||||
@@ -652,7 +670,7 @@ func setupLoginRequest(providedSetupKey string, customDNSAddressConverted []byte
|
|||||||
loginRequest.ServerSSHAllowed = &serverSSHAllowed
|
loginRequest.ServerSSHAllowed = &serverSSHAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Flag(enableSSHRootFlag).Changed {
|
if cmd.Flag(enableSSHRootFlag).Changed && !sshConfigElevated {
|
||||||
loginRequest.EnableSSHRoot = &enableSSHRoot
|
loginRequest.EnableSSHRoot = &enableSSHRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,7 +686,7 @@ func setupLoginRequest(providedSetupKey string, customDNSAddressConverted []byte
|
|||||||
loginRequest.EnableSSHRemotePortForwarding = &enableSSHRemotePortForward
|
loginRequest.EnableSSHRemotePortForwarding = &enableSSHRemotePortForward
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Flag(disableSSHAuthFlag).Changed {
|
if cmd.Flag(disableSSHAuthFlag).Changed && !sshConfigElevated {
|
||||||
loginRequest.DisableSSHAuth = &disableSSHAuth
|
loginRequest.DisableSSHAuth = &disableSSHAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/wailsapp/wails/v3/pkg/events"
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/notifications"
|
"github.com/wailsapp/wails/v3/pkg/services/notifications"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/cmd"
|
||||||
"github.com/netbirdio/netbird/client/ui/authsession"
|
"github.com/netbirdio/netbird/client/ui/authsession"
|
||||||
"github.com/netbirdio/netbird/client/ui/i18n"
|
"github.com/netbirdio/netbird/client/ui/i18n"
|
||||||
"github.com/netbirdio/netbird/client/ui/preferences"
|
"github.com/netbirdio/netbird/client/ui/preferences"
|
||||||
@@ -80,6 +82,16 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// When re-launched under a privilege prompt (pkexec/UAC/osascript) to apply
|
||||||
|
// the dangerous SSH settings, this binary is invoked as os.Executable().
|
||||||
|
// Delegate to the shared CLI command and exit before starting the GUI.
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == cmd.SetSSHConfigCmdName {
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
daemonAddr, userSetLogFile := parseFlagsAndInitLog()
|
daemonAddr, userSetLogFile := parseFlagsAndInitLog()
|
||||||
conn := NewConn(daemonAddr)
|
conn := NewConn(daemonAddr)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/cmd"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -190,9 +194,27 @@ func (s *Settings) SetConfig(ctx context.Context, p SetConfigParams) error {
|
|||||||
SshJWTCacheTTL: p.SSHJWTCacheTTL,
|
SshJWTCacheTTL: p.SSHJWTCacheTTL,
|
||||||
}
|
}
|
||||||
_, err = cli.SetConfig(ctx, req)
|
_, err = cli.SetConfig(ctx, req)
|
||||||
|
if err != nil && wantsDangerousSSHConfig(p) && status.Code(err) == codes.PermissionDenied {
|
||||||
|
// The daemon restricts enabling SSH root login / disabling SSH auth to
|
||||||
|
// root/administrator, and the UI runs as the unprivileged desktop user.
|
||||||
|
// Elevate (polkit/UAC/osascript) so a privileged helper applies just
|
||||||
|
// those flags.
|
||||||
|
if elevErr := cmd.ElevateSSHConfig(p.ProfileName, p.Username, p.EnableSSHRoot, p.DisableSSHAuth); elevErr != nil {
|
||||||
|
return elevErr
|
||||||
|
}
|
||||||
|
req.EnableSSHRoot = nil
|
||||||
|
req.DisableSSHAuth = nil
|
||||||
|
_, err = cli.SetConfig(ctx, req)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wantsDangerousSSHConfig reports whether p tries to enable SSH root login or
|
||||||
|
// disable SSH authentication.
|
||||||
|
func wantsDangerousSSHConfig(p SetConfigParams) bool {
|
||||||
|
return (p.EnableSSHRoot != nil && *p.EnableSSHRoot) || (p.DisableSSHAuth != nil && *p.DisableSSHAuth)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Settings) GetRestrictions(ctx context.Context) (Restrictions, error) {
|
func (s *Settings) GetRestrictions(ctx context.Context) (Restrictions, error) {
|
||||||
cli, err := s.conn.Client()
|
cli, err := s.conn.Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user