Require privilege for VNC server and approval config changes

This commit is contained in:
Viktor Liu
2026-07-30 10:43:26 +02:00
parent 2b051ad250
commit abe8bc1b53
9 changed files with 401 additions and 118 deletions

View File

@@ -26,40 +26,51 @@ import (
// daemon's SSH server into a root (or unauthenticated) shell.
// - Enabling the SSH server at all is what makes the above reachable, and a
// profile the caller owns is not a privilege they hold.
// - While the SSH server is enabled, repointing the profile at another
// management identity hands SSH authorization decisions, including which
// keys and users are accepted, to whoever controls that identity. Changing
// the management URL and deregistering the peer are both ways to do that.
// - Enabling the VNC server exposes the console session, which on a
// multi-user host belongs to another user, and disabling its approval
// prompt removes that user's only say in it.
// - While a remote-access server (SSH or VNC) is enabled, repointing the
// profile at another management identity hands authorization decisions,
// including which keys and users are accepted, to whoever controls that
// identity. Changing the management URL and deregistering the peer are both
// ways to do that.
//
// Everything else stays unauthenticated, so this is not an authorization model:
// it only refuses the changes that would let a local user become root. A caller
// whose identity cannot be established is refused as well.
// it only refuses the changes that would let a local user become root, or reach
// another user's desktop. A caller whose identity cannot be established is
// refused as well.
// privilegedConfigChange is the subset of a config request that crosses the
// user-to-root boundary. Fields are nil or empty when the request leaves them
// untouched.
type privilegedConfigChange struct {
managementURL string
serverSSHAllowed *bool
enableSSHRoot *bool
disableSSHAuth *bool
managementURL string
serverSSHAllowed *bool
enableSSHRoot *bool
disableSSHAuth *bool
serverVNCAllowed *bool
disableVNCApproval *bool
}
func privilegedChangeFromSetConfig(msg *proto.SetConfigRequest) privilegedConfigChange {
return privilegedConfigChange{
managementURL: msg.GetManagementUrl(),
serverSSHAllowed: msg.ServerSSHAllowed,
enableSSHRoot: msg.EnableSSHRoot,
disableSSHAuth: msg.DisableSSHAuth,
managementURL: msg.GetManagementUrl(),
serverSSHAllowed: msg.ServerSSHAllowed,
enableSSHRoot: msg.EnableSSHRoot,
disableSSHAuth: msg.DisableSSHAuth,
serverVNCAllowed: msg.ServerVNCAllowed,
disableVNCApproval: msg.DisableVNCApproval,
}
}
func privilegedChangeFromLogin(msg *proto.LoginRequest) privilegedConfigChange {
return privilegedConfigChange{
managementURL: msg.GetManagementUrl(),
serverSSHAllowed: msg.ServerSSHAllowed,
enableSSHRoot: msg.EnableSSHRoot,
disableSSHAuth: msg.DisableSSHAuth,
managementURL: msg.GetManagementUrl(),
serverSSHAllowed: msg.ServerSSHAllowed,
enableSSHRoot: msg.EnableSSHRoot,
disableSSHAuth: msg.DisableSSHAuth,
serverVNCAllowed: msg.ServerVNCAllowed,
disableVNCApproval: msg.DisableVNCApproval,
}
}
@@ -83,15 +94,21 @@ func requirePrivilegeForConfigChange(ctx context.Context, stored *profilemanager
return denyPrivileged(ctx, "enabling the NetBird SSH server", ipcauth.UpCommand("--allow-server-ssh"))
}
// Only guard the management binding while the SSH server is enabled: that is
// when the management identity decides who may open a shell here.
if !sshServerEnabled(stored) {
if err := requirePrivilegeForVNCChange(ctx, stored, change); err != nil {
return err
}
// Only guard the management binding while a remote-access server is enabled:
// that is when the management identity decides who may open a shell or reach
// the desktop here.
server, enabled := enabledRemoteAccessServer(stored)
if !enabled {
return nil
}
if change.managementURL != "" && !sameManagementURL(stored.ManagementURL, change.managementURL) {
return denyPrivileged(ctx,
"changing the management URL while the NetBird SSH server is enabled",
fmt.Sprintf("changing the management URL while the NetBird %s server is enabled", server),
ipcauth.UpCommand("-m "+change.managementURL))
}
@@ -99,20 +116,21 @@ func requirePrivilegeForConfigChange(ctx context.Context, stored *profilemanager
}
// requirePrivilegeForDeregistration refuses to deregister the peer from the
// management server when the caller is not privileged and the profile has the
// SSH server enabled. Deregistering frees the peer's key to be registered
// against another management identity, which is the same handover the
// management server when the caller is not privileged and the profile has a
// remote-access server enabled. Deregistering frees the peer's key to be
// registered against another management identity, which is the same handover the
// management URL check refuses.
//
// Callers that treat deregistration as best-effort (profile removal) continue
// without it; callers that were asked to deregister surface the error.
func requirePrivilegeForDeregistration(ctx context.Context, cfg *profilemanager.Config) error {
if !sshServerEnabled(cfg) {
server, enabled := enabledRemoteAccessServer(cfg)
if !enabled {
return nil
}
return denyPrivileged(ctx,
"deregistering this peer while the NetBird SSH server is enabled",
fmt.Sprintf("deregistering this peer while the NetBird %s server is enabled", server),
ipcauth.ElevatedCommand("netbird logout"))
}

58
client/server/vnc_gate.go Normal file
View File

@@ -0,0 +1,58 @@
package server
import (
"context"
"github.com/netbirdio/netbird/client/internal/ipcauth"
"github.com/netbirdio/netbird/client/internal/profilemanager"
)
// The VNC half of the privilege gate described in ssh_gate.go. The daemon runs
// as root/LocalSystem and the VNC server it hosts attaches to the console
// session, so both of these cross the user-to-root boundary:
//
// - Enabling the VNC server publishes the console desktop, keyboard and
// mouse to whoever the account authorizes. On a multi-user host that
// desktop belongs to another user, and the profile the caller owns is not a
// privilege they hold over it.
// - Disabling the approval prompt removes the console user's per-connection
// consent, turning an authorized VNC session into a silent one.
func requirePrivilegeForVNCChange(ctx context.Context, stored *profilemanager.Config, change privilegedConfigChange) error {
if enables(storedFlag(stored, func(c *profilemanager.Config) *bool { return c.DisableVNCApproval }), change.disableVNCApproval) {
return denyPrivileged(ctx, "disabling VNC connection approval", ipcauth.UpCommand("--disable-vnc-approval"))
}
if enables(storedFlag(stored, func(c *profilemanager.Config) *bool { return c.ServerVNCAllowed }), change.serverVNCAllowed) {
return denyPrivileged(ctx, "enabling the NetBird VNC server", ipcauth.UpCommand("--allow-server-vnc"))
}
return nil
}
// vncServerEnabled reports whether the profile currently runs the VNC server.
//
// Unlike the SSH server, VNC defaults to off: the flag was introduced with the
// server itself, so a nil value is a config written before VNC existed and
// means the server is not running.
func vncServerEnabled(cfg *profilemanager.Config) bool {
if cfg == nil || cfg.ServerVNCAllowed == nil {
return false
}
return *cfg.ServerVNCAllowed
}
// enabledRemoteAccessServer names a remote-access server the profile currently
// runs, and whether it runs any. It decides whether the management-binding and
// deregistration guards apply, since either server hands authorization
// decisions to the management identity the profile points at. SSH is reported
// first when both are on: it is the more privileged of the two.
func enabledRemoteAccessServer(cfg *profilemanager.Config) (string, bool) {
switch {
case sshServerEnabled(cfg):
return "SSH", true
case vncServerEnabled(cfg):
return "VNC", true
default:
return "", false
}
}

View File

@@ -0,0 +1,178 @@
package server
import (
"testing"
"github.com/netbirdio/netbird/client/internal/profilemanager"
)
func TestRequirePrivilegeForConfigChange_VNCFlags(t *testing.T) {
tests := []struct {
name string
stored *profilemanager.Config
change privilegedConfigChange
privileged bool
wantDeny bool
}{
{
name: "enabling the vnc server unprivileged is refused",
stored: &profilemanager.Config{ServerVNCAllowed: boolPtr(false)},
change: privilegedConfigChange{serverVNCAllowed: boolPtr(true)},
wantDeny: true,
},
{
name: "enabling the vnc server as root is allowed",
stored: &profilemanager.Config{ServerVNCAllowed: boolPtr(false)},
change: privilegedConfigChange{serverVNCAllowed: boolPtr(true)},
privileged: true,
},
{
name: "restating an already enabled vnc server is not a change",
stored: &profilemanager.Config{ServerVNCAllowed: boolPtr(true)},
change: privilegedConfigChange{serverVNCAllowed: boolPtr(true)},
},
{
name: "turning the vnc server off is not guarded",
stored: &profilemanager.Config{ServerVNCAllowed: boolPtr(true)},
change: privilegedConfigChange{serverVNCAllowed: boolPtr(false)},
},
{
// Unlike SSH, a nil flag means off: the flag shipped with the server.
name: "a config written before vnc existed counts as off, so enabling is refused",
stored: &profilemanager.Config{},
change: privilegedConfigChange{serverVNCAllowed: boolPtr(true)},
wantDeny: true,
},
{
name: "a profile with no config yet counts as off, so enabling is refused",
stored: nil,
change: privilegedConfigChange{serverVNCAllowed: boolPtr(true)},
wantDeny: true,
},
{
name: "disabling the vnc approval prompt unprivileged is refused",
stored: &profilemanager.Config{DisableVNCApproval: boolPtr(false)},
change: privilegedConfigChange{disableVNCApproval: boolPtr(true)},
wantDeny: true,
},
{
name: "disabling the vnc approval prompt as root is allowed",
stored: &profilemanager.Config{DisableVNCApproval: boolPtr(false)},
change: privilegedConfigChange{disableVNCApproval: boolPtr(true)},
privileged: true,
},
{
name: "re-enabling the vnc approval prompt is not guarded",
stored: &profilemanager.Config{DisableVNCApproval: boolPtr(true)},
change: privilegedConfigChange{disableVNCApproval: boolPtr(false)},
},
{
name: "restating a disabled approval prompt is not a change",
stored: &profilemanager.Config{DisableVNCApproval: boolPtr(true)},
change: privilegedConfigChange{disableVNCApproval: boolPtr(true)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := userCtx()
if tt.privileged {
ctx = rootCtx()
}
err := requirePrivilegeForConfigChange(ctx, tt.stored, tt.change)
if tt.wantDeny {
assertDenied(t, err)
return
}
assertAllowed(t, err)
})
}
}
// The management binding and deregistration guards protect either remote-access
// server, so the VNC server alone must arm them even with SSH off.
func TestRequirePrivilegeForConfigChange_ManagementURLWithVNCOnly(t *testing.T) {
vncOnly := &profilemanager.Config{
ServerSSHAllowed: boolPtr(false),
ServerVNCAllowed: boolPtr(true),
ManagementURL: mustURL(t, "https://api.netbird.io:443"),
DisableVNCApproval: boolPtr(false),
}
err := requirePrivilegeForConfigChange(userCtx(), vncOnly,
privilegedConfigChange{managementURL: "https://attacker.example.com:443"})
assertDenied(t, err)
// The same move is the administrator's to make.
assertAllowed(t, requirePrivilegeForConfigChange(rootCtx(), vncOnly,
privilegedConfigChange{managementURL: "https://selfhosted.example.com:443"}))
// Restating the stored binding is not a change, so it is never refused.
assertAllowed(t, requirePrivilegeForConfigChange(userCtx(), vncOnly,
privilegedConfigChange{managementURL: "https://api.netbird.io"}))
}
func TestRequirePrivilegeForDeregistration_VNCOnly(t *testing.T) {
vncOnly := &profilemanager.Config{ServerSSHAllowed: boolPtr(false), ServerVNCAllowed: boolPtr(true)}
assertDenied(t, requirePrivilegeForDeregistration(userCtx(), vncOnly))
assertAllowed(t, requirePrivilegeForDeregistration(rootCtx(), vncOnly))
bothOff := &profilemanager.Config{ServerSSHAllowed: boolPtr(false), ServerVNCAllowed: boolPtr(false)}
assertAllowed(t, requirePrivilegeForDeregistration(userCtx(), bothOff))
}
// enabledRemoteAccessServer names the server in the refusal, so the user is told
// which one is holding the binding down. SSH wins when both are on: it is the
// more privileged of the two.
func TestEnabledRemoteAccessServer(t *testing.T) {
tests := []struct {
name string
cfg *profilemanager.Config
wantServer string
wantOn bool
}{
{
name: "ssh only",
cfg: &profilemanager.Config{ServerSSHAllowed: boolPtr(true), ServerVNCAllowed: boolPtr(false)},
wantServer: "SSH",
wantOn: true,
},
{
name: "vnc only",
cfg: &profilemanager.Config{ServerSSHAllowed: boolPtr(false), ServerVNCAllowed: boolPtr(true)},
wantServer: "VNC",
wantOn: true,
},
{
name: "both on reports ssh",
cfg: &profilemanager.Config{ServerSSHAllowed: boolPtr(true), ServerVNCAllowed: boolPtr(true)},
wantServer: "SSH",
wantOn: true,
},
{
name: "both off",
cfg: &profilemanager.Config{ServerSSHAllowed: boolPtr(false), ServerVNCAllowed: boolPtr(false)},
},
{
// A nil SSH flag means on (legacy configs), so it still arms the guard.
name: "legacy config with no flags at all reports ssh",
cfg: &profilemanager.Config{},
wantServer: "SSH",
wantOn: true,
},
{
name: "no config",
cfg: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server, on := enabledRemoteAccessServer(tt.cfg)
if on != tt.wantOn || server != tt.wantServer {
t.Fatalf("enabledRemoteAccessServer() = (%q, %v), want (%q, %v)", server, on, tt.wantServer, tt.wantOn)
}
})
}
}

View File

@@ -0,0 +1,86 @@
import { useTranslation } from "react-i18next";
import { CopyToClipboard } from "@/components/CopyToClipboard";
import { usePrivilege } from "@/hooks/usePrivilege.ts";
import { Privilege } from "@bindings/services/models.js";
import { type ReactNode } from "react";
export type GuardedControl = {
disabled: boolean;
hint: ReactNode;
};
// useGuardedControl returns a guard for the settings controls the daemon
// restricts to root/administrator: enabling a remote-access server, or removing
// one of its safeguards.
//
// The daemon restricts only the direction that hands out access from a process
// running as root. So for an unprivileged user a guarded control is either
// unavailable (it is off and only they could turn it on) or a one-way switch (it
// is on, they may turn it off, but not back on) — say which, either way.
//
// A null privilege means we could not determine it: leave the control alone
// rather than greying it out with nothing to explain why. The daemon enforces
// this regardless, and a rejected save reports its own guidance.
export const useGuardedControl = () => {
const privilege = usePrivilege();
return (
guardedDirectionActive: boolean,
command: (p: Privilege) => string,
// inverted marks a control whose guarded direction is switching it off,
// so the one-way warning has to read the other way round.
inverted = false,
): GuardedControl => {
if (!privilege || privilege.privileged) {
return { disabled: false, hint: undefined };
}
const hint = (
<PrivilegeHint
actor={privilege.actor}
command={command(privilege)}
oneWay={guardedDirectionActive}
inverted={inverted}
/>
);
return { disabled: !guardedDirectionActive, hint };
};
};
// PrivilegeHint explains what an unprivileged user can and cannot do with a
// guarded control, and offers the command that does it with the privileges the
// daemon requires. oneWay covers the control being in the guarded state already:
// switching it back is the part that needs privileges.
export function PrivilegeHint({
actor,
command,
oneWay,
inverted,
}: {
actor: string;
command: string;
oneWay: boolean;
inverted: boolean;
}): ReactNode {
const { t } = useTranslation();
if (!command) return null;
return (
<div
className={
"-mt-2 flex flex-col gap-1 rounded-md bg-nb-gray-930 px-3 py-2 text-xs text-nb-gray-300"
}
>
<span>
{!oneWay
? t("settings.privilege.hint", { actor })
: inverted
? t("settings.privilege.oneWayInverted", { actor })
: t("settings.privilege.oneWay", { actor })}
</span>
<CopyToClipboard message={command} alwaysShowIcon wrap variant={"bright"}>
<code className={"select-text break-all font-mono text-xs text-nb-gray-200"}>
{command}
</code>
</CopyToClipboard>
</div>
);
}

View File

@@ -1,51 +1,19 @@
import { useTranslation } from "react-i18next";
import { CopyToClipboard } from "@/components/CopyToClipboard";
import FancyToggleSwitch from "@/components/switches/FancyToggleSwitch";
import { HelpText } from "@/components/typography/HelpText";
import { Input } from "@/components/inputs/Input";
import { Label } from "@/components/typography/Label";
import { cn } from "@/lib/cn";
import { SectionGroup } from "@/modules/settings/SettingsSection.tsx";
import { useGuardedControl } from "@/modules/settings/PrivilegeGuard.tsx";
import { useSettings } from "@/contexts/SettingsContext.tsx";
import { usePrivilege } from "@/hooks/usePrivilege.ts";
import { Privilege } from "@bindings/services/models.js";
import { type ChangeEvent, type ReactNode, useEffect, useId, useState } from "react";
import { type ChangeEvent, useEffect, useId, useState } from "react";
export function SettingsSSH() {
const { t } = useTranslation();
const { config, setField } = useSettings();
const privilege = usePrivilege();
const guarded = useGuardedControl();
const isSSHServerEnabled = config.serverSshAllowed;
// The daemon restricts only the direction that hands out shells from a process
// running as root. So for an unprivileged user a guarded control is either
// unavailable (it is off and only they could turn it on) or a one-way switch
// (it is on, they may turn it off, but not back on) — say which, either way.
//
// A null privilege means we could not determine it: leave the control alone
// rather than greying it out with nothing to explain why. The daemon enforces
// this regardless, and a rejected save reports its own guidance.
const guarded = (
guardedDirectionActive: boolean,
command: (p: Privilege) => string,
// inverted marks a control whose guarded direction is switching it off, so
// the one-way warning has to read the other way round.
inverted = false,
) => {
if (!privilege || privilege.privileged) {
return { disabled: false, hint: undefined };
}
const hint = (
<PrivilegeHint
actor={privilege.actor}
command={command(privilege)}
oneWay={guardedDirectionActive}
inverted={inverted}
/>
);
return { disabled: !guardedDirectionActive, hint };
};
const sshServer = guarded(config.serverSshAllowed, (p) => p.allowSshServer);
const sshRoot = guarded(config.enableSshRoot, (p) => p.enableSshRoot);
// Inverted control: the guarded direction is switching authentication off, so
@@ -162,42 +130,3 @@ export function SettingsSSH() {
</>
);
}
// PrivilegeHint explains what an unprivileged user can and cannot do with a
// guarded control, and offers the command that does it with the privileges the
// daemon requires. oneWay covers the control being in the guarded state already:
// switching it back is the part that needs privileges.
function PrivilegeHint({
actor,
command,
oneWay,
inverted,
}: {
actor: string;
command: string;
oneWay: boolean;
inverted: boolean;
}): ReactNode {
const { t } = useTranslation();
if (!command) return null;
return (
<div
className={
"-mt-2 flex flex-col gap-1 rounded-md bg-nb-gray-930 px-3 py-2 text-xs text-nb-gray-300"
}
>
<span>
{!oneWay
? t("settings.ssh.privilege.hint", { actor })
: inverted
? t("settings.ssh.privilege.oneWayInverted", { actor })
: t("settings.ssh.privilege.oneWay", { actor })}
</span>
<CopyToClipboard message={command} alwaysShowIcon wrap variant={"bright"}>
<code className={"select-text break-all font-mono text-xs text-nb-gray-200"}>
{command}
</code>
</CopyToClipboard>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next";
import FancyToggleSwitch from "@/components/switches/FancyToggleSwitch";
import { SectionGroup } from "@/modules/settings/SettingsSection.tsx";
import { useGuardedControl } from "@/modules/settings/PrivilegeGuard.tsx";
import { useSettings } from "@/contexts/SettingsContext.tsx";
import { useRestrictions } from "@/contexts/RestrictionsContext.tsx";
@@ -8,9 +9,15 @@ export function SettingsVNC() {
const { t } = useTranslation();
const { config, setField } = useSettings();
const { mdm } = useRestrictions();
const guarded = useGuardedControl();
const isVNCServerEnabled = config.serverVncAllowed;
const vncServerManaged = mdm.allowServerVNC != null;
const vncServer = guarded(config.serverVncAllowed, (p) => p.allowVncServer);
// Inverted control: the guarded direction is switching the approval prompt
// off, so the already-disabled state is the one-way one.
const vncApproval = guarded(config.disableVncApproval, (p) => p.disableVncApproval, true);
return (
<>
<SectionGroup title={t("settings.vnc.section.server")}>
@@ -19,8 +26,9 @@ export function SettingsVNC() {
onChange={(v) => setField("serverVncAllowed", v)}
label={t("settings.vnc.server.label")}
helpText={t("settings.vnc.server.help")}
disabled={vncServerManaged}
disabled={vncServerManaged || vncServer.disabled}
/>
{!vncServerManaged && vncServer.hint}
</SectionGroup>
{!mdm.disableVNCApproval && (
@@ -33,7 +41,9 @@ export function SettingsVNC() {
onChange={(v) => setField("disableVncApproval", !v)}
label={t("settings.vnc.approval.label")}
helpText={t("settings.vnc.approval.help")}
disabled={vncApproval.disabled}
/>
{vncApproval.hint}
</SectionGroup>
)}
</>

View File

@@ -1855,16 +1855,16 @@
"message": "Operation failed.",
"description": "Generic fallback error message used when no specific error applies."
},
"settings.ssh.privilege.hint": {
"settings.privilege.hint": {
"message": "Requires {actor}. Run this instead:",
"description": "Help text under an SSH setting the user cannot change: it needs elevated privileges. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
"description": "Help text under a remote-access setting the user cannot change: it needs elevated privileges. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
},
"settings.ssh.privilege.oneWay": {
"settings.privilege.oneWay": {
"message": "You can switch this off, but switching it back on needs {actor}:",
"description": "Warning under an SSH setting an unprivileged user may disable but not re-enable. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
"description": "Warning under a remote-access setting an unprivileged user may disable but not re-enable. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
},
"settings.ssh.privilege.oneWayInverted": {
"settings.privilege.oneWayInverted": {
"message": "You can switch this on, but switching it back off needs {actor}:",
"description": "Warning under the SSH authentication setting, which an unprivileged user may re-enable but not disable again. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
"description": "Warning under a safeguard setting (SSH authentication, VNC approval) which an unprivileged user may re-enable but not disable again. {actor} is 'root' on Linux/macOS or 'administrator privileges' on Windows. Followed by a copyable command."
}
}

View File

@@ -53,9 +53,11 @@ type Privilege struct {
// Actor names what the operation requires ("root", "administrator privileges").
Actor string `json:"actor"`
// Commands equivalent to the settings the daemon guards, ready to copy.
AllowSSHServer string `json:"allowSshServer"`
EnableSSHRoot string `json:"enableSshRoot"`
DisableSSHAuth string `json:"disableSshAuth"`
AllowSSHServer string `json:"allowSshServer"`
EnableSSHRoot string `json:"enableSshRoot"`
DisableSSHAuth string `json:"disableSshAuth"`
AllowVNCServer string `json:"allowVncServer"`
DisableVNCApproval string `json:"disableVncApproval"`
}
type ConfigParams struct {
@@ -233,8 +235,8 @@ func (s *Settings) SetConfig(ctx context.Context, p SetConfigParams) error {
}
// Privilege reports whether this UI process could carry out the changes the
// daemon restricts to root/administrator, and the command that performs the one
// users hit in the SSH settings. It applies the daemon's own rule to what it can
// daemon restricts to root/administrator, and the command that performs each of
// the ones users hit in the SSH and VNC settings. It applies the daemon's own rule to what it can
// see locally, so the frontend can present those controls as unavailable up front
// instead of letting a save fail. No daemon round-trip, so it also works while the
// daemon is down.
@@ -259,11 +261,13 @@ func (s *Settings) Privilege() Privilege {
func newPrivilege(privileged bool) Privilege {
return Privilege{
Privileged: privileged,
Actor: ipcauth.PrivilegedActor(),
AllowSSHServer: ipcauth.UpCommand("--allow-server-ssh"),
EnableSSHRoot: ipcauth.UpCommand("--enable-ssh-root"),
DisableSSHAuth: ipcauth.UpCommand("--disable-ssh-auth"),
Privileged: privileged,
Actor: ipcauth.PrivilegedActor(),
AllowSSHServer: ipcauth.UpCommand("--allow-server-ssh"),
EnableSSHRoot: ipcauth.UpCommand("--enable-ssh-root"),
DisableSSHAuth: ipcauth.UpCommand("--disable-ssh-auth"),
AllowVNCServer: ipcauth.UpCommand("--allow-server-vnc"),
DisableVNCApproval: ipcauth.UpCommand("--disable-vnc-approval"),
}
}

Binary file not shown.