diff --git a/management/internals/modules/services/domains/api.go b/management/internals/modules/reverseproxy/domains/api.go similarity index 99% rename from management/internals/modules/services/domains/api.go rename to management/internals/modules/reverseproxy/domains/api.go index b810f1df8..0824fa143 100644 --- a/management/internals/modules/services/domains/api.go +++ b/management/internals/modules/reverseproxy/domains/api.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/gorilla/mux" + nbcontext "github.com/netbirdio/netbird/management/server/context" "github.com/netbirdio/netbird/shared/management/http/api" "github.com/netbirdio/netbird/shared/management/http/util" diff --git a/management/internals/modules/services/domains/manager.go b/management/internals/modules/reverseproxy/domains/manager.go similarity index 100% rename from management/internals/modules/services/domains/manager.go rename to management/internals/modules/reverseproxy/domains/manager.go diff --git a/management/internals/modules/services/domains/validator.go b/management/internals/modules/reverseproxy/domains/validator.go similarity index 100% rename from management/internals/modules/services/domains/validator.go rename to management/internals/modules/reverseproxy/domains/validator.go diff --git a/management/internals/modules/services/domains/validator_test.go b/management/internals/modules/reverseproxy/domains/validator_test.go similarity index 93% rename from management/internals/modules/services/domains/validator_test.go rename to management/internals/modules/reverseproxy/domains/validator_test.go index 9a7dec79c..eaea4185d 100644 --- a/management/internals/modules/services/domains/validator_test.go +++ b/management/internals/modules/reverseproxy/domains/validator_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/netbirdio/netbird/management/internals/modules/services/domains" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domains" ) type resolver struct { diff --git a/management/internals/modules/reverseproxy/interface.go b/management/internals/modules/reverseproxy/interface.go new file mode 100644 index 000000000..11ee13feb --- /dev/null +++ b/management/internals/modules/reverseproxy/interface.go @@ -0,0 +1,13 @@ +package reverseproxy + +import ( + "context" +) + +type Manager interface { + GetAllReverseProxies(ctx context.Context, accountID, userID string) ([]*ReverseProxy, error) + GetReverseProxy(ctx context.Context, accountID, userID, reverseProxyID string) (*ReverseProxy, error) + CreateReverseProxy(ctx context.Context, accountID, userID string, reverseProxy *ReverseProxy) (*ReverseProxy, error) + UpdateReverseProxy(ctx context.Context, accountID, userID string, reverseProxy *ReverseProxy) (*ReverseProxy, error) + DeleteReverseProxy(ctx context.Context, accountID, userID, reverseProxyID string) error +} diff --git a/management/internals/modules/reverseproxy/manager/api.go b/management/internals/modules/reverseproxy/manager/api.go new file mode 100644 index 000000000..3253592c0 --- /dev/null +++ b/management/internals/modules/reverseproxy/manager/api.go @@ -0,0 +1,161 @@ +package manager + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + nbcontext "github.com/netbirdio/netbird/management/server/context" + "github.com/netbirdio/netbird/shared/management/http/api" + "github.com/netbirdio/netbird/shared/management/http/util" + "github.com/netbirdio/netbird/shared/management/status" +) + +type handler struct { + manager reverseproxy.Manager +} + +func RegisterEndpoints(router *mux.Router, manager reverseproxy.Manager) { + h := &handler{ + manager: manager, + } + + router.HandleFunc("/reverse-proxies", h.getAllReverseProxies).Methods("GET", "OPTIONS") + router.HandleFunc("/reverse-proxies", h.createReverseProxy).Methods("POST", "OPTIONS") + router.HandleFunc("/reverse-proxies/{reverseProxyId}", h.getReverseProxy).Methods("GET", "OPTIONS") + router.HandleFunc("/reverse-proxies/{reverseProxyId}", h.updateReverseProxy).Methods("PUT", "OPTIONS") + router.HandleFunc("/reverse-proxies/{reverseProxyId}", h.deleteReverseProxy).Methods("DELETE", "OPTIONS") +} + +func (h *handler) getAllReverseProxies(w http.ResponseWriter, r *http.Request) { + userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + allReverseProxies, err := h.manager.GetAllReverseProxies(r.Context(), userAuth.AccountId, userAuth.UserId) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + apiReverseProxies := make([]*api.ReverseProxy, 0, len(allReverseProxies)) + for _, reverseProxy := range allReverseProxies { + apiReverseProxies = append(apiReverseProxies, reverseProxy.ToAPIResponse()) + } + + util.WriteJSONObject(r.Context(), w, apiReverseProxies) +} + +func (h *handler) createReverseProxy(w http.ResponseWriter, r *http.Request) { + userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + var req api.PostApiReverseProxyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) + return + } + + reverseProxy := new(reverseproxy.ReverseProxy) + reverseProxy.FromAPIRequest(&req) + + if err = reverseProxy.Validate(); err != nil { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "%s", err.Error()), w) + return + } + + createdReverseProxy, err := h.manager.CreateReverseProxy(r.Context(), userAuth.AccountId, userAuth.UserId, reverseProxy) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, createdReverseProxy.ToAPIResponse()) +} + +func (h *handler) getReverseProxy(w http.ResponseWriter, r *http.Request) { + userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + reverseProxyID := mux.Vars(r)["proxyId"] + if reverseProxyID == "" { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "reverse proxy ID is required"), w) + return + } + + reverseProxy, err := h.manager.GetReverseProxy(r.Context(), userAuth.AccountId, userAuth.UserId, reverseProxyID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, reverseProxy.ToAPIResponse()) +} + +func (h *handler) updateReverseProxy(w http.ResponseWriter, r *http.Request) { + userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + reverseProxyID := mux.Vars(r)["proxyId"] + if reverseProxyID == "" { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "reverse proxy ID is required"), w) + return + } + + var req api.PutApiReverseProxyProxyIdJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) + return + } + + reverseProxy := new(reverseproxy.ReverseProxy) + reverseProxy.ID = reverseProxyID + reverseProxy.FromAPIRequest(&req) + + if err = reverseProxy.Validate(); err != nil { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "%s", err.Error()), w) + return + } + + updatedReverseProxy, err := h.manager.UpdateReverseProxy(r.Context(), userAuth.AccountId, userAuth.UserId, reverseProxy) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, updatedReverseProxy.ToAPIResponse()) +} + +func (h *handler) deleteReverseProxy(w http.ResponseWriter, r *http.Request) { + userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + reverseProxyID := mux.Vars(r)["proxyId"] + if reverseProxyID == "" { + util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "reverse proxy ID is required"), w) + return + } + + if err := h.manager.DeleteReverseProxy(r.Context(), userAuth.AccountId, userAuth.UserId, reverseProxyID); err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, util.EmptyObject{}) +} diff --git a/management/internals/modules/reverseproxy/manager/manager.go b/management/internals/modules/reverseproxy/manager/manager.go new file mode 100644 index 000000000..6bd50c0d6 --- /dev/null +++ b/management/internals/modules/reverseproxy/manager/manager.go @@ -0,0 +1,171 @@ +package manager + +import ( + "context" + "fmt" + + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + "github.com/netbirdio/netbird/management/server/account" + "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/permissions/modules" + "github.com/netbirdio/netbird/management/server/permissions/operations" + "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/shared/management/status" +) + +type managerImpl struct { + store store.Store + accountManager account.Manager + permissionsManager permissions.Manager +} + +func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager) reverseproxy.Manager { + return &managerImpl{ + store: store, + accountManager: accountManager, + permissionsManager: permissionsManager, + } +} + +func (m *managerImpl) GetAllReverseProxies(ctx context.Context, accountID, userID string) ([]*reverseproxy.ReverseProxy, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetAccountReverseProxies(ctx, store.LockingStrengthNone, accountID) +} + +func (m *managerImpl) GetReverseProxy(ctx context.Context, accountID, userID, reverseProxyID string) (*reverseproxy.ReverseProxy, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetReverseProxyByID(ctx, store.LockingStrengthNone, accountID, reverseProxyID) +} + +func (m *managerImpl) CreateReverseProxy(ctx context.Context, accountID, userID string, reverseProxy *reverseproxy.ReverseProxy) (*reverseproxy.ReverseProxy, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + authConfig := reverseProxy.Auth + + reverseProxy = reverseproxy.NewReverseProxy(accountID, reverseProxy.Name, reverseProxy.Domain, reverseProxy.Targets, reverseProxy.Enabled) + + reverseProxy.Auth = authConfig + + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + // Check for duplicate domain + existingReverseProxy, err := transaction.GetReverseProxyByDomain(ctx, accountID, reverseProxy.Domain) + if err != nil { + if sErr, ok := status.FromError(err); !ok || sErr.Type() != status.NotFound { + return fmt.Errorf("failed to check existing reverse proxy: %w", err) + } + } + if existingReverseProxy != nil { + return status.Errorf(status.AlreadyExists, "reverse proxy with domain %s already exists", reverseProxy.Domain) + } + + if err = transaction.CreateReverseProxy(ctx, reverseProxy); err != nil { + return fmt.Errorf("failed to create reverse proxy: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + m.accountManager.StoreEvent(ctx, userID, reverseProxy.ID, accountID, activity.ReverseProxyCreated, reverseProxy.EventMeta()) + + return reverseProxy, nil +} + +func (m *managerImpl) UpdateReverseProxy(ctx context.Context, accountID, userID string, reverseProxy *reverseproxy.ReverseProxy) (*reverseproxy.ReverseProxy, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Update) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + // Get existing reverse proxy + existingReverseProxy, err := transaction.GetReverseProxyByID(ctx, store.LockingStrengthUpdate, accountID, reverseProxy.ID) + if err != nil { + return err + } + + // Check if domain changed and if it conflicts + if existingReverseProxy.Domain != reverseProxy.Domain { + conflictReverseProxy, err := transaction.GetReverseProxyByDomain(ctx, accountID, reverseProxy.Domain) + if err != nil { + if sErr, ok := status.FromError(err); !ok || sErr.Type() != status.NotFound { + return fmt.Errorf("failed to check existing reverse proxy: %w", err) + } + } + if conflictReverseProxy != nil && conflictReverseProxy.ID != reverseProxy.ID { + return status.Errorf(status.AlreadyExists, "reverse proxy with domain %s already exists", reverseProxy.Domain) + } + } + + if err = transaction.UpdateReverseProxy(ctx, reverseProxy); err != nil { + return fmt.Errorf("failed to update reverse proxy: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + m.accountManager.StoreEvent(ctx, userID, reverseProxy.ID, accountID, activity.ReverseProxyUpdated, reverseProxy.EventMeta()) + + return reverseProxy, nil +} + +func (m *managerImpl) DeleteReverseProxy(ctx context.Context, accountID, userID, reverseProxyID string) error { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete) + if err != nil { + return status.NewPermissionValidationError(err) + } + if !ok { + return status.NewPermissionDeniedError() + } + + var reverseProxy *reverseproxy.ReverseProxy + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + var err error + reverseProxy, err = transaction.GetReverseProxyByID(ctx, store.LockingStrengthUpdate, accountID, reverseProxyID) + if err != nil { + return err + } + + if err = transaction.DeleteReverseProxy(ctx, accountID, reverseProxyID); err != nil { + return fmt.Errorf("failed to delete reverse proxy: %w", err) + } + + return nil + }) + if err != nil { + return err + } + + m.accountManager.StoreEvent(ctx, userID, reverseProxyID, accountID, activity.ReverseProxyDeleted, reverseProxy.EventMeta()) + + return nil +} diff --git a/management/internals/modules/reverseproxy/reverseproxy.go b/management/internals/modules/reverseproxy/reverseproxy.go new file mode 100644 index 000000000..29cf1b5db --- /dev/null +++ b/management/internals/modules/reverseproxy/reverseproxy.go @@ -0,0 +1,196 @@ +package reverseproxy + +import ( + "errors" + + "github.com/rs/xid" + + "github.com/netbirdio/netbird/shared/management/http/api" +) + +type Target struct { + Path *string `json:"path,omitempty"` + Host string `json:"host"` + Port int `json:"port"` + Protocol string `json:"protocol"` + TargetId string `json:"target_id"` + TargetType string `json:"target_type"` + Enabled bool `json:"enabled"` +} + +type PasswordAuthConfig struct { + Enabled bool `json:"enabled"` + Password string `json:"password"` +} + +type PINAuthConfig struct { + Enabled bool `json:"enabled"` + Pin string `json:"pin"` +} + +type BearerAuthConfig struct { + Enabled bool `json:"enabled"` + DistributionGroups []string `json:"distribution_groups,omitempty" gorm:"serializer:json"` +} + +type LinkAuthConfig struct { + Enabled bool `json:"enabled"` +} + +type AuthConfig struct { + PasswordAuth *PasswordAuthConfig `json:"password_auth,omitempty" gorm:"serializer:json"` + PinAuth *PINAuthConfig `json:"pin_auth,omitempty" gorm:"serializer:json"` + BearerAuth *BearerAuthConfig `json:"bearer_auth,omitempty" gorm:"serializer:json"` + LinkAuth *LinkAuthConfig `json:"link_auth,omitempty" gorm:"serializer:json"` +} + +type ReverseProxy struct { + ID string `gorm:"primaryKey"` + AccountID string `gorm:"index"` + Name string + Domain string `gorm:"index"` + Targets []Target `gorm:"serializer:json"` + Enabled bool + Auth AuthConfig `gorm:"serializer:json"` +} + +func NewReverseProxy(accountID, name, domain string, targets []Target, enabled bool) *ReverseProxy { + return &ReverseProxy{ + ID: xid.New().String(), + AccountID: accountID, + Name: name, + Domain: domain, + Targets: targets, + Enabled: enabled, + } +} + +func (r *ReverseProxy) ToAPIResponse() *api.ReverseProxy { + authConfig := api.ReverseProxyAuthConfig{} + + if r.Auth.PasswordAuth != nil { + authConfig.PasswordAuth = &api.PasswordAuthConfig{ + Enabled: r.Auth.PasswordAuth.Enabled, + Password: r.Auth.PasswordAuth.Password, + } + } + + if r.Auth.PinAuth != nil { + authConfig.PinAuth = &api.PINAuthConfig{ + Enabled: r.Auth.PinAuth.Enabled, + Pin: r.Auth.PinAuth.Pin, + } + } + + if r.Auth.BearerAuth != nil { + authConfig.BearerAuth = &api.BearerAuthConfig{ + Enabled: r.Auth.BearerAuth.Enabled, + DistributionGroups: &r.Auth.BearerAuth.DistributionGroups, + } + } + + if r.Auth.LinkAuth != nil { + authConfig.LinkAuth = &api.LinkAuthConfig{ + Enabled: r.Auth.LinkAuth.Enabled, + } + } + + // Convert internal targets to API targets + apiTargets := make([]api.ReverseProxyTarget, 0, len(r.Targets)) + for _, target := range r.Targets { + apiTargets = append(apiTargets, api.ReverseProxyTarget{ + Path: target.Path, + Host: target.Host, + Port: target.Port, + Protocol: api.ReverseProxyTargetProtocol(target.Protocol), + TargetId: target.TargetId, + TargetType: api.ReverseProxyTargetTargetType(target.TargetType), + Enabled: target.Enabled, + }) + } + + return &api.ReverseProxy{ + Id: r.ID, + Name: r.Name, + Domain: r.Domain, + Targets: apiTargets, + Enabled: r.Enabled, + Auth: authConfig, + } +} + +func (r *ReverseProxy) FromAPIRequest(req *api.ReverseProxyRequest) { + r.Name = req.Name + r.Domain = req.Domain + + // Convert API targets to internal targets + targets := make([]Target, 0, len(req.Targets)) + for _, apiTarget := range req.Targets { + targets = append(targets, Target{ + Path: apiTarget.Path, + Host: apiTarget.Host, + Port: apiTarget.Port, + Protocol: string(apiTarget.Protocol), + TargetId: apiTarget.TargetId, + TargetType: string(apiTarget.TargetType), + Enabled: apiTarget.Enabled, + }) + } + r.Targets = targets + + r.Enabled = req.Enabled + + if req.Auth.PasswordAuth != nil { + r.Auth.PasswordAuth = &PasswordAuthConfig{ + Enabled: req.Auth.PasswordAuth.Enabled, + Password: req.Auth.PasswordAuth.Password, + } + } + + if req.Auth.PinAuth != nil { + r.Auth.PinAuth = &PINAuthConfig{ + Enabled: req.Auth.PinAuth.Enabled, + Pin: req.Auth.PinAuth.Pin, + } + } + + if req.Auth.BearerAuth != nil { + bearerAuth := &BearerAuthConfig{ + Enabled: req.Auth.BearerAuth.Enabled, + } + if req.Auth.BearerAuth.DistributionGroups != nil { + bearerAuth.DistributionGroups = *req.Auth.BearerAuth.DistributionGroups + } + r.Auth.BearerAuth = bearerAuth + } + + if req.Auth.LinkAuth != nil { + r.Auth.LinkAuth = &LinkAuthConfig{ + Enabled: req.Auth.LinkAuth.Enabled, + } + } + +} + +func (r *ReverseProxy) Validate() error { + if r.Name == "" { + return errors.New("reverse proxy name is required") + } + if len(r.Name) > 255 { + return errors.New("reverse proxy name exceeds maximum length of 255 characters") + } + + if r.Domain == "" { + return errors.New("reverse proxy domain is required") + } + + if len(r.Targets) == 0 { + return errors.New("at least one target is required") + } + + return nil +} + +func (r *ReverseProxy) EventMeta() map[string]any { + return map[string]any{"name": r.Name, "domain": r.Domain} +} diff --git a/management/internals/modules/services/interface.go b/management/internals/modules/services/interface.go deleted file mode 100644 index ee1ab539b..000000000 --- a/management/internals/modules/services/interface.go +++ /dev/null @@ -1,13 +0,0 @@ -package services - -import ( - "context" -) - -type Manager interface { - GetAllServices(ctx context.Context, accountID, userID string) ([]*Service, error) - GetService(ctx context.Context, accountID, userID, serviceID string) (*Service, error) - CreateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error) - UpdateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error) - DeleteService(ctx context.Context, accountID, userID, serviceID string) error -} diff --git a/management/internals/modules/services/manager/api.go b/management/internals/modules/services/manager/api.go deleted file mode 100644 index d49935c76..000000000 --- a/management/internals/modules/services/manager/api.go +++ /dev/null @@ -1,161 +0,0 @@ -package manager - -import ( - "encoding/json" - "net/http" - - "github.com/gorilla/mux" - - "github.com/netbirdio/netbird/management/internals/modules/services" - nbcontext "github.com/netbirdio/netbird/management/server/context" - "github.com/netbirdio/netbird/shared/management/http/api" - "github.com/netbirdio/netbird/shared/management/http/util" - "github.com/netbirdio/netbird/shared/management/status" -) - -type handler struct { - manager services.Manager -} - -func RegisterEndpoints(router *mux.Router, manager services.Manager) { - h := &handler{ - manager: manager, - } - - router.HandleFunc("/services", h.getAllServices).Methods("GET", "OPTIONS") - router.HandleFunc("/services", h.createService).Methods("POST", "OPTIONS") - router.HandleFunc("/services/{serviceId}", h.getService).Methods("GET", "OPTIONS") - router.HandleFunc("/services/{serviceId}", h.updateService).Methods("PUT", "OPTIONS") - router.HandleFunc("/services/{serviceId}", h.deleteService).Methods("DELETE", "OPTIONS") -} - -func (h *handler) getAllServices(w http.ResponseWriter, r *http.Request) { - userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - allServices, err := h.manager.GetAllServices(r.Context(), userAuth.AccountId, userAuth.UserId) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - apiServices := make([]*api.Service, 0, len(allServices)) - for _, service := range allServices { - apiServices = append(apiServices, service.ToAPIResponse()) - } - - util.WriteJSONObject(r.Context(), w, apiServices) -} - -func (h *handler) createService(w http.ResponseWriter, r *http.Request) { - userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - var req api.PostApiServicesJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) - return - } - - service := new(services.Service) - service.FromAPIRequest(&req) - - if err = service.Validate(); err != nil { - util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "%s", err.Error()), w) - return - } - - createdService, err := h.manager.CreateService(r.Context(), userAuth.AccountId, userAuth.UserId, service) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - util.WriteJSONObject(r.Context(), w, createdService.ToAPIResponse()) -} - -func (h *handler) getService(w http.ResponseWriter, r *http.Request) { - userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - serviceID := mux.Vars(r)["serviceId"] - if serviceID == "" { - util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "service ID is required"), w) - return - } - - service, err := h.manager.GetService(r.Context(), userAuth.AccountId, userAuth.UserId, serviceID) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - util.WriteJSONObject(r.Context(), w, service.ToAPIResponse()) -} - -func (h *handler) updateService(w http.ResponseWriter, r *http.Request) { - userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - serviceID := mux.Vars(r)["serviceId"] - if serviceID == "" { - util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "service ID is required"), w) - return - } - - var req api.PutApiServicesServiceIdJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) - return - } - - service := new(services.Service) - service.ID = serviceID - service.FromAPIRequest(&req) - - if err = service.Validate(); err != nil { - util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "%s", err.Error()), w) - return - } - - updatedService, err := h.manager.UpdateService(r.Context(), userAuth.AccountId, userAuth.UserId, service) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - util.WriteJSONObject(r.Context(), w, updatedService.ToAPIResponse()) -} - -func (h *handler) deleteService(w http.ResponseWriter, r *http.Request) { - userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) - if err != nil { - util.WriteError(r.Context(), err, w) - return - } - - serviceID := mux.Vars(r)["serviceId"] - if serviceID == "" { - util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "service ID is required"), w) - return - } - - if err := h.manager.DeleteService(r.Context(), userAuth.AccountId, userAuth.UserId, serviceID); err != nil { - util.WriteError(r.Context(), err, w) - return - } - - util.WriteJSONObject(r.Context(), w, util.EmptyObject{}) -} diff --git a/management/internals/modules/services/manager/manager.go b/management/internals/modules/services/manager/manager.go deleted file mode 100644 index 7ae380997..000000000 --- a/management/internals/modules/services/manager/manager.go +++ /dev/null @@ -1,199 +0,0 @@ -package manager - -import ( - "context" - "fmt" - - "github.com/netbirdio/netbird/management/internals/modules/services" - "github.com/netbirdio/netbird/management/server/account" - "github.com/netbirdio/netbird/management/server/activity" - "github.com/netbirdio/netbird/management/server/permissions" - "github.com/netbirdio/netbird/management/server/permissions/modules" - "github.com/netbirdio/netbird/management/server/permissions/operations" - "github.com/netbirdio/netbird/management/server/store" - "github.com/netbirdio/netbird/shared/management/status" -) - -type managerImpl struct { - store store.Store - accountManager account.Manager - permissionsManager permissions.Manager -} - -func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager) services.Manager { - return &managerImpl{ - store: store, - accountManager: accountManager, - permissionsManager: permissionsManager, - } -} - -func (m *managerImpl) GetAllServices(ctx context.Context, accountID, userID string) ([]*services.Service, error) { - ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read) - if err != nil { - return nil, status.NewPermissionValidationError(err) - } - if !ok { - return nil, status.NewPermissionDeniedError() - } - - return m.store.GetAccountServices(ctx, store.LockingStrengthNone, accountID) -} - -func (m *managerImpl) GetService(ctx context.Context, accountID, userID, serviceID string) (*services.Service, error) { - ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read) - if err != nil { - return nil, status.NewPermissionValidationError(err) - } - if !ok { - return nil, status.NewPermissionDeniedError() - } - - return m.store.GetServiceByID(ctx, store.LockingStrengthNone, accountID, serviceID) -} - -func (m *managerImpl) CreateService(ctx context.Context, accountID, userID string, service *services.Service) (*services.Service, error) { - ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create) - if err != nil { - return nil, status.NewPermissionValidationError(err) - } - if !ok { - return nil, status.NewPermissionDeniedError() - } - - // Store auth config before creating new service - authType := service.AuthType - authBasicUsername := service.AuthBasicUsername - authBasicPassword := service.AuthBasicPassword - authPINValue := service.AuthPINValue - authPINHeader := service.AuthPINHeader - authBearerEnabled := service.AuthBearerEnabled - - service = services.NewService(accountID, service.Name, service.Description, service.Domain, service.Targets, service.DistributionGroups, service.Enabled, service.Exposed) - - // Restore auth config - service.AuthType = authType - service.AuthBasicUsername = authBasicUsername - service.AuthBasicPassword = authBasicPassword - service.AuthPINValue = authPINValue - service.AuthPINHeader = authPINHeader - service.AuthBearerEnabled = authBearerEnabled - - err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { - // Check for duplicate domain - existingService, err := transaction.GetServiceByDomain(ctx, accountID, service.Domain) - if err != nil { - if sErr, ok := status.FromError(err); !ok || sErr.Type() != status.NotFound { - return fmt.Errorf("failed to check existing service: %w", err) - } - } - if existingService != nil { - return status.Errorf(status.AlreadyExists, "service with domain %s already exists", service.Domain) - } - - // Validate distribution groups exist - for _, groupID := range service.DistributionGroups { - _, err = transaction.GetGroupByID(ctx, store.LockingStrengthNone, accountID, groupID) - if err != nil { - return status.Errorf(status.InvalidArgument, "%s", err.Error()) - } - } - - if err = transaction.CreateService(ctx, service); err != nil { - return fmt.Errorf("failed to create service: %w", err) - } - - return nil - }) - if err != nil { - return nil, err - } - - m.accountManager.StoreEvent(ctx, userID, service.ID, accountID, activity.ServiceCreated, service.EventMeta()) - - return service, nil -} - -func (m *managerImpl) UpdateService(ctx context.Context, accountID, userID string, service *services.Service) (*services.Service, error) { - ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Update) - if err != nil { - return nil, status.NewPermissionValidationError(err) - } - if !ok { - return nil, status.NewPermissionDeniedError() - } - - err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { - // Get existing service - existingService, err := transaction.GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, service.ID) - if err != nil { - return err - } - - // Check if domain changed and if it conflicts - if existingService.Domain != service.Domain { - conflictService, err := transaction.GetServiceByDomain(ctx, accountID, service.Domain) - if err != nil { - if sErr, ok := status.FromError(err); !ok || sErr.Type() != status.NotFound { - return fmt.Errorf("failed to check existing service: %w", err) - } - } - if conflictService != nil && conflictService.ID != service.ID { - return status.Errorf(status.AlreadyExists, "service with domain %s already exists", service.Domain) - } - } - - // Validate distribution groups exist - for _, groupID := range service.DistributionGroups { - _, err = transaction.GetGroupByID(ctx, store.LockingStrengthNone, accountID, groupID) - if err != nil { - return status.Errorf(status.InvalidArgument, "%s", err.Error()) - } - } - - if err = transaction.UpdateService(ctx, service); err != nil { - return fmt.Errorf("failed to update service: %w", err) - } - - return nil - }) - if err != nil { - return nil, err - } - - m.accountManager.StoreEvent(ctx, userID, service.ID, accountID, activity.ServiceUpdated, service.EventMeta()) - - return service, nil -} - -func (m *managerImpl) DeleteService(ctx context.Context, accountID, userID, serviceID string) error { - ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete) - if err != nil { - return status.NewPermissionValidationError(err) - } - if !ok { - return status.NewPermissionDeniedError() - } - - var service *services.Service - err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { - var err error - service, err = transaction.GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID) - if err != nil { - return err - } - - if err = transaction.DeleteService(ctx, accountID, serviceID); err != nil { - return fmt.Errorf("failed to delete service: %w", err) - } - - return nil - }) - if err != nil { - return err - } - - m.accountManager.StoreEvent(ctx, userID, serviceID, accountID, activity.ServiceDeleted, service.EventMeta()) - - return nil -} diff --git a/management/internals/modules/services/service.go b/management/internals/modules/services/service.go deleted file mode 100644 index cc685f7a4..000000000 --- a/management/internals/modules/services/service.go +++ /dev/null @@ -1,184 +0,0 @@ -package services - -import ( - "errors" - - "github.com/rs/xid" - - "github.com/netbirdio/netbird/shared/management/http/api" -) - -type Target struct { - Path string `json:"path"` - Host string `json:"host"` - Enabled bool `json:"enabled"` -} - -type Service struct { - ID string `gorm:"primaryKey"` - AccountID string `gorm:"index"` - Name string - Description string - Domain string `gorm:"index"` - Targets []Target `gorm:"serializer:json"` - DistributionGroups []string `gorm:"serializer:json"` - Enabled bool - Exposed bool - - // Authentication configuration - AuthType string - AuthBasicUsername string - AuthBasicPassword string - AuthPINValue string - AuthPINHeader string - AuthBearerEnabled bool -} - -func NewService(accountID, name, description, domain string, targets []Target, distributionGroups []string, enabled, exposed bool) *Service { - return &Service{ - ID: xid.New().String(), - AccountID: accountID, - Name: name, - Description: description, - Domain: domain, - Targets: targets, - DistributionGroups: distributionGroups, - Enabled: enabled, - Exposed: exposed, - } -} - -func (s *Service) ToAPIResponse() *api.Service { - var authConfig *api.ServiceAuthConfig - - switch s.AuthType { - case "basic": - authConfig = &api.ServiceAuthConfig{ - Type: "basic", - BasicAuth: &api.BasicAuthConfig{ - Username: s.AuthBasicUsername, - Password: s.AuthBasicPassword, - }, - } - case "pin": - authConfig = &api.ServiceAuthConfig{ - Type: "pin", - PinAuth: &api.PINAuthConfig{ - Pin: s.AuthPINValue, - Header: s.AuthPINHeader, - }, - } - case "bearer": - authConfig = &api.ServiceAuthConfig{ - Type: "bearer", - BearerAuth: &api.BearerAuthConfig{ - Enabled: s.AuthBearerEnabled, - }, - } - } - - // Convert internal targets to API targets - apiTargets := make([]api.ServiceTarget, 0, len(s.Targets)) - for _, target := range s.Targets { - apiTargets = append(apiTargets, api.ServiceTarget{ - Path: target.Path, - Host: target.Host, - Enabled: target.Enabled, - }) - } - - return &api.Service{ - Id: s.ID, - Name: s.Name, - Description: &s.Description, - Domain: s.Domain, - Targets: apiTargets, - DistributionGroups: s.DistributionGroups, - Enabled: s.Enabled, - Exposed: s.Exposed, - Auth: authConfig, - } -} - -func (s *Service) FromAPIRequest(req *api.ServiceRequest) { - s.Name = req.Name - s.Domain = req.Domain - - // Convert API targets to internal targets - targets := make([]Target, 0, len(req.Targets)) - for _, apiTarget := range req.Targets { - targets = append(targets, Target{ - Path: apiTarget.Path, - Host: apiTarget.Host, - Enabled: apiTarget.Enabled, - }) - } - s.Targets = targets - - s.DistributionGroups = req.DistributionGroups - - if req.Description != nil { - s.Description = *req.Description - } - - enabled := true - if req.Enabled != nil { - enabled = *req.Enabled - } - s.Enabled = enabled - - exposed := false - if req.Exposed != nil { - exposed = *req.Exposed - } - s.Exposed = exposed - - // Handle auth config - if req.Auth != nil { - s.AuthType = string(req.Auth.Type) - - switch req.Auth.Type { - case "basic": - if req.Auth.BasicAuth != nil { - s.AuthBasicUsername = req.Auth.BasicAuth.Username - s.AuthBasicPassword = req.Auth.BasicAuth.Password - } - case "pin": - if req.Auth.PinAuth != nil { - s.AuthPINValue = req.Auth.PinAuth.Pin - s.AuthPINHeader = req.Auth.PinAuth.Header - } - case "bearer": - if req.Auth.BearerAuth != nil { - s.AuthBearerEnabled = req.Auth.BearerAuth.Enabled - } - } - } -} - -func (s *Service) Validate() error { - if s.Name == "" { - return errors.New("service name is required") - } - if len(s.Name) > 255 { - return errors.New("service name exceeds maximum length of 255 characters") - } - - if s.Domain == "" { - return errors.New("service domain is required") - } - - if len(s.Targets) == 0 { - return errors.New("at least one target is required") - } - - if len(s.DistributionGroups) == 0 { - return errors.New("at least one distribution group is required") - } - - return nil -} - -func (s *Service) EventMeta() map[string]any { - return map[string]any{"name": s.Name, "domain": s.Domain} -} diff --git a/management/internals/server/boot.go b/management/internals/server/boot.go index f42c40714..624bcd6ee 100644 --- a/management/internals/server/boot.go +++ b/management/internals/server/boot.go @@ -92,7 +92,7 @@ func (s *BaseServer) EventStore() activity.Store { func (s *BaseServer) APIHandler() http.Handler { return Create(s, func() http.Handler { - httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.IntegratedValidator(), s.ProxyController(), s.PermissionsManager(), s.PeersManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager()) + httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.IntegratedValidator(), s.ProxyController(), s.PermissionsManager(), s.PeersManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ReverseProxyManager()) if err != nil { log.Fatalf("failed to create API handler: %v", err) } @@ -150,7 +150,7 @@ func (s *BaseServer) GRPCServer() *grpc.Server { } mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv) - proxyService := nbgrpc.NewProxyServiceServer(s.Store(), s.AccountManager()) + proxyService := nbgrpc.NewProxyServiceServer(s.Store()) mgmtProto.RegisterProxyServiceServer(gRPCAPIHandler, proxyService) log.Info("ProxyService registered on gRPC server") diff --git a/management/internals/server/modules.go b/management/internals/server/modules.go index bd6a69cfa..8a768acf6 100644 --- a/management/internals/server/modules.go +++ b/management/internals/server/modules.go @@ -8,8 +8,8 @@ import ( "github.com/netbirdio/management-integrations/integrations" "github.com/netbirdio/netbird/management/internals/modules/peers" - "github.com/netbirdio/netbird/management/internals/modules/services" - nbservices "github.com/netbirdio/netbird/management/internals/modules/services/manager" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + nbreverseproxy "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/manager" "github.com/netbirdio/netbird/management/internals/modules/zones" zonesManager "github.com/netbirdio/netbird/management/internals/modules/zones/manager" "github.com/netbirdio/netbird/management/internals/modules/zones/records" @@ -177,8 +177,8 @@ func (s *BaseServer) RecordsManager() records.Manager { }) } -func (s *BaseServer) ServiceManager() services.Manager { - return Create(s, func() services.Manager { - return nbservices.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager()) +func (s *BaseServer) ReverseProxyManager() reverseproxy.Manager { + return Create(s, func() reverseproxy.Manager { + return nbreverseproxy.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager()) }) } diff --git a/management/internals/shared/grpc/proxy.go b/management/internals/shared/grpc/proxy.go index 4a9c4f765..5ba365e63 100644 --- a/management/internals/shared/grpc/proxy.go +++ b/management/internals/shared/grpc/proxy.go @@ -6,18 +6,19 @@ import ( "sync" "time" - "github.com/netbirdio/netbird/management/internals/modules/services" - "github.com/netbirdio/netbird/management/server/store" - "github.com/netbirdio/netbird/management/server/types" - "github.com/netbirdio/netbird/shared/management/proto" log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/shared/management/proto" ) -type serviceStore interface { - GetAccountServices(ctx context.Context, lockStrength store.LockingStrength, accountID string) ([]*services.Service, error) +type reverseProxyStore interface { + GetAccountReverseProxies(ctx context.Context, lockStrength store.LockingStrength, accountID string) ([]*reverseproxy.ReverseProxy, error) } type keyStore interface { @@ -31,11 +32,11 @@ type ProxyServiceServer struct { // Map of connected proxies: proxy_id -> proxy connection connectedProxies sync.Map - // Channel for broadcasting service updates to all proxies + // Channel for broadcasting reverse proxy updates to all proxies updatesChan chan *proto.ProxyMapping - // Store of services - serviceStore serviceStore + // Store of reverse proxies + reverseProxyStore reverseProxyStore // Store for client setup keys keyStore keyStore @@ -52,10 +53,10 @@ type proxyConnection struct { } // NewProxyServiceServer creates a new proxy service server -func NewProxyServiceServer(store serviceStore) *ProxyServiceServer { +func NewProxyServiceServer(store reverseProxyStore) *ProxyServiceServer { return &ProxyServiceServer{ - updatesChan: make(chan *proto.ProxyMapping, 100), - serviceStore: store, + updatesChan: make(chan *proto.ProxyMapping, 100), + reverseProxyStore: store, } } @@ -110,51 +111,51 @@ func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest } } -// sendSnapshot sends the initial snapshot of all services to proxy +// sendSnapshot sends the initial snapshot of all reverse proxies to proxy func (s *ProxyServiceServer) sendSnapshot(ctx context.Context, conn *proxyConnection) error { - svcs, err := s.serviceStore.GetAccountServices(ctx, store.LockingStrengthNone, conn.proxyID) // TODO: check locking strength and accountID. + reverseProxies, err := s.reverseProxyStore.GetAccountReverseProxies(ctx, store.LockingStrengthNone, conn.proxyID) // TODO: check locking strength and accountID. if err != nil { // TODO: something - return fmt.Errorf("get account services from store: %w", err) + return fmt.Errorf("get account reverse proxies from store: %w", err) } - for _, svc := range svcs { - if !svc.Enabled || !svc.Exposed { - // We don't care about disabled services for snapshots. + for _, rp := range reverseProxies { + if !rp.Enabled { + // We don't care about disabled reverse proxies for snapshots. continue } // Fill auth values. // TODO: This will be removed soon as the management server should be handling authentication rather than the proxy, so probably not much use in fleshing this out too much. auth := &proto.Authentication{} - if svc.AuthBearerEnabled { + if rp.Auth.BearerAuth != nil && rp.Auth.BearerAuth.Enabled { auth.Oidc = &proto.OIDC{ Enabled: true, // TODO: fill other OIDC fields from account OIDC settings. } } - if svc.AuthBasicPassword != "" { + if rp.Auth.PasswordAuth != nil && rp.Auth.PasswordAuth.Password != "" { auth.Basic = &proto.HTTPBasic{ Enabled: true, - Username: svc.AuthBasicUsername, - Password: svc.AuthBasicPassword, + Username: "", + Password: rp.Auth.PasswordAuth.Password, } } - if svc.AuthPINValue != "" { + if rp.Auth.PinAuth != nil && rp.Auth.PinAuth.Pin != "" { auth.Pin = &proto.Pin{ Enabled: true, - Pin: svc.AuthPINValue, + Pin: rp.Auth.PinAuth.Pin, } } var paths []*proto.PathMapping - for _, t := range svc.Targets { + for _, t := range rp.Targets { if !t.Enabled { - // We don't care about disabled service targets for snapshots. + // We don't care about disabled reverse proxy targets for snapshots. continue } paths = append(paths, &proto.PathMapping{ - Path: t.Path, + Path: *t.Path, Target: t.Host, }) } @@ -179,8 +180,8 @@ func (s *ProxyServiceServer) sendSnapshot(ctx context.Context, conn *proxyConnec Mapping: []*proto.ProxyMapping{ { Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED, // Initial snapshot, all records are "new" for the proxy. - Id: svc.ID, - Domain: svc.Domain, + Id: rp.ID, + Domain: rp.Domain, Path: paths, SetupKey: key.Key, Auth: auth, @@ -214,34 +215,34 @@ func (s *ProxyServiceServer) sender(conn *proxyConnection, errChan chan<- error) // SendAccessLog processes access log from proxy func (s *ProxyServiceServer) SendAccessLog(ctx context.Context, req *proto.SendAccessLogRequest) (*proto.SendAccessLogResponse, error) { log.WithFields(log.Fields{ - "proxy_id": "", // TODO: get proxy id, probably from context or maybe from request message. - "service_id": req.GetLog().GetServiceId(), - "host": req.GetLog().GetHost(), - "path": req.GetLog().GetPath(), - "method": req.GetLog().GetMethod(), - "response_code": req.GetLog().GetResponseCode(), - "duration_ms": req.GetLog().GetDurationMs(), - "source_ip": req.GetLog().GetSourceIp(), - "auth_mechanism": req.GetLog().GetAuthMechanism(), - "user_id": req.GetLog().GetUserId(), - "auth_success": req.GetLog().GetAuthSuccess(), + "proxy_id": "", // TODO: get proxy id, probably from context or maybe from request message. + "reverse_proxy_id": req.GetLog().GetServiceId(), + "host": req.GetLog().GetHost(), + "path": req.GetLog().GetPath(), + "method": req.GetLog().GetMethod(), + "response_code": req.GetLog().GetResponseCode(), + "duration_ms": req.GetLog().GetDurationMs(), + "source_ip": req.GetLog().GetSourceIp(), + "auth_mechanism": req.GetLog().GetAuthMechanism(), + "user_id": req.GetLog().GetUserId(), + "auth_success": req.GetLog().GetAuthSuccess(), }).Info("Access log from proxy") // TODO: Store access log in database/metrics system return &proto.SendAccessLogResponse{}, nil } -// SendServiceUpdate broadcasts a service update to all connected proxies. -// Management should call this when services are created/updated/removed -func (s *ProxyServiceServer) SendServiceUpdate(update *proto.ProxyMapping) { +// SendReverseProxyUpdate broadcasts a reverse proxy update to all connected proxies. +// Management should call this when reverse proxies are created/updated/removed +func (s *ProxyServiceServer) SendReverseProxyUpdate(update *proto.ProxyMapping) { // Send it to all connected proxies s.connectedProxies.Range(func(key, value interface{}) bool { conn := value.(*proxyConnection) select { case conn.sendChan <- update: - log.Debugf("Sent service update to proxy %s", conn.proxyID) + log.Debugf("Sent reverse proxy update to proxy %s", conn.proxyID) default: - log.Warnf("Failed to send service update to proxy %s (channel full)", conn.proxyID) + log.Warnf("Failed to send reverse proxy update to proxy %s (channel full)", conn.proxyID) } return true }) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index e1b7e5300..74f1c461a 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -204,9 +204,9 @@ const ( UserInviteLinkRegenerated Activity = 106 UserInviteLinkDeleted Activity = 107 - ServiceCreated Activity = 108 - ServiceUpdated Activity = 109 - ServiceDeleted Activity = 110 + ReverseProxyCreated Activity = 108 + ReverseProxyUpdated Activity = 109 + ReverseProxyDeleted Activity = 110 AccountDeleted Activity = 99999 ) @@ -342,9 +342,9 @@ var activityMap = map[Activity]Code{ UserInviteLinkRegenerated: {"User invite link regenerated", "user.invite.link.regenerate"}, UserInviteLinkDeleted: {"User invite link deleted", "user.invite.link.delete"}, - ServiceCreated: {"Service created", "service.create"}, - ServiceUpdated: {"Service updated", "service.update"}, - ServiceDeleted: {"Service deleted", "service.delete"}, + ReverseProxyCreated: {"Reverse proxy created", "reverseproxy.create"}, + ReverseProxyUpdated: {"Reverse proxy updated", "reverseproxy.update"}, + ReverseProxyDeleted: {"Reverse proxy deleted", "reverseproxy.delete"}, } // StringCode returns a string code of the activity diff --git a/management/server/http/handler.go b/management/server/http/handler.go index 3615f0dd0..8205b2258 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -12,8 +12,8 @@ import ( "github.com/rs/cors" log "github.com/sirupsen/logrus" - nbservices "github.com/netbirdio/netbird/management/internals/modules/services" - services "github.com/netbirdio/netbird/management/internals/modules/services/manager" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + reverseproxymanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/manager" idpmanager "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/management-integrations/integrations" @@ -62,7 +62,7 @@ const ( ) // NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints. -func NewAPIHandler(ctx context.Context, accountManager account.Manager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, authManager auth.Manager, appMetrics telemetry.AppMetrics, integratedValidator integrated_validator.IntegratedValidator, proxyController port_forwarding.Controller, permissionsManager permissions.Manager, peersManager nbpeers.Manager, settingsManager settings.Manager, zManager zones.Manager, rManager records.Manager, networkMapController network_map.Controller, idpManager idpmanager.Manager, serviceManager nbservices.Manager) (http.Handler, error) { +func NewAPIHandler(ctx context.Context, accountManager account.Manager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, authManager auth.Manager, appMetrics telemetry.AppMetrics, integratedValidator integrated_validator.IntegratedValidator, proxyController port_forwarding.Controller, permissionsManager permissions.Manager, peersManager nbpeers.Manager, settingsManager settings.Manager, zManager zones.Manager, rManager records.Manager, networkMapController network_map.Controller, idpManager idpmanager.Manager, reverseProxyManager reverseproxy.Manager) (http.Handler, error) { // Register bypass paths for unauthenticated endpoints if err := bypass.AddBypassPath("/api/instance"); err != nil { @@ -158,7 +158,7 @@ func NewAPIHandler(ctx context.Context, accountManager account.Manager, networks idp.AddEndpoints(accountManager, router) instance.AddEndpoints(instanceManager, router) instance.AddVersionEndpoint(instanceManager, router) - services.RegisterEndpoints(router, serviceManager) + reverseproxymanager.RegisterEndpoints(router, reverseProxyManager) // Mount embedded IdP handler at /oauth2 path if configured if embeddedIdpEnabled { diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 5959d9946..731fb6cb1 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -27,7 +27,7 @@ import ( "gorm.io/gorm/logger" nbdns "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/internals/modules/services" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" "github.com/netbirdio/netbird/management/internals/modules/zones" "github.com/netbirdio/netbird/management/internals/modules/zones/records" resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" @@ -127,7 +127,7 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine types.Engine, met &types.Account{}, &types.Policy{}, &types.PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, &installation{}, &types.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{}, &networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{}, &types.AccountOnboarding{}, - &types.Job{}, &zones.Zone{}, &records.Record{}, &types.UserInviteRecord{}, &services.Service{}, + &types.Job{}, &zones.Zone{}, &records.Record{}, &types.UserInviteRecord{}, &reverseproxy.ReverseProxy{}, ) if err != nil { return nil, fmt.Errorf("auto migratePreAuto: %w", err) @@ -4604,87 +4604,87 @@ func (s *SqlStore) GetPeerIDByKey(ctx context.Context, lockStrength LockingStren return peerID, nil } -func (s *SqlStore) CreateService(ctx context.Context, service *services.Service) error { - result := s.db.Create(service) +func (s *SqlStore) CreateReverseProxy(ctx context.Context, proxy *reverseproxy.ReverseProxy) error { + result := s.db.Create(proxy) if result.Error != nil { - log.WithContext(ctx).Errorf("failed to create service to store: %v", result.Error) - return status.Errorf(status.Internal, "failed to create service to store") + log.WithContext(ctx).Errorf("failed to create reverse proxy to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to create reverse proxy to store") } return nil } -func (s *SqlStore) UpdateService(ctx context.Context, service *services.Service) error { - result := s.db.Select("*").Save(service) +func (s *SqlStore) UpdateReverseProxy(ctx context.Context, proxy *reverseproxy.ReverseProxy) error { + result := s.db.Select("*").Save(proxy) if result.Error != nil { - log.WithContext(ctx).Errorf("failed to update service to store: %v", result.Error) - return status.Errorf(status.Internal, "failed to update service to store") + log.WithContext(ctx).Errorf("failed to update reverse proxy to store: %v", result.Error) + return status.Errorf(status.Internal, "failed to update reverse proxy to store") } return nil } -func (s *SqlStore) DeleteService(ctx context.Context, accountID, serviceID string) error { - result := s.db.Delete(&services.Service{}, accountAndIDQueryCondition, accountID, serviceID) +func (s *SqlStore) DeleteReverseProxy(ctx context.Context, accountID, proxyID string) error { + result := s.db.Delete(&reverseproxy.ReverseProxy{}, accountAndIDQueryCondition, accountID, proxyID) if result.Error != nil { - log.WithContext(ctx).Errorf("failed to delete service from store: %v", result.Error) - return status.Errorf(status.Internal, "failed to delete service from store") + log.WithContext(ctx).Errorf("failed to delete reverse proxy from store: %v", result.Error) + return status.Errorf(status.Internal, "failed to delete reverse proxy from store") } if result.RowsAffected == 0 { - return status.Errorf(status.NotFound, "service %s not found", serviceID) + return status.Errorf(status.NotFound, "reverse proxy %s not found", proxyID) } return nil } -func (s *SqlStore) GetServiceByID(ctx context.Context, lockStrength LockingStrength, accountID, serviceID string) (*services.Service, error) { +func (s *SqlStore) GetReverseProxyByID(ctx context.Context, lockStrength LockingStrength, accountID, proxyID string) (*reverseproxy.ReverseProxy, error) { tx := s.db if lockStrength != LockingStrengthNone { tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) } - var service *services.Service - result := tx.Take(&service, accountAndIDQueryCondition, accountID, serviceID) + var proxy *reverseproxy.ReverseProxy + result := tx.Take(&proxy, accountAndIDQueryCondition, accountID, proxyID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return nil, status.Errorf(status.NotFound, "service %s not found", serviceID) + return nil, status.Errorf(status.NotFound, "reverse proxy %s not found", proxyID) } - log.WithContext(ctx).Errorf("failed to get service from store: %v", result.Error) - return nil, status.Errorf(status.Internal, "failed to get service from store") + log.WithContext(ctx).Errorf("failed to get reverse proxy from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get reverse proxy from store") } - return service, nil + return proxy, nil } -func (s *SqlStore) GetServiceByDomain(ctx context.Context, accountID, domain string) (*services.Service, error) { - var service *services.Service - result := s.db.Where("account_id = ? AND domain = ?", accountID, domain).First(&service) +func (s *SqlStore) GetReverseProxyByDomain(ctx context.Context, accountID, domain string) (*reverseproxy.ReverseProxy, error) { + var proxy *reverseproxy.ReverseProxy + result := s.db.Where("account_id = ? AND domain = ?", accountID, domain).First(&proxy) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return nil, status.Errorf(status.NotFound, "service with domain %s not found", domain) + return nil, status.Errorf(status.NotFound, "reverse proxy with domain %s not found", domain) } - log.WithContext(ctx).Errorf("failed to get service by domain from store: %v", result.Error) - return nil, status.Errorf(status.Internal, "failed to get service by domain from store") + log.WithContext(ctx).Errorf("failed to get reverse proxy by domain from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get reverse proxy by domain from store") } - return service, nil + return proxy, nil } -func (s *SqlStore) GetAccountServices(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*services.Service, error) { +func (s *SqlStore) GetAccountReverseProxies(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*reverseproxy.ReverseProxy, error) { tx := s.db if lockStrength != LockingStrengthNone { tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)}) } - var servicesList []*services.Service - result := tx.Find(&servicesList, accountIDCondition, accountID) + var proxyList []*reverseproxy.ReverseProxy + result := tx.Find(&proxyList, accountIDCondition, accountID) if result.Error != nil { - log.WithContext(ctx).Errorf("failed to get services from the store: %s", result.Error) - return nil, status.Errorf(status.Internal, "failed to get services from store") + log.WithContext(ctx).Errorf("failed to get reverse proxy from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "failed to get reverse proxy from store") } - return servicesList, nil + return proxyList, nil } diff --git a/management/server/store/store.go b/management/server/store/store.go index f102f7793..c7c2a1c2c 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -23,7 +23,7 @@ import ( "gorm.io/gorm" "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/internals/modules/services" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" "github.com/netbirdio/netbird/management/internals/modules/zones" "github.com/netbirdio/netbird/management/internals/modules/zones/records" "github.com/netbirdio/netbird/management/server/telemetry" @@ -242,12 +242,12 @@ type Store interface { MarkAllPendingJobsAsFailed(ctx context.Context, accountID, peerID, reason string) error GetPeerIDByKey(ctx context.Context, lockStrength LockingStrength, key string) (string, error) - CreateService(ctx context.Context, service *services.Service) error - UpdateService(ctx context.Context, service *services.Service) error - DeleteService(ctx context.Context, accountID, serviceID string) error - GetServiceByID(ctx context.Context, lockStrength LockingStrength, accountID, serviceID string) (*services.Service, error) - GetServiceByDomain(ctx context.Context, accountID, domain string) (*services.Service, error) - GetAccountServices(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*services.Service, error) + CreateReverseProxy(ctx context.Context, service *reverseproxy.ReverseProxy) error + UpdateReverseProxy(ctx context.Context, service *reverseproxy.ReverseProxy) error + DeleteReverseProxy(ctx context.Context, accountID, serviceID string) error + GetReverseProxyByID(ctx context.Context, lockStrength LockingStrength, accountID, serviceID string) (*reverseproxy.ReverseProxy, error) + GetReverseProxyByDomain(ctx context.Context, accountID, domain string) (*reverseproxy.ReverseProxy, error) + GetAccountReverseProxies(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*reverseproxy.ReverseProxy, error) } const ( diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index f3dba3d0e..24c9df9c2 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -2855,10 +2855,6 @@ components: ReverseProxyAuthConfig: type: object properties: - type: - type: string - enum: [password, pin, bearer, link] - description: Authentication type password_auth: $ref: '#/components/schemas/PasswordAuthConfig' pin_auth: @@ -2867,8 +2863,6 @@ components: $ref: '#/components/schemas/BearerAuthConfig' link_auth: $ref: '#/components/schemas/LinkAuthConfig' - required: - - type PasswordAuthConfig: type: object properties: diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index fc84d0eed..d006d81b1 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -1,10 +1,14 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.11.1-0.20220912230023-4a1477f6a8ba DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. package api import ( + "encoding/json" + "errors" "time" + + "github.com/oapi-codegen/runtime" ) const ( @@ -21,56 +25,118 @@ const ( // Defines values for EventActivityCode. const ( - EventActivityCodeAccountCreate EventActivityCode = "account.create" - EventActivityCodeAccountSettingPeerLoginExpirationDisable EventActivityCode = "account.setting.peer.login.expiration.disable" - EventActivityCodeAccountSettingPeerLoginExpirationEnable EventActivityCode = "account.setting.peer.login.expiration.enable" - EventActivityCodeAccountSettingPeerLoginExpirationUpdate EventActivityCode = "account.setting.peer.login.expiration.update" - EventActivityCodeDnsSettingDisabledManagementGroupAdd EventActivityCode = "dns.setting.disabled.management.group.add" - EventActivityCodeDnsSettingDisabledManagementGroupDelete EventActivityCode = "dns.setting.disabled.management.group.delete" - EventActivityCodeGroupAdd EventActivityCode = "group.add" - EventActivityCodeGroupUpdate EventActivityCode = "group.update" - EventActivityCodeNameserverGroupAdd EventActivityCode = "nameserver.group.add" - EventActivityCodeNameserverGroupDelete EventActivityCode = "nameserver.group.delete" - EventActivityCodeNameserverGroupUpdate EventActivityCode = "nameserver.group.update" - EventActivityCodePeerLoginExpirationDisable EventActivityCode = "peer.login.expiration.disable" - EventActivityCodePeerLoginExpirationEnable EventActivityCode = "peer.login.expiration.enable" - EventActivityCodePeerLoginExpire EventActivityCode = "peer.login.expire" - EventActivityCodePeerRename EventActivityCode = "peer.rename" - EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable" - EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable" - EventActivityCodePersonalAccessTokenCreate EventActivityCode = "personal.access.token.create" - EventActivityCodePersonalAccessTokenDelete EventActivityCode = "personal.access.token.delete" - EventActivityCodePolicyAdd EventActivityCode = "policy.add" - EventActivityCodePolicyDelete EventActivityCode = "policy.delete" - EventActivityCodePolicyUpdate EventActivityCode = "policy.update" - EventActivityCodeRouteAdd EventActivityCode = "route.add" - EventActivityCodeRouteDelete EventActivityCode = "route.delete" - EventActivityCodeRouteUpdate EventActivityCode = "route.update" - EventActivityCodeRuleAdd EventActivityCode = "rule.add" - EventActivityCodeRuleDelete EventActivityCode = "rule.delete" - EventActivityCodeRuleUpdate EventActivityCode = "rule.update" - EventActivityCodeServiceCreate EventActivityCode = "service.create" - EventActivityCodeServiceDelete EventActivityCode = "service.delete" - EventActivityCodeServiceUpdate EventActivityCode = "service.update" - EventActivityCodeServiceUserCreate EventActivityCode = "service.user.create" - EventActivityCodeServiceUserDelete EventActivityCode = "service.user.delete" - EventActivityCodeSetupkeyAdd EventActivityCode = "setupkey.add" - EventActivityCodeSetupkeyGroupAdd EventActivityCode = "setupkey.group.add" - EventActivityCodeSetupkeyGroupDelete EventActivityCode = "setupkey.group.delete" - EventActivityCodeSetupkeyOveruse EventActivityCode = "setupkey.overuse" - EventActivityCodeSetupkeyPeerAdd EventActivityCode = "setupkey.peer.add" - EventActivityCodeSetupkeyRevoke EventActivityCode = "setupkey.revoke" - EventActivityCodeSetupkeyUpdate EventActivityCode = "setupkey.update" - EventActivityCodeUserBlock EventActivityCode = "user.block" - EventActivityCodeUserGroupAdd EventActivityCode = "user.group.add" - EventActivityCodeUserGroupDelete EventActivityCode = "user.group.delete" - EventActivityCodeUserInvite EventActivityCode = "user.invite" - EventActivityCodeUserJoin EventActivityCode = "user.join" - EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add" - EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete" - EventActivityCodeUserPeerLogin EventActivityCode = "user.peer.login" - EventActivityCodeUserRoleUpdate EventActivityCode = "user.role.update" - EventActivityCodeUserUnblock EventActivityCode = "user.unblock" + EventActivityCodeAccountCreate EventActivityCode = "account.create" + EventActivityCodeAccountDelete EventActivityCode = "account.delete" + EventActivityCodeAccountDnsDomainUpdate EventActivityCode = "account.dns.domain.update" + EventActivityCodeAccountNetworkRangeUpdate EventActivityCode = "account.network.range.update" + EventActivityCodeAccountPeerInactivityExpirationDisable EventActivityCode = "account.peer.inactivity.expiration.disable" + EventActivityCodeAccountPeerInactivityExpirationEnable EventActivityCode = "account.peer.inactivity.expiration.enable" + EventActivityCodeAccountPeerInactivityExpirationUpdate EventActivityCode = "account.peer.inactivity.expiration.update" + EventActivityCodeAccountSettingGroupPropagationDisable EventActivityCode = "account.setting.group.propagation.disable" + EventActivityCodeAccountSettingGroupPropagationEnable EventActivityCode = "account.setting.group.propagation.enable" + EventActivityCodeAccountSettingLazyConnectionDisable EventActivityCode = "account.setting.lazy.connection.disable" + EventActivityCodeAccountSettingLazyConnectionEnable EventActivityCode = "account.setting.lazy.connection.enable" + EventActivityCodeAccountSettingPeerApprovalDisable EventActivityCode = "account.setting.peer.approval.disable" + EventActivityCodeAccountSettingPeerApprovalEnable EventActivityCode = "account.setting.peer.approval.enable" + EventActivityCodeAccountSettingPeerLoginExpirationDisable EventActivityCode = "account.setting.peer.login.expiration.disable" + EventActivityCodeAccountSettingPeerLoginExpirationEnable EventActivityCode = "account.setting.peer.login.expiration.enable" + EventActivityCodeAccountSettingPeerLoginExpirationUpdate EventActivityCode = "account.setting.peer.login.expiration.update" + EventActivityCodeAccountSettingRoutingPeerDnsResolutionDisable EventActivityCode = "account.setting.routing.peer.dns.resolution.disable" + EventActivityCodeAccountSettingRoutingPeerDnsResolutionEnable EventActivityCode = "account.setting.routing.peer.dns.resolution.enable" + EventActivityCodeAccountSettingsAutoVersionUpdate EventActivityCode = "account.settings.auto.version.update" + EventActivityCodeDashboardLogin EventActivityCode = "dashboard.login" + EventActivityCodeDnsSettingDisabledManagementGroupAdd EventActivityCode = "dns.setting.disabled.management.group.add" + EventActivityCodeDnsSettingDisabledManagementGroupDelete EventActivityCode = "dns.setting.disabled.management.group.delete" + EventActivityCodeDnsZoneCreate EventActivityCode = "dns.zone.create" + EventActivityCodeDnsZoneDelete EventActivityCode = "dns.zone.delete" + EventActivityCodeDnsZoneRecordCreate EventActivityCode = "dns.zone.record.create" + EventActivityCodeDnsZoneRecordDelete EventActivityCode = "dns.zone.record.delete" + EventActivityCodeDnsZoneRecordUpdate EventActivityCode = "dns.zone.record.update" + EventActivityCodeDnsZoneUpdate EventActivityCode = "dns.zone.update" + EventActivityCodeGroupAdd EventActivityCode = "group.add" + EventActivityCodeGroupDelete EventActivityCode = "group.delete" + EventActivityCodeGroupUpdate EventActivityCode = "group.update" + EventActivityCodeIdentityproviderCreate EventActivityCode = "identityprovider.create" + EventActivityCodeIdentityproviderDelete EventActivityCode = "identityprovider.delete" + EventActivityCodeIdentityproviderUpdate EventActivityCode = "identityprovider.update" + EventActivityCodeIntegrationCreate EventActivityCode = "integration.create" + EventActivityCodeIntegrationDelete EventActivityCode = "integration.delete" + EventActivityCodeIntegrationUpdate EventActivityCode = "integration.update" + EventActivityCodeNameserverGroupAdd EventActivityCode = "nameserver.group.add" + EventActivityCodeNameserverGroupDelete EventActivityCode = "nameserver.group.delete" + EventActivityCodeNameserverGroupUpdate EventActivityCode = "nameserver.group.update" + EventActivityCodeNetworkCreate EventActivityCode = "network.create" + EventActivityCodeNetworkDelete EventActivityCode = "network.delete" + EventActivityCodeNetworkResourceCreate EventActivityCode = "network.resource.create" + EventActivityCodeNetworkResourceDelete EventActivityCode = "network.resource.delete" + EventActivityCodeNetworkResourceUpdate EventActivityCode = "network.resource.update" + EventActivityCodeNetworkRouterCreate EventActivityCode = "network.router.create" + EventActivityCodeNetworkRouterDelete EventActivityCode = "network.router.delete" + EventActivityCodeNetworkRouterUpdate EventActivityCode = "network.router.update" + EventActivityCodeNetworkUpdate EventActivityCode = "network.update" + EventActivityCodePeerApprovalRevoke EventActivityCode = "peer.approval.revoke" + EventActivityCodePeerApprove EventActivityCode = "peer.approve" + EventActivityCodePeerGroupAdd EventActivityCode = "peer.group.add" + EventActivityCodePeerGroupDelete EventActivityCode = "peer.group.delete" + EventActivityCodePeerInactivityExpirationDisable EventActivityCode = "peer.inactivity.expiration.disable" + EventActivityCodePeerInactivityExpirationEnable EventActivityCode = "peer.inactivity.expiration.enable" + EventActivityCodePeerIpUpdate EventActivityCode = "peer.ip.update" + EventActivityCodePeerJobCreate EventActivityCode = "peer.job.create" + EventActivityCodePeerLoginExpirationDisable EventActivityCode = "peer.login.expiration.disable" + EventActivityCodePeerLoginExpirationEnable EventActivityCode = "peer.login.expiration.enable" + EventActivityCodePeerLoginExpire EventActivityCode = "peer.login.expire" + EventActivityCodePeerRename EventActivityCode = "peer.rename" + EventActivityCodePeerSetupkeyAdd EventActivityCode = "peer.setupkey.add" + EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable" + EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable" + EventActivityCodePeerUserAdd EventActivityCode = "peer.user.add" + EventActivityCodePersonalAccessTokenCreate EventActivityCode = "personal.access.token.create" + EventActivityCodePersonalAccessTokenDelete EventActivityCode = "personal.access.token.delete" + EventActivityCodePolicyAdd EventActivityCode = "policy.add" + EventActivityCodePolicyDelete EventActivityCode = "policy.delete" + EventActivityCodePolicyUpdate EventActivityCode = "policy.update" + EventActivityCodePostureCheckCreate EventActivityCode = "posture.check.create" + EventActivityCodePostureCheckDelete EventActivityCode = "posture.check.delete" + EventActivityCodePostureCheckUpdate EventActivityCode = "posture.check.update" + EventActivityCodeResourceGroupAdd EventActivityCode = "resource.group.add" + EventActivityCodeResourceGroupDelete EventActivityCode = "resource.group.delete" + EventActivityCodeRouteAdd EventActivityCode = "route.add" + EventActivityCodeRouteDelete EventActivityCode = "route.delete" + EventActivityCodeRouteUpdate EventActivityCode = "route.update" + EventActivityCodeRuleAdd EventActivityCode = "rule.add" + EventActivityCodeRuleDelete EventActivityCode = "rule.delete" + EventActivityCodeRuleUpdate EventActivityCode = "rule.update" + EventActivityCodeServiceCreate EventActivityCode = "service.create" + EventActivityCodeServiceDelete EventActivityCode = "service.delete" + EventActivityCodeServiceUpdate EventActivityCode = "service.update" + EventActivityCodeServiceUserCreate EventActivityCode = "service.user.create" + EventActivityCodeServiceUserDelete EventActivityCode = "service.user.delete" + EventActivityCodeSetupkeyAdd EventActivityCode = "setupkey.add" + EventActivityCodeSetupkeyDelete EventActivityCode = "setupkey.delete" + EventActivityCodeSetupkeyGroupAdd EventActivityCode = "setupkey.group.add" + EventActivityCodeSetupkeyGroupDelete EventActivityCode = "setupkey.group.delete" + EventActivityCodeSetupkeyOveruse EventActivityCode = "setupkey.overuse" + EventActivityCodeSetupkeyRevoke EventActivityCode = "setupkey.revoke" + EventActivityCodeSetupkeyUpdate EventActivityCode = "setupkey.update" + EventActivityCodeTransferredOwnerRole EventActivityCode = "transferred.owner.role" + EventActivityCodeUserApprove EventActivityCode = "user.approve" + EventActivityCodeUserBlock EventActivityCode = "user.block" + EventActivityCodeUserCreate EventActivityCode = "user.create" + EventActivityCodeUserDelete EventActivityCode = "user.delete" + EventActivityCodeUserGroupAdd EventActivityCode = "user.group.add" + EventActivityCodeUserGroupDelete EventActivityCode = "user.group.delete" + EventActivityCodeUserInvite EventActivityCode = "user.invite" + EventActivityCodeUserInviteLinkAccept EventActivityCode = "user.invite.link.accept" + EventActivityCodeUserInviteLinkCreate EventActivityCode = "user.invite.link.create" + EventActivityCodeUserInviteLinkDelete EventActivityCode = "user.invite.link.delete" + EventActivityCodeUserInviteLinkRegenerate EventActivityCode = "user.invite.link.regenerate" + EventActivityCodeUserJoin EventActivityCode = "user.join" + EventActivityCodeUserPasswordChange EventActivityCode = "user.password.change" + EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete" + EventActivityCodeUserPeerLogin EventActivityCode = "user.peer.login" + EventActivityCodeUserReject EventActivityCode = "user.reject" + EventActivityCodeUserRoleUpdate EventActivityCode = "user.role.update" + EventActivityCodeUserUnblock EventActivityCode = "user.unblock" ) // Defines values for GeoLocationCheckAction. @@ -125,6 +191,13 @@ const ( IngressPortAllocationRequestPortRangeProtocolUdp IngressPortAllocationRequestPortRangeProtocol = "udp" ) +// Defines values for JobResponseStatus. +const ( + JobResponseStatusFailed JobResponseStatus = "failed" + JobResponseStatusPending JobResponseStatus = "pending" + JobResponseStatusSucceeded JobResponseStatus = "succeeded" +) + // Defines values for NameserverNsType. const ( NameserverNsTypeUdp NameserverNsType = "udp" @@ -196,14 +269,6 @@ const ( ResourceTypeSubnet ResourceType = "subnet" ) -// Defines values for ReverseProxyAuthConfigType. -const ( - ReverseProxyAuthConfigTypeBearer ReverseProxyAuthConfigType = "bearer" - ReverseProxyAuthConfigTypeLink ReverseProxyAuthConfigType = "link" - ReverseProxyAuthConfigTypePassword ReverseProxyAuthConfigType = "password" - ReverseProxyAuthConfigTypePin ReverseProxyAuthConfigType = "pin" -) - // Defines values for ReverseProxyDomainType. const ( ReverseProxyDomainTypeCustom ReverseProxyDomainType = "custom" @@ -229,6 +294,11 @@ const ( UserStatusInvited UserStatus = "invited" ) +// Defines values for WorkloadType. +const ( + WorkloadTypeBundle WorkloadType = "bundle" +) + // Defines values for GetApiEventsNetworkTrafficParamsType. const ( GetApiEventsNetworkTrafficParamsTypeTYPEDROP GetApiEventsNetworkTrafficParamsType = "TYPE_DROP" @@ -406,6 +476,47 @@ type BearerAuthConfig struct { Enabled bool `json:"enabled"` } +// BundleParameters These parameters control what gets included in the bundle and how it is processed. +type BundleParameters struct { + // Anonymize Whether sensitive data should be anonymized in the bundle. + Anonymize bool `json:"anonymize"` + + // BundleFor Whether to generate a bundle for the given timeframe. + BundleFor bool `json:"bundle_for"` + + // BundleForTime Time period in minutes for which to generate the bundle. + BundleForTime int `json:"bundle_for_time"` + + // LogFileCount Maximum number of log files to include in the bundle. + LogFileCount int `json:"log_file_count"` +} + +// BundleResult defines model for BundleResult. +type BundleResult struct { + UploadKey *string `json:"upload_key"` +} + +// BundleWorkloadRequest defines model for BundleWorkloadRequest. +type BundleWorkloadRequest struct { + // Parameters These parameters control what gets included in the bundle and how it is processed. + Parameters BundleParameters `json:"parameters"` + + // Type Identifies the type of workload the job will execute. + // Currently only `"bundle"` is supported. + Type WorkloadType `json:"type"` +} + +// BundleWorkloadResponse defines model for BundleWorkloadResponse. +type BundleWorkloadResponse struct { + // Parameters These parameters control what gets included in the bundle and how it is processed. + Parameters BundleParameters `json:"parameters"` + Result BundleResult `json:"result"` + + // Type Identifies the type of workload the job will execute. + // Currently only `"bundle"` is supported. + Type WorkloadType `json:"type"` +} + // Checks List of objects that perform the actual checks type Checks struct { // GeoLocationCheck Posture check for geo location @@ -793,6 +904,40 @@ type InstanceStatus struct { SetupRequired bool `json:"setup_required"` } +// InstanceVersionInfo Version information for NetBird components +type InstanceVersionInfo struct { + // DashboardAvailableVersion The latest available version of the dashboard (from GitHub releases) + DashboardAvailableVersion *string `json:"dashboard_available_version,omitempty"` + + // ManagementAvailableVersion The latest available version of the management server (from GitHub releases) + ManagementAvailableVersion *string `json:"management_available_version,omitempty"` + + // ManagementCurrentVersion The current running version of the management server + ManagementCurrentVersion string `json:"management_current_version"` + + // ManagementUpdateAvailable Indicates if a newer management version is available + ManagementUpdateAvailable bool `json:"management_update_available"` +} + +// JobRequest defines model for JobRequest. +type JobRequest struct { + Workload WorkloadRequest `json:"workload"` +} + +// JobResponse defines model for JobResponse. +type JobResponse struct { + CompletedAt *time.Time `json:"completed_at"` + CreatedAt time.Time `json:"created_at"` + FailedReason *string `json:"failed_reason"` + Id string `json:"id"` + Status JobResponseStatus `json:"status"` + TriggeredBy string `json:"triggered_by"` + Workload WorkloadResponse `json:"workload"` +} + +// JobResponseStatus defines model for JobResponse.Status. +type JobResponseStatus string + // LinkAuthConfig defines model for LinkAuthConfig. type LinkAuthConfig struct { // Enabled Whether link auth is enabled @@ -1187,6 +1332,15 @@ type PasswordAuthConfig struct { Password string `json:"password"` } +// PasswordChangeRequest defines model for PasswordChangeRequest. +type PasswordChangeRequest struct { + // NewPassword The new password to set + NewPassword string `json:"new_password"` + + // OldPassword The current password + OldPassword string `json:"old_password"` +} + // Peer defines model for Peer. type Peer struct { // ApprovalRequired (Cloud only) Indicates whether peer needs approval @@ -1774,14 +1928,8 @@ type ReverseProxyAuthConfig struct { LinkAuth *LinkAuthConfig `json:"link_auth,omitempty"` PasswordAuth *PasswordAuthConfig `json:"password_auth,omitempty"` PinAuth *PINAuthConfig `json:"pin_auth,omitempty"` - - // Type Authentication type - Type ReverseProxyAuthConfigType `json:"type"` } -// ReverseProxyAuthConfigType Authentication type -type ReverseProxyAuthConfigType string - // ReverseProxyDomain defines model for ReverseProxyDomain. type ReverseProxyDomain struct { // Domain Domain name @@ -2190,6 +2338,99 @@ type UserCreateRequest struct { Role string `json:"role"` } +// UserInvite A user invite +type UserInvite struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // CreatedAt Invite creation time + CreatedAt time.Time `json:"created_at"` + + // Email User's email address + Email string `json:"email"` + + // Expired Whether the invite has expired + Expired bool `json:"expired"` + + // ExpiresAt Invite expiration time + ExpiresAt time.Time `json:"expires_at"` + + // Id Invite ID + Id string `json:"id"` + + // InviteToken The invite link to be shared with the user. Only returned when the invite is created or regenerated. + InviteToken *string `json:"invite_token,omitempty"` + + // Name User's full name + Name string `json:"name"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// UserInviteAcceptRequest Request to accept an invite and set password +type UserInviteAcceptRequest struct { + // Password The password the user wants to set. Must be at least 8 characters long and contain at least one uppercase letter, one digit, and one special character (any character that is not a letter or digit, including spaces). + Password string `json:"password"` +} + +// UserInviteAcceptResponse Response after accepting an invite +type UserInviteAcceptResponse struct { + // Success Whether the invite was accepted successfully + Success bool `json:"success"` +} + +// UserInviteCreateRequest Request to create a user invite link +type UserInviteCreateRequest struct { + // AutoGroups Group IDs to auto-assign to peers registered by this user + AutoGroups []string `json:"auto_groups"` + + // Email User's email address + Email string `json:"email"` + + // ExpiresIn Invite expiration time in seconds (default 72 hours) + ExpiresIn *int `json:"expires_in,omitempty"` + + // Name User's full name + Name string `json:"name"` + + // Role User's NetBird account role + Role string `json:"role"` +} + +// UserInviteInfo Public information about an invite +type UserInviteInfo struct { + // Email User's email address + Email string `json:"email"` + + // ExpiresAt Invite expiration time + ExpiresAt time.Time `json:"expires_at"` + + // InvitedBy Name of the user who sent the invite + InvitedBy string `json:"invited_by"` + + // Name User's full name + Name string `json:"name"` + + // Valid Whether the invite is still valid (not expired) + Valid bool `json:"valid"` +} + +// UserInviteRegenerateRequest Request to regenerate an invite link +type UserInviteRegenerateRequest struct { + // ExpiresIn Invite expiration time in seconds (default 72 hours) + ExpiresIn *int `json:"expires_in,omitempty"` +} + +// UserInviteRegenerateResponse Response after regenerating an invite +type UserInviteRegenerateResponse struct { + // InviteExpiresAt New invite expiration time + InviteExpiresAt time.Time `json:"invite_expires_at"` + + // InviteToken The new invite token + InviteToken string `json:"invite_token"` +} + // UserPermissions defines model for UserPermissions. type UserPermissions struct { // IsRestricted Indicates whether this User's Peers view is restricted @@ -2209,6 +2450,20 @@ type UserRequest struct { Role string `json:"role"` } +// WorkloadRequest defines model for WorkloadRequest. +type WorkloadRequest struct { + union json.RawMessage +} + +// WorkloadResponse defines model for WorkloadResponse. +type WorkloadResponse struct { + union json.RawMessage +} + +// WorkloadType Identifies the type of workload the job will execute. +// Currently only `"bundle"` is supported. +type WorkloadType string + // Zone defines model for Zone. type Zone struct { // DistributionGroups Group IDs that defines groups of peers that will resolve this zone @@ -2392,6 +2647,9 @@ type PostApiPeersPeerIdIngressPortsJSONRequestBody = IngressPortAllocationReques // PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody defines body for PutApiPeersPeerIdIngressPortsAllocationId for application/json ContentType. type PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody = IngressPortAllocationRequest +// PostApiPeersPeerIdJobsJSONRequestBody defines body for PostApiPeersPeerIdJobs for application/json ContentType. +type PostApiPeersPeerIdJobsJSONRequestBody = JobRequest + // PostApiPeersPeerIdTemporaryAccessJSONRequestBody defines body for PostApiPeersPeerIdTemporaryAccess for application/json ContentType. type PostApiPeersPeerIdTemporaryAccessJSONRequestBody = PeerTemporaryAccessRequest @@ -2434,8 +2692,138 @@ type PutApiSetupKeysKeyIdJSONRequestBody = SetupKeyRequest // PostApiUsersJSONRequestBody defines body for PostApiUsers for application/json ContentType. type PostApiUsersJSONRequestBody = UserCreateRequest +// PostApiUsersInvitesJSONRequestBody defines body for PostApiUsersInvites for application/json ContentType. +type PostApiUsersInvitesJSONRequestBody = UserInviteCreateRequest + +// PostApiUsersInvitesInviteIdRegenerateJSONRequestBody defines body for PostApiUsersInvitesInviteIdRegenerate for application/json ContentType. +type PostApiUsersInvitesInviteIdRegenerateJSONRequestBody = UserInviteRegenerateRequest + +// PostApiUsersInvitesTokenAcceptJSONRequestBody defines body for PostApiUsersInvitesTokenAccept for application/json ContentType. +type PostApiUsersInvitesTokenAcceptJSONRequestBody = UserInviteAcceptRequest + // PutApiUsersUserIdJSONRequestBody defines body for PutApiUsersUserId for application/json ContentType. type PutApiUsersUserIdJSONRequestBody = UserRequest +// PutApiUsersUserIdPasswordJSONRequestBody defines body for PutApiUsersUserIdPassword for application/json ContentType. +type PutApiUsersUserIdPasswordJSONRequestBody = PasswordChangeRequest + // PostApiUsersUserIdTokensJSONRequestBody defines body for PostApiUsersUserIdTokens for application/json ContentType. type PostApiUsersUserIdTokensJSONRequestBody = PersonalAccessTokenRequest + +// AsBundleWorkloadRequest returns the union data inside the WorkloadRequest as a BundleWorkloadRequest +func (t WorkloadRequest) AsBundleWorkloadRequest() (BundleWorkloadRequest, error) { + var body BundleWorkloadRequest + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBundleWorkloadRequest overwrites any union data inside the WorkloadRequest as the provided BundleWorkloadRequest +func (t *WorkloadRequest) FromBundleWorkloadRequest(v BundleWorkloadRequest) error { + v.Type = "bundle" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBundleWorkloadRequest performs a merge with any union data inside the WorkloadRequest, using the provided BundleWorkloadRequest +func (t *WorkloadRequest) MergeBundleWorkloadRequest(v BundleWorkloadRequest) error { + v.Type = "bundle" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t WorkloadRequest) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t WorkloadRequest) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bundle": + return t.AsBundleWorkloadRequest() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t WorkloadRequest) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *WorkloadRequest) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsBundleWorkloadResponse returns the union data inside the WorkloadResponse as a BundleWorkloadResponse +func (t WorkloadResponse) AsBundleWorkloadResponse() (BundleWorkloadResponse, error) { + var body BundleWorkloadResponse + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBundleWorkloadResponse overwrites any union data inside the WorkloadResponse as the provided BundleWorkloadResponse +func (t *WorkloadResponse) FromBundleWorkloadResponse(v BundleWorkloadResponse) error { + v.Type = "bundle" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBundleWorkloadResponse performs a merge with any union data inside the WorkloadResponse, using the provided BundleWorkloadResponse +func (t *WorkloadResponse) MergeBundleWorkloadResponse(v BundleWorkloadResponse) error { + v.Type = "bundle" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t WorkloadResponse) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t WorkloadResponse) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "bundle": + return t.AsBundleWorkloadResponse() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t WorkloadResponse) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *WorkloadResponse) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +}