move to reverse proxy and update api

This commit is contained in:
pascal
2026-01-27 15:34:01 +01:00
parent 4b89427447
commit 8dd22f3a4f
21 changed files with 1101 additions and 733 deletions
@@ -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"
@@ -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 {
@@ -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
}
@@ -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{})
}
@@ -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
}
@@ -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}
}
@@ -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
}
@@ -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{})
}
@@ -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
}
@@ -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}
}
+2 -2
View File
@@ -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")
+5 -5
View File
@@ -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())
})
}
+46 -45
View File
@@ -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
})
+6 -6
View File
@@ -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
+4 -4
View File
@@ -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 {
+35 -35
View File
@@ -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
}
+7 -7
View File
@@ -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 (