mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-15 09:42:47 +02:00
[client] Add support for disabling profiles feature via command line flag (#4235)
* Add support for disabling profiles feature via command line flag * Add profiles disabling flag to service command * Refactor profile menu initialization and enhance error notifications in event handlers
This commit is contained in:
@ -72,6 +72,7 @@ var (
|
|||||||
anonymizeFlag bool
|
anonymizeFlag bool
|
||||||
dnsRouteInterval time.Duration
|
dnsRouteInterval time.Duration
|
||||||
lazyConnEnabled bool
|
lazyConnEnabled bool
|
||||||
|
profilesDisabled bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird",
|
Use: "netbird",
|
||||||
|
@ -42,6 +42,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd)
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd)
|
||||||
|
serviceCmd.PersistentFlags().BoolVar(&profilesDisabled, "disable-profiles", false, "Disables profiles feature. If enabled, the client will not be able to change or edit any profile.")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
||||||
serviceEnvDesc := `Sets extra environment variables for the service. ` +
|
serviceEnvDesc := `Sets extra environment variables for the service. ` +
|
||||||
|
@ -61,7 +61,7 @@ func (p *program) Start(svc service.Service) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles))
|
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), profilesDisabled)
|
||||||
if err := serverInstance.Start(); err != nil {
|
if err := serverInstance.Start(); err != nil {
|
||||||
log.Fatalf("failed to start daemon: %v", err)
|
log.Fatalf("failed to start daemon: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func startClientDaemon(
|
|||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
|
|
||||||
server := client.New(ctx,
|
server := client.New(ctx,
|
||||||
"")
|
"", false)
|
||||||
if err := server.Start(); err != nil {
|
if err := server.Start(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ const (
|
|||||||
defaultRetryMultiplier = 1.7
|
defaultRetryMultiplier = 1.7
|
||||||
|
|
||||||
errRestoreResidualState = "failed to restore residual state: %v"
|
errRestoreResidualState = "failed to restore residual state: %v"
|
||||||
|
errProfilesDisabled = "profiles are disabled, you cannot use this feature without profiles enabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server for service control.
|
// Server for service control.
|
||||||
@ -69,6 +70,7 @@ type Server struct {
|
|||||||
isSessionActive atomic.Bool
|
isSessionActive atomic.Bool
|
||||||
|
|
||||||
profileManager profilemanager.ServiceManager
|
profileManager profilemanager.ServiceManager
|
||||||
|
profilesDisabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type oauthAuthFlow struct {
|
type oauthAuthFlow struct {
|
||||||
@ -79,13 +81,14 @@ type oauthAuthFlow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New server instance constructor.
|
// New server instance constructor.
|
||||||
func New(ctx context.Context, logFile string) *Server {
|
func New(ctx context.Context, logFile string, profilesDisabled bool) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
rootCtx: ctx,
|
rootCtx: ctx,
|
||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
persistNetworkMap: true,
|
persistNetworkMap: true,
|
||||||
statusRecorder: peer.NewRecorder(""),
|
statusRecorder: peer.NewRecorder(""),
|
||||||
profileManager: profilemanager.ServiceManager{},
|
profileManager: profilemanager.ServiceManager{},
|
||||||
|
profilesDisabled: profilesDisabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,6 +323,10 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.checkProfilesDisabled() {
|
||||||
|
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
profState := profilemanager.ActiveProfileState{
|
profState := profilemanager.ActiveProfileState{
|
||||||
Name: msg.ProfileName,
|
Name: msg.ProfileName,
|
||||||
Username: msg.Username,
|
Username: msg.Username,
|
||||||
@ -737,6 +744,11 @@ func (s *Server) switchProfileIfNeeded(profileName string, userName *string, act
|
|||||||
}
|
}
|
||||||
|
|
||||||
if profileName != activeProf.Name || username != activeProf.Username {
|
if profileName != activeProf.Name || username != activeProf.Username {
|
||||||
|
if s.checkProfilesDisabled() {
|
||||||
|
log.Errorf("profiles are disabled, you cannot use this feature without profiles enabled")
|
||||||
|
return gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("switching to profile %s for user %s", profileName, username)
|
log.Infof("switching to profile %s for user %s", profileName, username)
|
||||||
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||||
Name: profileName,
|
Name: profileName,
|
||||||
@ -1069,6 +1081,10 @@ func (s *Server) AddProfile(ctx context.Context, msg *proto.AddProfileRequest) (
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.checkProfilesDisabled() {
|
||||||
|
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
if msg.ProfileName == "" || msg.Username == "" {
|
if msg.ProfileName == "" || msg.Username == "" {
|
||||||
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name and username must be provided")
|
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name and username must be provided")
|
||||||
}
|
}
|
||||||
@ -1086,6 +1102,10 @@ func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequ
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.checkProfilesDisabled() {
|
||||||
|
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
if msg.ProfileName == "" {
|
if msg.ProfileName == "" {
|
||||||
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name must be provided")
|
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name must be provided")
|
||||||
}
|
}
|
||||||
@ -1142,3 +1162,13 @@ func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfi
|
|||||||
Username: activeProfile.Username,
|
Username: activeProfile.Username,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) checkProfilesDisabled() bool {
|
||||||
|
// Check if the environment variable is set to disable profiles
|
||||||
|
if s.profilesDisabled {
|
||||||
|
log.Warn("Profiles are disabled via NB_DISABLE_PROFILES environment variable")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -94,7 +94,7 @@ func TestConnectWithRetryRuns(t *testing.T) {
|
|||||||
t.Fatalf("failed to set active profile state: %v", err)
|
t.Fatalf("failed to set active profile state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(ctx, "debug")
|
s := New(ctx, "debug", false)
|
||||||
|
|
||||||
s.config = config
|
s.config = config
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ func TestServer_Up(t *testing.T) {
|
|||||||
t.Fatalf("failed to set active profile state: %v", err)
|
t.Fatalf("failed to set active profile state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(ctx, "console")
|
s := New(ctx, "console", false)
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -227,7 +227,7 @@ func TestServer_SubcribeEvents(t *testing.T) {
|
|||||||
t.Fatalf("failed to set active profile state: %v", err)
|
t.Fatalf("failed to set active profile state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(ctx, "console")
|
s := New(ctx, "console", false)
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -802,7 +802,21 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
|
|
||||||
profileMenuItem := systray.AddMenuItem("", "")
|
profileMenuItem := systray.AddMenuItem("", "")
|
||||||
emailMenuItem := systray.AddMenuItem("", "")
|
emailMenuItem := systray.AddMenuItem("", "")
|
||||||
s.mProfile = newProfileMenu(s.ctx, s.profileManager, *s.eventHandler, profileMenuItem, emailMenuItem, s.menuDownClick, s.menuUpClick, s.getSrvClient, s.loadSettings)
|
|
||||||
|
newProfileMenuArgs := &newProfileMenuArgs{
|
||||||
|
ctx: s.ctx,
|
||||||
|
profileManager: s.profileManager,
|
||||||
|
eventHandler: s.eventHandler,
|
||||||
|
profileMenuItem: profileMenuItem,
|
||||||
|
emailMenuItem: emailMenuItem,
|
||||||
|
downClickCallback: s.menuDownClick,
|
||||||
|
upClickCallback: s.menuUpClick,
|
||||||
|
getSrvClientCallback: s.getSrvClient,
|
||||||
|
loadSettingsCallback: s.loadSettings,
|
||||||
|
app: s.app,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mProfile = newProfileMenu(*newProfileMenuArgs)
|
||||||
|
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
s.mUp = systray.AddMenuItem("Connect", "Connect")
|
s.mUp = systray.AddMenuItem("Connect", "Connect")
|
||||||
|
@ -86,35 +86,60 @@ func (h *eventHandler) handleDisconnectClick() {
|
|||||||
|
|
||||||
func (h *eventHandler) handleAllowSSHClick() {
|
func (h *eventHandler) handleAllowSSHClick() {
|
||||||
h.toggleCheckbox(h.client.mAllowSSH)
|
h.toggleCheckbox(h.client.mAllowSSH)
|
||||||
h.updateConfigWithErr()
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mAllowSSH) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update SSH settings"))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleAutoConnectClick() {
|
func (h *eventHandler) handleAutoConnectClick() {
|
||||||
h.toggleCheckbox(h.client.mAutoConnect)
|
h.toggleCheckbox(h.client.mAutoConnect)
|
||||||
h.updateConfigWithErr()
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mAutoConnect) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update auto-connect settings"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleRosenpassClick() {
|
func (h *eventHandler) handleRosenpassClick() {
|
||||||
h.toggleCheckbox(h.client.mEnableRosenpass)
|
h.toggleCheckbox(h.client.mEnableRosenpass)
|
||||||
h.updateConfigWithErr()
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mEnableRosenpass) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update Rosenpass settings"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleLazyConnectionClick() {
|
func (h *eventHandler) handleLazyConnectionClick() {
|
||||||
h.toggleCheckbox(h.client.mLazyConnEnabled)
|
h.toggleCheckbox(h.client.mLazyConnEnabled)
|
||||||
h.updateConfigWithErr()
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mLazyConnEnabled) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update lazy connection settings"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleBlockInboundClick() {
|
func (h *eventHandler) handleBlockInboundClick() {
|
||||||
h.toggleCheckbox(h.client.mBlockInbound)
|
h.toggleCheckbox(h.client.mBlockInbound)
|
||||||
h.updateConfigWithErr()
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mBlockInbound) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update block inbound settings"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleNotificationsClick() {
|
func (h *eventHandler) handleNotificationsClick() {
|
||||||
h.toggleCheckbox(h.client.mNotifications)
|
h.toggleCheckbox(h.client.mNotifications)
|
||||||
if h.client.eventManager != nil {
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mNotifications) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update notifications settings"))
|
||||||
|
} else if h.client.eventManager != nil {
|
||||||
h.client.eventManager.SetNotificationsEnabled(h.client.mNotifications.Checked())
|
h.client.eventManager.SetNotificationsEnabled(h.client.mNotifications.Checked())
|
||||||
}
|
}
|
||||||
h.updateConfigWithErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) handleAdvancedSettingsClick() {
|
func (h *eventHandler) handleAdvancedSettingsClick() {
|
||||||
@ -166,10 +191,12 @@ func (h *eventHandler) toggleCheckbox(item *systray.MenuItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) updateConfigWithErr() {
|
func (h *eventHandler) updateConfigWithErr() error {
|
||||||
if err := h.client.updateConfig(); err != nil {
|
if err := h.client.updateConfig(); err != nil {
|
||||||
log.Errorf("failed to update config: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) runSelfCommand(ctx context.Context, command, arg string) {
|
func (h *eventHandler) runSelfCommand(ctx context.Context, command, arg string) {
|
||||||
|
@ -334,7 +334,7 @@ type profileMenu struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
profileManager *profilemanager.ProfileManager
|
profileManager *profilemanager.ProfileManager
|
||||||
eventHandler eventHandler
|
eventHandler *eventHandler
|
||||||
profileMenuItem *systray.MenuItem
|
profileMenuItem *systray.MenuItem
|
||||||
emailMenuItem *systray.MenuItem
|
emailMenuItem *systray.MenuItem
|
||||||
profileSubItems []*subItem
|
profileSubItems []*subItem
|
||||||
@ -344,24 +344,34 @@ type profileMenu struct {
|
|||||||
upClickCallback func() error
|
upClickCallback func() error
|
||||||
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error)
|
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error)
|
||||||
loadSettingsCallback func()
|
loadSettingsCallback func()
|
||||||
|
app fyne.App
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProfileMenu(ctx context.Context, profileManager *profilemanager.ProfileManager,
|
type newProfileMenuArgs struct {
|
||||||
|
ctx context.Context
|
||||||
|
profileManager *profilemanager.ProfileManager
|
||||||
|
eventHandler *eventHandler
|
||||||
|
profileMenuItem *systray.MenuItem
|
||||||
|
emailMenuItem *systray.MenuItem
|
||||||
|
downClickCallback func() error
|
||||||
|
upClickCallback func() error
|
||||||
|
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error)
|
||||||
|
loadSettingsCallback func()
|
||||||
|
app fyne.App
|
||||||
|
}
|
||||||
|
|
||||||
eventHandler eventHandler, profileMenuItem, emailMenuItem *systray.MenuItem,
|
func newProfileMenu(args newProfileMenuArgs) *profileMenu {
|
||||||
downClickCallback, upClickCallback func() error,
|
|
||||||
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error),
|
|
||||||
loadSettingsCallback func()) *profileMenu {
|
|
||||||
p := profileMenu{
|
p := profileMenu{
|
||||||
ctx: ctx,
|
ctx: args.ctx,
|
||||||
profileManager: profileManager,
|
profileManager: args.profileManager,
|
||||||
eventHandler: eventHandler,
|
eventHandler: args.eventHandler,
|
||||||
profileMenuItem: profileMenuItem,
|
profileMenuItem: args.profileMenuItem,
|
||||||
emailMenuItem: emailMenuItem,
|
emailMenuItem: args.emailMenuItem,
|
||||||
downClickCallback: downClickCallback,
|
downClickCallback: args.downClickCallback,
|
||||||
upClickCallback: upClickCallback,
|
upClickCallback: args.upClickCallback,
|
||||||
getSrvClientCallback: getSrvClientCallback,
|
getSrvClientCallback: args.getSrvClientCallback,
|
||||||
loadSettingsCallback: loadSettingsCallback,
|
loadSettingsCallback: args.loadSettingsCallback,
|
||||||
|
app: args.app,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.emailMenuItem.Disable()
|
p.emailMenuItem.Disable()
|
||||||
@ -479,6 +489,8 @@ func (p *profileMenu) refresh() {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to switch profile: %v", err)
|
log.Errorf("failed to switch profile: %v", err)
|
||||||
|
// show notification dialog
|
||||||
|
p.app.SendNotification(fyne.NewNotification("Error", "Failed to switch profile"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user