mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-13 08:57:28 +02:00
[client] Feat: Support Multiple Profiles (#3980)
[client] Feat: Support Multiple Profiles (#3980)
This commit is contained in:
@ -22,6 +22,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/auth"
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/management/domain"
|
||||
|
||||
@ -50,14 +51,12 @@ type Server struct {
|
||||
rootCtx context.Context
|
||||
actCancel context.CancelFunc
|
||||
|
||||
latestConfigInput internal.ConfigInput
|
||||
|
||||
logFile string
|
||||
|
||||
oauthAuthFlow oauthAuthFlow
|
||||
|
||||
mutex sync.Mutex
|
||||
config *internal.Config
|
||||
config *profilemanager.Config
|
||||
proto.UnimplementedDaemonServiceServer
|
||||
|
||||
connectClient *internal.ConnectClient
|
||||
@ -68,6 +67,8 @@ type Server struct {
|
||||
lastProbe time.Time
|
||||
persistNetworkMap bool
|
||||
isSessionActive atomic.Bool
|
||||
|
||||
profileManager profilemanager.ServiceManager
|
||||
}
|
||||
|
||||
type oauthAuthFlow struct {
|
||||
@ -78,15 +79,13 @@ type oauthAuthFlow struct {
|
||||
}
|
||||
|
||||
// New server instance constructor.
|
||||
func New(ctx context.Context, configPath, logFile string) *Server {
|
||||
func New(ctx context.Context, logFile string) *Server {
|
||||
return &Server{
|
||||
rootCtx: ctx,
|
||||
latestConfigInput: internal.ConfigInput{
|
||||
ConfigPath: configPath,
|
||||
},
|
||||
rootCtx: ctx,
|
||||
logFile: logFile,
|
||||
persistNetworkMap: true,
|
||||
statusRecorder: peer.NewRecorder(""),
|
||||
profileManager: profilemanager.ServiceManager{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +98,7 @@ func (s *Server) Start() error {
|
||||
log.Warnf("failed to redirect stderr: %v", err)
|
||||
}
|
||||
|
||||
if err := restoreResidualState(s.rootCtx); err != nil {
|
||||
if err := restoreResidualState(s.rootCtx, s.profileManager.GetStatePath()); err != nil {
|
||||
log.Warnf(errRestoreResidualState, err)
|
||||
}
|
||||
|
||||
@ -118,25 +117,41 @@ func (s *Server) Start() error {
|
||||
ctx, cancel := context.WithCancel(s.rootCtx)
|
||||
s.actCancel = cancel
|
||||
|
||||
// if configuration exists, we just start connections. if is new config we skip and set status NeedsLogin
|
||||
// on failure we return error to retry
|
||||
config, err := internal.UpdateConfig(s.latestConfigInput)
|
||||
if errorStatus, ok := gstatus.FromError(err); ok && errorStatus.Code() == codes.NotFound {
|
||||
s.config, err = internal.UpdateOrCreateConfig(s.latestConfigInput)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create configuration file: %v", err)
|
||||
return err
|
||||
}
|
||||
state.Set(internal.StatusNeedsLogin)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
log.Warnf("unable to create configuration file: %v", err)
|
||||
return err
|
||||
// set the default config if not exists
|
||||
if err := s.setDefaultConfigIfNotExists(ctx); err != nil {
|
||||
log.Errorf("failed to set default config: %v", err)
|
||||
return fmt.Errorf("failed to set default config: %w", err)
|
||||
}
|
||||
|
||||
// if configuration exists, we just start connections.
|
||||
config, _ = internal.UpdateOldManagementURL(ctx, config, s.latestConfigInput.ConfigPath)
|
||||
activeProf, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
cfgPath, err := activeProf.FilePath()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
config, err := profilemanager.GetConfig(cfgPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile config: %v", err)
|
||||
|
||||
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||
Name: "default",
|
||||
Username: "",
|
||||
}); err != nil {
|
||||
log.Errorf("failed to set active profile state: %v", err)
|
||||
return fmt.Errorf("failed to set active profile state: %w", err)
|
||||
}
|
||||
|
||||
config, err = profilemanager.GetConfig(s.profileManager.DefaultProfilePath())
|
||||
if err != nil {
|
||||
log.Errorf("failed to get default profile config: %v", err)
|
||||
return fmt.Errorf("failed to get default profile config: %w", err)
|
||||
}
|
||||
}
|
||||
s.config = config
|
||||
|
||||
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
||||
@ -157,10 +172,34 @@ func (s *Server) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) setDefaultConfigIfNotExists(ctx context.Context) error {
|
||||
ok, err := s.profileManager.CopyDefaultProfileIfNotExists()
|
||||
if err != nil {
|
||||
if err := s.profileManager.CreateDefaultProfile(); err != nil {
|
||||
log.Errorf("failed to create default profile: %v", err)
|
||||
return fmt.Errorf("failed to create default profile: %w", err)
|
||||
}
|
||||
|
||||
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||
Name: "default",
|
||||
Username: "",
|
||||
}); err != nil {
|
||||
log.Errorf("failed to set active profile state: %v", err)
|
||||
return fmt.Errorf("failed to set active profile state: %w", err)
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
state := internal.CtxGetState(ctx)
|
||||
state.Set(internal.StatusNeedsLogin)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectWithRetryRuns runs the client connection with a backoff strategy where we retry the operation as additional
|
||||
// mechanism to keep the client connected even when the connection is lost.
|
||||
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
|
||||
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
|
||||
func (s *Server) connectWithRetryRuns(ctx context.Context, config *profilemanager.Config, statusRecorder *peer.Status,
|
||||
runningChan chan struct{},
|
||||
) {
|
||||
backOff := getConnectWithBackoff(ctx)
|
||||
@ -276,6 +315,90 @@ func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (i
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigRequest) (*proto.SetConfigResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
profState := profilemanager.ActiveProfileState{
|
||||
Name: msg.ProfileName,
|
||||
Username: msg.Username,
|
||||
}
|
||||
|
||||
profPath, err := profState.FilePath()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
var config profilemanager.ConfigInput
|
||||
|
||||
config.ConfigPath = profPath
|
||||
|
||||
if msg.ManagementUrl != "" {
|
||||
config.ManagementURL = msg.ManagementUrl
|
||||
}
|
||||
|
||||
if msg.AdminURL != "" {
|
||||
config.AdminURL = msg.AdminURL
|
||||
}
|
||||
|
||||
if msg.InterfaceName != nil {
|
||||
config.InterfaceName = msg.InterfaceName
|
||||
}
|
||||
|
||||
if msg.WireguardPort != nil {
|
||||
wgPort := int(*msg.WireguardPort)
|
||||
config.WireguardPort = &wgPort
|
||||
}
|
||||
|
||||
if msg.OptionalPreSharedKey != nil {
|
||||
if *msg.OptionalPreSharedKey != "" {
|
||||
config.PreSharedKey = msg.OptionalPreSharedKey
|
||||
}
|
||||
}
|
||||
|
||||
if msg.CleanDNSLabels {
|
||||
config.DNSLabels = domain.List{}
|
||||
|
||||
} else if msg.DnsLabels != nil {
|
||||
dnsLabels := domain.FromPunycodeList(msg.DnsLabels)
|
||||
config.DNSLabels = dnsLabels
|
||||
}
|
||||
|
||||
if msg.CleanNATExternalIPs {
|
||||
config.NATExternalIPs = make([]string, 0)
|
||||
} else if msg.NatExternalIPs != nil {
|
||||
config.NATExternalIPs = msg.NatExternalIPs
|
||||
}
|
||||
|
||||
config.CustomDNSAddress = msg.CustomDNSAddress
|
||||
if string(msg.CustomDNSAddress) == "empty" {
|
||||
config.CustomDNSAddress = []byte{}
|
||||
}
|
||||
|
||||
config.RosenpassEnabled = msg.RosenpassEnabled
|
||||
config.RosenpassPermissive = msg.RosenpassPermissive
|
||||
config.DisableAutoConnect = msg.DisableAutoConnect
|
||||
config.ServerSSHAllowed = msg.ServerSSHAllowed
|
||||
config.NetworkMonitor = msg.NetworkMonitor
|
||||
config.DisableClientRoutes = msg.DisableClientRoutes
|
||||
config.DisableServerRoutes = msg.DisableServerRoutes
|
||||
config.DisableDNS = msg.DisableDns
|
||||
config.DisableFirewall = msg.DisableFirewall
|
||||
config.BlockLANAccess = msg.BlockLanAccess
|
||||
config.DisableNotifications = msg.DisableNotifications
|
||||
config.LazyConnectionEnabled = msg.LazyConnectionEnabled
|
||||
config.BlockInbound = msg.BlockInbound
|
||||
|
||||
if _, err := profilemanager.UpdateConfig(config); err != nil {
|
||||
log.Errorf("failed to update profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to update profile config: %w", err)
|
||||
}
|
||||
|
||||
return &proto.SetConfigResponse{}, nil
|
||||
}
|
||||
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||
s.mutex.Lock()
|
||||
@ -292,7 +415,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
||||
s.actCancel = cancel
|
||||
s.mutex.Unlock()
|
||||
|
||||
if err := restoreResidualState(ctx); err != nil {
|
||||
if err := restoreResidualState(ctx, s.profileManager.GetStatePath()); err != nil {
|
||||
log.Warnf(errRestoreResidualState, err)
|
||||
}
|
||||
|
||||
@ -304,147 +427,62 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
||||
}
|
||||
}()
|
||||
|
||||
activeProf, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
if msg.ProfileName != nil {
|
||||
if *msg.ProfileName != "default" && (msg.Username == nil || *msg.Username == "") {
|
||||
log.Errorf("profile name is set to %s, but username is not provided", *msg.ProfileName)
|
||||
return nil, fmt.Errorf("profile name is set to %s, but username is not provided", *msg.ProfileName)
|
||||
}
|
||||
|
||||
var username string
|
||||
if *msg.ProfileName != "default" {
|
||||
username = *msg.Username
|
||||
}
|
||||
|
||||
if *msg.ProfileName != activeProf.Name && username != activeProf.Username {
|
||||
log.Infof("switching to profile %s for user '%s'", *msg.ProfileName, username)
|
||||
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||
Name: *msg.ProfileName,
|
||||
Username: username,
|
||||
}); err != nil {
|
||||
log.Errorf("failed to set active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to set active profile state: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeProf, err = s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("active profile: %s for %s", activeProf.Name, activeProf.Username)
|
||||
|
||||
s.mutex.Lock()
|
||||
inputConfig := s.latestConfigInput
|
||||
|
||||
if msg.ManagementUrl != "" {
|
||||
inputConfig.ManagementURL = msg.ManagementUrl
|
||||
s.latestConfigInput.ManagementURL = msg.ManagementUrl
|
||||
}
|
||||
|
||||
if msg.AdminURL != "" {
|
||||
inputConfig.AdminURL = msg.AdminURL
|
||||
s.latestConfigInput.AdminURL = msg.AdminURL
|
||||
}
|
||||
|
||||
if msg.CleanNATExternalIPs {
|
||||
inputConfig.NATExternalIPs = make([]string, 0)
|
||||
s.latestConfigInput.NATExternalIPs = nil
|
||||
} else if msg.NatExternalIPs != nil {
|
||||
inputConfig.NATExternalIPs = msg.NatExternalIPs
|
||||
s.latestConfigInput.NATExternalIPs = msg.NatExternalIPs
|
||||
}
|
||||
|
||||
inputConfig.CustomDNSAddress = msg.CustomDNSAddress
|
||||
s.latestConfigInput.CustomDNSAddress = msg.CustomDNSAddress
|
||||
if string(msg.CustomDNSAddress) == "empty" {
|
||||
inputConfig.CustomDNSAddress = []byte{}
|
||||
s.latestConfigInput.CustomDNSAddress = []byte{}
|
||||
}
|
||||
|
||||
if msg.Hostname != "" {
|
||||
// nolint
|
||||
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, msg.Hostname)
|
||||
}
|
||||
|
||||
if msg.RosenpassEnabled != nil {
|
||||
inputConfig.RosenpassEnabled = msg.RosenpassEnabled
|
||||
s.latestConfigInput.RosenpassEnabled = msg.RosenpassEnabled
|
||||
}
|
||||
|
||||
if msg.RosenpassPermissive != nil {
|
||||
inputConfig.RosenpassPermissive = msg.RosenpassPermissive
|
||||
s.latestConfigInput.RosenpassPermissive = msg.RosenpassPermissive
|
||||
}
|
||||
|
||||
if msg.ServerSSHAllowed != nil {
|
||||
inputConfig.ServerSSHAllowed = msg.ServerSSHAllowed
|
||||
s.latestConfigInput.ServerSSHAllowed = msg.ServerSSHAllowed
|
||||
}
|
||||
|
||||
if msg.DisableAutoConnect != nil {
|
||||
inputConfig.DisableAutoConnect = msg.DisableAutoConnect
|
||||
s.latestConfigInput.DisableAutoConnect = msg.DisableAutoConnect
|
||||
}
|
||||
|
||||
if msg.InterfaceName != nil {
|
||||
inputConfig.InterfaceName = msg.InterfaceName
|
||||
s.latestConfigInput.InterfaceName = msg.InterfaceName
|
||||
}
|
||||
|
||||
if msg.WireguardPort != nil {
|
||||
port := int(*msg.WireguardPort)
|
||||
inputConfig.WireguardPort = &port
|
||||
s.latestConfigInput.WireguardPort = &port
|
||||
}
|
||||
|
||||
if msg.NetworkMonitor != nil {
|
||||
inputConfig.NetworkMonitor = msg.NetworkMonitor
|
||||
s.latestConfigInput.NetworkMonitor = msg.NetworkMonitor
|
||||
}
|
||||
|
||||
if len(msg.ExtraIFaceBlacklist) > 0 {
|
||||
inputConfig.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||
s.latestConfigInput.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||
}
|
||||
|
||||
if msg.DnsRouteInterval != nil {
|
||||
duration := msg.DnsRouteInterval.AsDuration()
|
||||
inputConfig.DNSRouteInterval = &duration
|
||||
s.latestConfigInput.DNSRouteInterval = &duration
|
||||
}
|
||||
|
||||
if msg.DisableClientRoutes != nil {
|
||||
inputConfig.DisableClientRoutes = msg.DisableClientRoutes
|
||||
s.latestConfigInput.DisableClientRoutes = msg.DisableClientRoutes
|
||||
}
|
||||
if msg.DisableServerRoutes != nil {
|
||||
inputConfig.DisableServerRoutes = msg.DisableServerRoutes
|
||||
s.latestConfigInput.DisableServerRoutes = msg.DisableServerRoutes
|
||||
}
|
||||
if msg.DisableDns != nil {
|
||||
inputConfig.DisableDNS = msg.DisableDns
|
||||
s.latestConfigInput.DisableDNS = msg.DisableDns
|
||||
}
|
||||
if msg.DisableFirewall != nil {
|
||||
inputConfig.DisableFirewall = msg.DisableFirewall
|
||||
s.latestConfigInput.DisableFirewall = msg.DisableFirewall
|
||||
}
|
||||
if msg.BlockLanAccess != nil {
|
||||
inputConfig.BlockLANAccess = msg.BlockLanAccess
|
||||
s.latestConfigInput.BlockLANAccess = msg.BlockLanAccess
|
||||
}
|
||||
if msg.BlockInbound != nil {
|
||||
inputConfig.BlockInbound = msg.BlockInbound
|
||||
s.latestConfigInput.BlockInbound = msg.BlockInbound
|
||||
}
|
||||
|
||||
if msg.CleanDNSLabels {
|
||||
inputConfig.DNSLabels = domain.List{}
|
||||
s.latestConfigInput.DNSLabels = nil
|
||||
} else if msg.DnsLabels != nil {
|
||||
dnsLabels := domain.FromPunycodeList(msg.DnsLabels)
|
||||
inputConfig.DNSLabels = dnsLabels
|
||||
s.latestConfigInput.DNSLabels = dnsLabels
|
||||
}
|
||||
|
||||
if msg.DisableNotifications != nil {
|
||||
inputConfig.DisableNotifications = msg.DisableNotifications
|
||||
s.latestConfigInput.DisableNotifications = msg.DisableNotifications
|
||||
}
|
||||
|
||||
if msg.LazyConnectionEnabled != nil {
|
||||
inputConfig.LazyConnectionEnabled = msg.LazyConnectionEnabled
|
||||
s.latestConfigInput.LazyConnectionEnabled = msg.LazyConnectionEnabled
|
||||
}
|
||||
|
||||
s.mutex.Unlock()
|
||||
|
||||
if msg.OptionalPreSharedKey != nil {
|
||||
inputConfig.PreSharedKey = msg.OptionalPreSharedKey
|
||||
}
|
||||
|
||||
config, err := internal.UpdateOrCreateConfig(inputConfig)
|
||||
cfgPath, err := activeProf.FilePath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
if msg.ManagementUrl == "" {
|
||||
config, _ = internal.UpdateOldManagementURL(ctx, config, s.latestConfigInput.ConfigPath)
|
||||
s.config = config
|
||||
s.latestConfigInput.ManagementURL = config.ManagementURL.String()
|
||||
config, err := profilemanager.GetConfig(cfgPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile config: %w", err)
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.config = config
|
||||
s.mutex.Unlock()
|
||||
@ -586,15 +624,17 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.WaitSSOLoginResponse{}, nil
|
||||
return &proto.WaitSSOLoginResponse{
|
||||
Email: tokenInfo.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Up starts engine work in the daemon.
|
||||
func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpResponse, error) {
|
||||
func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if err := restoreResidualState(callerCtx); err != nil {
|
||||
if err := restoreResidualState(callerCtx, s.profileManager.GetStatePath()); err != nil {
|
||||
log.Warnf(errRestoreResidualState, err)
|
||||
}
|
||||
|
||||
@ -628,6 +668,40 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
|
||||
return nil, fmt.Errorf("config is not defined, please call login command first")
|
||||
}
|
||||
|
||||
activeProf, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
if msg != nil && msg.ProfileName != nil {
|
||||
if err := s.switchProfileIfNeeded(*msg.ProfileName, msg.Username, activeProf); err != nil {
|
||||
log.Errorf("failed to switch profile: %v", err)
|
||||
return nil, fmt.Errorf("failed to switch profile: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
activeProf, err = s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("active profile: %s for %s", activeProf.Name, activeProf.Username)
|
||||
|
||||
cfgPath, err := activeProf.FilePath()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
config, err := profilemanager.GetConfig(cfgPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile config: %w", err)
|
||||
}
|
||||
s.config = config
|
||||
|
||||
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
|
||||
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
||||
|
||||
@ -651,6 +725,70 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) switchProfileIfNeeded(profileName string, userName *string, activeProf *profilemanager.ActiveProfileState) error {
|
||||
if profileName != "default" && (userName == nil || *userName == "") {
|
||||
log.Errorf("profile name is set to %s, but username is not provided", profileName)
|
||||
return fmt.Errorf("profile name is set to %s, but username is not provided", profileName)
|
||||
}
|
||||
|
||||
var username string
|
||||
if profileName != "default" {
|
||||
username = *userName
|
||||
}
|
||||
|
||||
if profileName != activeProf.Name || username != activeProf.Username {
|
||||
log.Infof("switching to profile %s for user %s", profileName, username)
|
||||
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||
Name: profileName,
|
||||
Username: username,
|
||||
}); err != nil {
|
||||
log.Errorf("failed to set active profile state: %v", err)
|
||||
return fmt.Errorf("failed to set active profile state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SwitchProfile switches the active profile in the daemon.
|
||||
func (s *Server) SwitchProfile(callerCtx context.Context, msg *proto.SwitchProfileRequest) (*proto.SwitchProfileResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
activeProf, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
if msg != nil && msg.ProfileName != nil {
|
||||
if err := s.switchProfileIfNeeded(*msg.ProfileName, msg.Username, activeProf); err != nil {
|
||||
log.Errorf("failed to switch profile: %v", err)
|
||||
return nil, fmt.Errorf("failed to switch profile: %w", err)
|
||||
}
|
||||
}
|
||||
activeProf, err = s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
cfgPath, err := activeProf.FilePath()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
config, err := profilemanager.GetConfig(cfgPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get default profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to get default profile config: %w", err)
|
||||
}
|
||||
|
||||
s.config = config
|
||||
|
||||
return &proto.SwitchProfileResponse{}, nil
|
||||
}
|
||||
|
||||
// Down engine work in the daemon.
|
||||
func (s *Server) Down(ctx context.Context, _ *proto.DownRequest) (*proto.DownResponse, error) {
|
||||
s.mutex.Lock()
|
||||
@ -738,58 +876,65 @@ func (s *Server) runProbes() {
|
||||
}
|
||||
|
||||
// GetConfig of the daemon.
|
||||
func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto.GetConfigResponse, error) {
|
||||
func (s *Server) GetConfig(ctx context.Context, req *proto.GetConfigRequest) (*proto.GetConfigResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
managementURL := s.latestConfigInput.ManagementURL
|
||||
adminURL := s.latestConfigInput.AdminURL
|
||||
preSharedKey := ""
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
if s.config != nil {
|
||||
if managementURL == "" && s.config.ManagementURL != nil {
|
||||
managementURL = s.config.ManagementURL.String()
|
||||
}
|
||||
prof := profilemanager.ActiveProfileState{
|
||||
Name: req.ProfileName,
|
||||
Username: req.Username,
|
||||
}
|
||||
|
||||
if s.config.AdminURL != nil {
|
||||
adminURL = s.config.AdminURL.String()
|
||||
}
|
||||
cfgPath, err := prof.FilePath()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile file path: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile file path: %w", err)
|
||||
}
|
||||
|
||||
preSharedKey = s.config.PreSharedKey
|
||||
if preSharedKey != "" {
|
||||
preSharedKey = "**********"
|
||||
}
|
||||
cfg, err := profilemanager.GetConfig(cfgPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile config: %w", err)
|
||||
}
|
||||
managementURL := cfg.ManagementURL
|
||||
adminURL := cfg.AdminURL
|
||||
|
||||
var preSharedKey = cfg.PreSharedKey
|
||||
if preSharedKey != "" {
|
||||
preSharedKey = "**********"
|
||||
}
|
||||
|
||||
disableNotifications := true
|
||||
if s.config.DisableNotifications != nil {
|
||||
disableNotifications = *s.config.DisableNotifications
|
||||
if cfg.DisableNotifications != nil {
|
||||
disableNotifications = *cfg.DisableNotifications
|
||||
}
|
||||
|
||||
networkMonitor := false
|
||||
if s.config.NetworkMonitor != nil {
|
||||
networkMonitor = *s.config.NetworkMonitor
|
||||
if cfg.NetworkMonitor != nil {
|
||||
networkMonitor = *cfg.NetworkMonitor
|
||||
}
|
||||
|
||||
disableDNS := s.config.DisableDNS
|
||||
disableClientRoutes := s.config.DisableClientRoutes
|
||||
disableServerRoutes := s.config.DisableServerRoutes
|
||||
blockLANAccess := s.config.BlockLANAccess
|
||||
disableDNS := cfg.DisableDNS
|
||||
disableClientRoutes := cfg.DisableClientRoutes
|
||||
disableServerRoutes := cfg.DisableServerRoutes
|
||||
blockLANAccess := cfg.BlockLANAccess
|
||||
|
||||
return &proto.GetConfigResponse{
|
||||
ManagementUrl: managementURL,
|
||||
ConfigFile: s.latestConfigInput.ConfigPath,
|
||||
LogFile: s.logFile,
|
||||
ManagementUrl: managementURL.String(),
|
||||
PreSharedKey: preSharedKey,
|
||||
AdminURL: adminURL,
|
||||
InterfaceName: s.config.WgIface,
|
||||
WireguardPort: int64(s.config.WgPort),
|
||||
DisableAutoConnect: s.config.DisableAutoConnect,
|
||||
ServerSSHAllowed: *s.config.ServerSSHAllowed,
|
||||
RosenpassEnabled: s.config.RosenpassEnabled,
|
||||
RosenpassPermissive: s.config.RosenpassPermissive,
|
||||
LazyConnectionEnabled: s.config.LazyConnectionEnabled,
|
||||
BlockInbound: s.config.BlockInbound,
|
||||
AdminURL: adminURL.String(),
|
||||
InterfaceName: cfg.WgIface,
|
||||
WireguardPort: int64(cfg.WgPort),
|
||||
DisableAutoConnect: cfg.DisableAutoConnect,
|
||||
ServerSSHAllowed: *cfg.ServerSSHAllowed,
|
||||
RosenpassEnabled: cfg.RosenpassEnabled,
|
||||
RosenpassPermissive: cfg.RosenpassPermissive,
|
||||
LazyConnectionEnabled: cfg.LazyConnectionEnabled,
|
||||
BlockInbound: cfg.BlockInbound,
|
||||
DisableNotifications: disableNotifications,
|
||||
NetworkMonitor: networkMonitor,
|
||||
DisableDns: disableDNS,
|
||||
@ -918,3 +1063,82 @@ func sendTerminalNotification() error {
|
||||
|
||||
return wallCmd.Wait()
|
||||
}
|
||||
|
||||
// AddProfile adds a new profile to the daemon.
|
||||
func (s *Server) AddProfile(ctx context.Context, msg *proto.AddProfileRequest) (*proto.AddProfileResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if msg.ProfileName == "" || msg.Username == "" {
|
||||
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name and username must be provided")
|
||||
}
|
||||
|
||||
if err := s.profileManager.AddProfile(msg.ProfileName, msg.Username); err != nil {
|
||||
log.Errorf("failed to create profile: %v", err)
|
||||
return nil, fmt.Errorf("failed to create profile: %w", err)
|
||||
}
|
||||
|
||||
return &proto.AddProfileResponse{}, nil
|
||||
}
|
||||
|
||||
// RemoveProfile removes a profile from the daemon.
|
||||
func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequest) (*proto.RemoveProfileResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if msg.ProfileName == "" {
|
||||
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name must be provided")
|
||||
}
|
||||
|
||||
if err := s.profileManager.RemoveProfile(msg.ProfileName, msg.Username); err != nil {
|
||||
log.Errorf("failed to remove profile: %v", err)
|
||||
return nil, fmt.Errorf("failed to remove profile: %w", err)
|
||||
}
|
||||
|
||||
return &proto.RemoveProfileResponse{}, nil
|
||||
}
|
||||
|
||||
// ListProfiles lists all profiles in the daemon.
|
||||
func (s *Server) ListProfiles(ctx context.Context, msg *proto.ListProfilesRequest) (*proto.ListProfilesResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if msg.Username == "" {
|
||||
return nil, gstatus.Errorf(codes.InvalidArgument, "username must be provided")
|
||||
}
|
||||
|
||||
profiles, err := s.profileManager.ListProfiles(msg.Username)
|
||||
if err != nil {
|
||||
log.Errorf("failed to list profiles: %v", err)
|
||||
return nil, fmt.Errorf("failed to list profiles: %w", err)
|
||||
}
|
||||
|
||||
response := &proto.ListProfilesResponse{
|
||||
Profiles: make([]*proto.Profile, len(profiles)),
|
||||
}
|
||||
for i, profile := range profiles {
|
||||
response.Profiles[i] = &proto.Profile{
|
||||
Name: profile.Name,
|
||||
IsActive: profile.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetActiveProfile returns the active profile in the daemon.
|
||||
func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfileRequest) (*proto.GetActiveProfileResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
activeProfile, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
return &proto.GetActiveProfileResponse{
|
||||
ProfileName: activeProfile.Name,
|
||||
Username: activeProfile.Username,
|
||||
}, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user