diff --git a/cmd/api/main.go b/cmd/api/main.go index e7a09423..a3f181dc 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -5,9 +5,11 @@ import ( "flag" "fmt" "io" + "net/url" "os" "os/signal" "path" + "path/filepath" "syscall" "time" @@ -35,13 +37,14 @@ import ( const serviceName = "vxapi" type Config struct { - Debug bool `config:"debug"` - Develop bool `config:"is_develop"` - Log LogConfig - DB DBConfig - Tracing TracingConfig - PublicAPI PublicAPIConfig - EventWorker EventWorkerConfig + Debug bool `config:"debug"` + Develop bool `config:"is_develop"` + Log LogConfig + DB DBConfig + Tracing TracingConfig + PublicAPI PublicAPIConfig + EventWorker EventWorkerConfig + ServerEventWorker ServerEventWorkerConfig } type LogConfig struct { @@ -51,11 +54,12 @@ type LogConfig struct { } type DBConfig struct { - User string `config:"db_user,required"` - Pass string `config:"db_pass,required"` - Name string `config:"db_name,required"` - Host string `config:"db_host,required"` - Port int `config:"db_port,required"` + User string `config:"db_user,required"` + Pass string `config:"db_pass,required"` + Name string `config:"db_name,required"` + Host string `config:"db_host,required"` + Port int `config:"db_port,required"` + MigrationDir string `config:"migration_dir"` } type PublicAPIConfig struct { @@ -65,6 +69,10 @@ type PublicAPIConfig struct { CertFile string `config:"api_ssl_crt"` KeyFile string `config:"api_ssl_key"` GracefulTimeout time.Duration `config:"public_api_graceful_timeout"` + StaticPath string `config:"api_static_path"` + StaticURL string `config:"api_static_url"` + TemplatesDir string `config:"templates_dir"` + CertsPath string `config:"certs_path"` } type TracingConfig struct { @@ -75,6 +83,10 @@ type EventWorkerConfig struct { PollInterval time.Duration `config:"event_worker_poll_interval"` } +type ServerEventWorkerConfig struct { + KeepDays int `config:"retention_events"` +} + func defaultConfig() Config { return Config{ Log: LogConfig{ @@ -85,14 +97,23 @@ func defaultConfig() Config { Tracing: TracingConfig{ Addr: "otel.local:8148", }, + DB: DBConfig{ + MigrationDir: "db/api/migrations", + }, PublicAPI: PublicAPIConfig{ Addr: ":8080", AddrHTTPS: ":8443", GracefulTimeout: time.Minute, + TemplatesDir: "templates", + StaticPath: "static", + CertsPath: filepath.Join("security", "certs", "api"), }, EventWorker: EventWorkerConfig{ PollInterval: 30 * time.Second, }, + ServerEventWorker: ServerEventWorkerConfig{ + KeepDays: 7, + }, } } @@ -160,12 +181,7 @@ func main() { logrus.WithError(err).Error("could not connect to database") return } - - migrationDir := "db/api/migrations" - if dir, ok := os.LookupEnv("MIGRATION_DIR"); ok { - migrationDir = dir - } - if err = db.Migrate(migrationDir); err != nil { + if err = db.Migrate(cfg.DB.MigrationDir); err != nil { logrus.WithError(err).Error("could not apply migrations") return } @@ -263,11 +279,25 @@ func main() { go worker.SyncModulesToPolicies(ctx, dbWithORM) // run worker to synchronize events retention policy to all instance DB - go worker.SyncRetentionEvents(ctx, dbWithORM) + go worker.SyncRetentionEvents(ctx, dbWithORM, cfg.ServerEventWorker.KeepDays) + uiStaticURL, err := url.Parse(cfg.PublicAPI.StaticURL) + if err != nil { + logrus.WithError(err).Error("error on parsing URL to redirect requests to the UI static") + return + } userActionWriter := useraction.NewLogWriter() router := server.NewRouter( + server.RouterConfig{ + BaseURL: "/api/v1", + Debug: cfg.Debug, + UseSSL: cfg.PublicAPI.UseSSL, + StaticPath: cfg.PublicAPI.StaticPath, + StaticURL: uiStaticURL, + TemplatesDir: cfg.PublicAPI.TemplatesDir, + CertsPath: cfg.PublicAPI.CertsPath, + }, dbWithORM, exchanger, userActionWriter, @@ -275,15 +305,15 @@ func main() { s3ConnectionStorage, ) - srvg, ctx := errgroup.WithContext(ctx) - srvg.Go(func() error { + group, ctx := errgroup.WithContext(ctx) + group.Go(func() error { return server.Server{ Addr: cfg.PublicAPI.Addr, GracefulTimeout: cfg.PublicAPI.GracefulTimeout, }.ListenAndServe(ctx, router) }) if cfg.PublicAPI.UseSSL { - srvg.Go(func() error { + group.Go(func() error { return server.Server{ Addr: cfg.PublicAPI.AddrHTTPS, CertFile: cfg.PublicAPI.CertFile, @@ -292,7 +322,7 @@ func main() { }.ListenAndServeTLS(ctx, router) }) } - if err = srvg.Wait(); err != nil { - logrus.WithError(err).Error("failed to start server") + if err = group.Wait(); err != nil { + logrus.WithError(err).Error("could not start services") } } diff --git a/pkg/app/api/server/middleware.go b/pkg/app/api/server/middleware.go index a0311103..b131cf7f 100644 --- a/pkg/app/api/server/middleware.go +++ b/pkg/app/api/server/middleware.go @@ -17,14 +17,13 @@ import ( "soldr/pkg/app/api/server/context" "soldr/pkg/app/api/server/private" "soldr/pkg/app/api/server/response" - "soldr/pkg/app/api/utils" "soldr/pkg/app/api/utils/dbencryptor" ) -func authTokenProtoRequired() gin.HandlerFunc { +func authTokenProtoRequired(apiBaseURL string) gin.HandlerFunc { privInteractive := "vxapi.modules.interactive" connTypeRegexp := regexp.MustCompile( - fmt.Sprintf("%s/vxpws/(aggregate|browser|external)/.*", utils.PrefixPathAPI), + fmt.Sprintf("%s/vxpws/(aggregate|browser|external)/.*", apiBaseURL), ) return func(c *gin.Context) { if c.IsAborted() { diff --git a/pkg/app/api/server/private/modules.go b/pkg/app/api/server/private/modules.go index 31984521..2dc2bebb 100644 --- a/pkg/app/api/server/private/modules.go +++ b/pkg/app/api/server/private/modules.go @@ -580,7 +580,7 @@ func BuildModuleSConfig(module *models.ModuleS) (map[string][]byte, error) { return files, nil } -func LoadModuleSTemplate(mi *models.ModuleInfo) (Template, *models.ModuleS, error) { +func LoadModuleSTemplate(mi *models.ModuleInfo, templatesDir string) (Template, *models.ModuleS, error) { fs, err := storage.NewFS() if err != nil { return nil, nil, errors.New("failed initialize FS driver: " + err.Error()) @@ -589,10 +589,6 @@ func LoadModuleSTemplate(mi *models.ModuleInfo) (Template, *models.ModuleS, erro var module *models.ModuleS template := make(Template) loadModuleDir := func(dir string) (map[string][]byte, error) { - templatesDir := "templates" - if dir, ok := os.LookupEnv("TEMPLATES_DIR"); ok { - templatesDir = dir - } tpath := joinPath(templatesDir, mi.Template, dir) if fs.IsNotExist(tpath) { return nil, errors.New("template directory not found") @@ -1298,17 +1294,20 @@ type ModuleService struct { db *gorm.DB serverConnector *client.AgentServerClient userActionWriter useraction.Writer + templatesDir string } func NewModuleService( db *gorm.DB, serverConnector *client.AgentServerClient, userActionWriter useraction.Writer, + templatesDir string, ) *ModuleService { return &ModuleService{ db: db, serverConnector: serverConnector, userActionWriter: userActionWriter, + templatesDir: templatesDir, } } @@ -3050,7 +3049,7 @@ func (s *ModuleService) CreateModule(c *gin.Context) { info.System = false var err error - if template, module, err = LoadModuleSTemplate(&info); err != nil { + if template, module, err = LoadModuleSTemplate(&info, s.templatesDir); err != nil { logrus.WithError(err).Errorf("error loading module") response.Error(c, response.ErrCreateModuleLoadFail, err) return diff --git a/pkg/app/api/server/proto/proto.go b/pkg/app/api/server/proto/proto.go index 6116fb7a..ca32f7af 100644 --- a/pkg/app/api/server/proto/proto.go +++ b/pkg/app/api/server/proto/proto.go @@ -9,7 +9,6 @@ import ( "fmt" "net/http" "net/url" - "os" "path/filepath" "github.com/gin-gonic/gin" @@ -126,12 +125,9 @@ func doVXServerConnection( ctxConn *ctxVXConnection, agentInfo *protoagent.Information, ltacGetter vxcommonVM.LTACGetter, + certsPath string, ) (*socket, error) { - certsDir := filepath.Join("security", "certs", "api") - if dir, ok := os.LookupEnv("CERTS_PATH"); ok { - certsDir = dir - } - hardeningVM, packEncryptor, err := prepareVM(ctxConn, NewCertProvider(certsDir), ltacGetter) + hardeningVM, packEncryptor, err := prepareVM(ctxConn, NewCertProvider(certsPath), ltacGetter) if err != nil { return nil, fmt.Errorf("failed to prepare VM: %w", err) } @@ -208,7 +204,14 @@ func sendAuthResp(ctx context.Context, conn vxproto.IConnection, authRespMessage return nil } -func wsConnectToVXServer(c *gin.Context, connType vxproto.AgentType, sockID, sockType string, uaf useraction.Fields) { +func wsConnectToVXServer( + c *gin.Context, + connType vxproto.AgentType, + sockID string, + sockType string, + certsPath string, + uaf useraction.Fields, +) { var ( serverConn *socket sv *models.Service @@ -275,16 +278,15 @@ func wsConnectToVXServer(c *gin.Context, connType vxproto.AgentType, sockID, soc sv: sv, logger: logger, } - certsDir := filepath.Join("security", "certs", "api") - if dir, ok := os.LookupEnv("CERTS_PATH"); ok { - certsDir = dir - } + logger.WithField("auth_req", authReq).Debug("try doVXServerConnection") - if serverConn, err = doVXServerConnection( + serverConn, err = doVXServerConnection( ctxConn, agentInfo, - NewStore(filepath.Join(certsDir, sockType)), - ); err != nil { + NewStore(filepath.Join(certsPath, sockType)), + certsPath, + ) + if err != nil { clientConn.Close(c.Request.Context()) logger.WithError(err).Error("failed to initialize connection to server") uaf.FailReason = "failed to initialize connection to server" @@ -348,17 +350,20 @@ type ProtoService struct { db *gorm.DB serverConnector *client.AgentServerClient userActionWriter useraction.Writer + certsPath string } func NewProtoService( db *gorm.DB, serverConnector *client.AgentServerClient, userActionWriter useraction.Writer, + certsPath string, ) *ProtoService { return &ProtoService{ db: db, serverConnector: serverConnector, userActionWriter: userActionWriter, + certsPath: certsPath, } } @@ -407,7 +412,7 @@ func (s *ProtoService) AggregateWSConnect(c *gin.Context) { return } - wsConnectToVXServer(c, vxproto.Aggregate, sockID, sockType, uaf) + wsConnectToVXServer(c, vxproto.Aggregate, sockID, sockType, s.certsPath, uaf) } func (s *ProtoService) BrowserWSConnect(c *gin.Context) { @@ -456,7 +461,7 @@ func (s *ProtoService) BrowserWSConnect(c *gin.Context) { return } - wsConnectToVXServer(c, vxproto.Browser, sockID, sockType, uaf) + wsConnectToVXServer(c, vxproto.Browser, sockID, sockType, s.certsPath, uaf) } func (s *ProtoService) ExternalWSConnect(c *gin.Context) { @@ -503,5 +508,5 @@ func (s *ProtoService) ExternalWSConnect(c *gin.Context) { return } - wsConnectToVXServer(c, vxproto.External, sockID, sockType, uaf) + wsConnectToVXServer(c, vxproto.External, sockID, sockType, s.certsPath, uaf) } diff --git a/pkg/app/api/server/public/auth.go b/pkg/app/api/server/public/auth.go index a50d5e07..7bae483a 100644 --- a/pkg/app/api/server/public/auth.go +++ b/pkg/app/api/server/public/auth.go @@ -20,6 +20,24 @@ import ( "soldr/pkg/app/api/utils" ) +type AuthServiceConfig struct { + APIBaseURL string + SessionTimeout int + SecureCookie bool +} + +type AuthService struct { + cfg AuthServiceConfig + db *gorm.DB +} + +func NewAuthService(cfg AuthServiceConfig, db *gorm.DB) *AuthService { + return &AuthService{ + cfg: cfg, + db: db, + } +} + // AuthLogin is function to login user in the system // @Summary Login user into system // @Tags Public @@ -32,17 +50,9 @@ import ( // @Failure 403 {object} utils.errorResp "login not permitted" // @Failure 500 {object} utils.errorResp "internal error on login" // @Router /auth/login [post] -func AuthLogin(c *gin.Context) { - var ( - data models.Login - err error - gDB *gorm.DB - privs []string - service *models.Service - user models.UserPassword - ) - - if err = c.ShouldBindJSON(&data); err != nil || data.Valid() != nil { +func (s *AuthService) AuthLogin(c *gin.Context) { + var data models.Login + if err := c.ShouldBindJSON(&data); err != nil || data.Valid() != nil { if err == nil { err = data.Valid() } @@ -51,12 +61,8 @@ func AuthLogin(c *gin.Context) { return } - if gDB = utils.GetGormDB(c, "gDB"); gDB == nil { - response.Error(c, response.ErrInternalDBNotFound, nil) - return - } - - if err = gDB.Take(&user, "(mail = ? OR name = ?) AND password IS NOT NULL", data.Mail, data.Mail).Error; err != nil { + var user models.UserPassword + if err := s.db.Take(&user, "(mail = ? OR name = ?) AND password IS NOT NULL", data.Mail, data.Mail).Error; err != nil { logrus.WithError(err).Errorf("error getting user by mail '%s'", data.Mail) response.Error(c, response.ErrAuthInvalidCredentials, err) return @@ -70,7 +76,7 @@ func AuthLogin(c *gin.Context) { return } - if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(data.Password)); err != nil { + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(data.Password)); err != nil { logrus.Errorf("error matching user input password") response.Error(c, response.ErrAuthInvalidCredentials, err) return @@ -82,7 +88,8 @@ func AuthLogin(c *gin.Context) { return } - if service, err = getService(c, gDB, data.Service, &user.User); err != nil { + service, err := getService(c, s.db, data.Service, &user.User) + if err != nil { logrus.WithError(err).Errorf("error loading service data by hash '%s'", data.Service) response.Error(c, response.ErrAuthInvalidServiceData, err) return @@ -92,7 +99,11 @@ func AuthLogin(c *gin.Context) { return } - if err = gDB.Table("privileges").Where("role_id = ?", user.RoleID).Pluck("name", &privs).Error; err != nil { + var privs []string + err = s.db.Table("privileges"). + Where("role_id = ?", user.RoleID). + Pluck("name", &privs).Error + if err != nil { logrus.WithError(err).Errorf("error getting user privileges list '%s'", user.Hash) response.Error(c, response.ErrAuthInvalidServiceData, err) return @@ -105,7 +116,7 @@ func AuthLogin(c *gin.Context) { return } - expires := utils.DefaultSessionTimeout + expires := s.cfg.SessionTimeout session := sessions.Default(c) session.Set("uid", user.ID) session.Set("rid", user.RoleID) @@ -119,8 +130,8 @@ func AuthLogin(c *gin.Context) { session.Set("uname", user.Name) session.Options(sessions.Options{ HttpOnly: true, - Secure: utils.IsUseSSL(), - Path: utils.PrefixPathAPI, + Secure: s.cfg.SecureCookie, + Path: s.cfg.APIBaseURL, MaxAge: expires, }) session.Save() @@ -148,7 +159,7 @@ func AuthLogin(c *gin.Context) { // @Param return_uri query string false "URI to redirect user there after logout" default(/) // @Success 307 "redirect to input return_uri path" // @Router /auth/logout [get] -func AuthLogout(c *gin.Context) { +func (s *AuthService) AuthLogout(c *gin.Context) { returnURI := "/" if returnURL, err := url.Parse(c.Query("return_uri")); err == nil { if uri := returnURL.RequestURI(); uri != "" { @@ -169,7 +180,17 @@ func AuthLogout(c *gin.Context) { }). Info("user made successful logout") - resetSession(c) + now := time.Now().Add(-1 * time.Second) + session.Set("gtm", now.Unix()) + session.Set("exp", now.Unix()) + session.Options(sessions.Options{ + HttpOnly: true, + Secure: s.cfg.SecureCookie, + Path: s.cfg.APIBaseURL, + MaxAge: -1, + }) + session.Save() + http.Redirect(c.Writer, c.Request, returnURI, http.StatusTemporaryRedirect) } @@ -184,14 +205,7 @@ func AuthLogout(c *gin.Context) { // @Failure 403 {object} utils.errorResp "switch service not permitted" // @Failure 500 {object} utils.errorResp "internal error on switch service" // @Router /auth/switch-service [post] -func AuthSwitchService(c *gin.Context) { - var ( - err error - gDB *gorm.DB - service models.Service - tenant models.Tenant - ) - +func (s *AuthService) AuthSwitchService(c *gin.Context) { serviceHash := c.PostForm("service") if err := models.GetValidator().Var(serviceHash, "len=32,hexadecimal,lowercase,required"); err != nil { logrus.WithError(err).Errorf("error validating input data") @@ -199,30 +213,31 @@ func AuthSwitchService(c *gin.Context) { return } - if gDB = utils.GetGormDB(c, "gDB"); gDB == nil { - response.Error(c, response.ErrInternalDBNotFound, nil) - return - } - tid, _ := context.GetUint64(c, "tid") exp, _ := context.GetInt64(c, "exp") expires := int(time.Until(time.Unix(exp, 0)) / time.Second) - if err = gDB.Take(&tenant, "id = ?", tid).Error; err != nil { + var tenant models.Tenant + err := s.db.Take(&tenant, "id = ?", tid).Error + if err != nil { logrus.WithError(err).Errorf("error loading tenant data '%d'", tid) response.Error(c, response.ErrAuthInvalidTenantData, err) return - } else if err = tenant.Valid(); err != nil { + } + if err = tenant.Valid(); err != nil { logrus.WithError(err).Errorf("error validating tenant data '%d:%s'", tid, tenant.Hash) response.Error(c, response.ErrAuthInvalidTenantData, err) return } - if err = gDB.Take(&service, "hash LIKE ? AND status = 'active'", serviceHash).Error; err != nil { + var service models.Service + err = s.db.Take(&service, "hash LIKE ? AND status = 'active'", serviceHash).Error + if err != nil { logrus.WithError(err).Errorf("error loading service data '%s'", serviceHash) response.Error(c, response.ErrAuthInvalidServiceData, err) return - } else if err = service.Valid(); err != nil { + } + if err = service.Valid(); err != nil { logrus.WithError(err).Errorf("error validating service data '%s'", serviceHash) response.Error(c, response.ErrAuthInvalidServiceData, err) return @@ -233,8 +248,8 @@ func AuthSwitchService(c *gin.Context) { session.Set("svc", service.Hash) session.Options(sessions.Options{ HttpOnly: true, - Secure: utils.IsUseSSL(), - Path: utils.PrefixPathAPI, + Secure: s.cfg.SecureCookie, + Path: s.cfg.APIBaseURL, MaxAge: expires, }) session.Save() @@ -256,20 +271,6 @@ func AuthSwitchService(c *gin.Context) { response.Success(c, http.StatusOK, service) } -func resetSession(c *gin.Context) { - now := time.Now().Add(-1 * time.Second) - session := sessions.Default(c) - session.Set("gtm", now.Unix()) - session.Set("exp", now.Unix()) - session.Options(sessions.Options{ - HttpOnly: true, - Secure: utils.IsUseSSL(), - Path: utils.PrefixPathAPI, - MaxAge: -1, - }) - session.Save() -} - func getService(c *gin.Context, gDB *gorm.DB, hash string, user *models.User) (*models.Service, error) { var service models.Service @@ -287,3 +288,41 @@ func getService(c *gin.Context, gDB *gorm.DB, hash string, user *models.User) (* return nil, fmt.Errorf("failed to get service '%s' from DB: %w", hash, err) } } + +func (s *AuthService) refreshCookie(c *gin.Context, resp *info, privs []string) error { + session := sessions.Default(c) + + expires := s.cfg.SessionTimeout + session.Set("prm", privs) + session.Set("gtm", time.Now().Unix()) + session.Set("exp", time.Now().Add(time.Duration(expires)*time.Second).Unix()) + resp.Privs = privs + + session.Set("uid", resp.User.ID) + session.Set("rid", resp.User.RoleID) + session.Set("tid", resp.User.TenantID) + session.Set("sid", session.Get("sid")) + session.Set("svc", session.Get("svc")) + session.Options(sessions.Options{ + HttpOnly: true, + Secure: s.cfg.SecureCookie, + Path: s.cfg.APIBaseURL, + MaxAge: expires, + }) + session.Save() + + logrus. + WithFields(logrus.Fields{ + "age": expires, + "uid": resp.User.ID, + "rid": resp.User.RoleID, + "tid": resp.User.TenantID, + "sid": session.Get("sid"), + "gtm": session.Get("gtm"), + "exp": session.Get("exp"), + "prm": session.Get("prm"), + }). + Infof("session was refreshed for '%s' '%s'", resp.User.Mail, resp.User.Name) + + return nil +} diff --git a/pkg/app/api/server/public/info.go b/pkg/app/api/server/public/info.go index bb7b2c88..98af4662 100644 --- a/pkg/app/api/server/public/info.go +++ b/pkg/app/api/server/public/info.go @@ -6,12 +6,10 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - "github.com/jinzhu/gorm" "github.com/sirupsen/logrus" "soldr/pkg/app/api/models" "soldr/pkg/app/api/server/response" - "soldr/pkg/app/api/utils" "soldr/pkg/version" ) @@ -26,44 +24,6 @@ type info struct { Services []*models.Service `json:"services"` } -func refreshCookie(c *gin.Context, resp *info, privs []string) error { - session := sessions.Default(c) - - expires := utils.DefaultSessionTimeout - session.Set("prm", privs) - session.Set("gtm", time.Now().Unix()) - session.Set("exp", time.Now().Add(time.Duration(expires)*time.Second).Unix()) - resp.Privs = privs - - session.Set("uid", resp.User.ID) - session.Set("rid", resp.User.RoleID) - session.Set("tid", resp.User.TenantID) - session.Set("sid", session.Get("sid")) - session.Set("svc", session.Get("svc")) - session.Options(sessions.Options{ - HttpOnly: true, - Secure: utils.IsUseSSL(), - Path: utils.PrefixPathAPI, - MaxAge: expires, - }) - session.Save() - - logrus. - WithFields(logrus.Fields{ - "age": expires, - "uid": resp.User.ID, - "rid": resp.User.RoleID, - "tid": resp.User.TenantID, - "sid": session.Get("sid"), - "gtm": session.Get("gtm"), - "exp": session.Get("exp"), - "prm": session.Get("prm"), - }). - Infof("session was refreshed for '%s' '%s'", resp.User.Mail, resp.User.Name) - - return nil -} - // Info is function to return settings and current information about system and config // @Summary Retrieve current user and system settings // @Tags Public @@ -74,22 +34,16 @@ func refreshCookie(c *gin.Context, resp *info, privs []string) error { // @Failure 404 {object} utils.errorResp "user not found" // @Failure 500 {object} utils.errorResp "internal error on getting information about system and config" // @Router /info [get] -func Info(c *gin.Context) { +func (s *AuthService) Info(c *gin.Context) { var ( err error expt int64 gtmt int64 - gDB *gorm.DB ok bool privs []string resp info ) - if gDB = utils.GetGormDB(c, "gDB"); gDB == nil { - response.Error(c, response.ErrInternalDBNotFound, nil) - return - } - nowt := time.Now().Unix() session := sessions.Default(c) uid := session.Get("uid") @@ -114,8 +68,9 @@ func Info(c *gin.Context) { resp.Privs = make([]string, 0) } else { resp.Type = "user" - err = gDB.Take(&resp.User, "id = ?", uid). - Related(&resp.Role).Related(&resp.Tenant).Error + err = s.db.Take(&resp.User, "id = ?", uid). + Related(&resp.Role). + Related(&resp.Tenant).Error if err != nil { response.Error(c, response.ErrInfoUserNotFound, err) return @@ -125,13 +80,13 @@ func Info(c *gin.Context) { return } - if err = gDB.Table("privileges").Where("role_id = ?", resp.User.RoleID).Pluck("name", &privs).Error; err != nil { + if err = s.db.Table("privileges").Where("role_id = ?", resp.User.RoleID).Pluck("name", &privs).Error; err != nil { logrus.WithError(err).Errorf("error getting user privileges list '%s'", resp.User.Hash) response.Error(c, response.ErrInfoInvalidUserData, err) return } - if err = gDB.Find(&resp.Services, "tenant_id = ?", resp.User.TenantID).Error; err != nil { + if err = s.db.Find(&resp.Services, "tenant_id = ?", resp.User.TenantID).Error; err != nil { logrus.WithError(err).Errorf("error getting user services list '%s'", resp.User.Hash) response.Error(c, response.ErrInfoInvalidServiceData, err) return @@ -150,10 +105,10 @@ func Info(c *gin.Context) { // check 5 minutes timeout to refresh current token var fiveMins int64 = 5 * 60 if nowt >= gtmt+fiveMins && c.Query("refresh_cookie") != "false" { - if err = refreshCookie(c, &resp, privs); err != nil { + if err = s.refreshCookie(c, &resp, privs); err != nil { logrus.WithError(err).Errorf("failed to refresh token") // raise error when there is elapsing last five minutes - if nowt >= gtmt+int64(utils.DefaultSessionTimeout)-fiveMins { + if nowt >= gtmt+int64(s.cfg.SessionTimeout)-fiveMins { response.Error(c, response.ErrInternal, err) return } diff --git a/pkg/app/api/server/router.go b/pkg/app/api/server/router.go index c3d0a1c1..b78a82fe 100644 --- a/pkg/app/api/server/router.go +++ b/pkg/app/api/server/router.go @@ -7,7 +7,6 @@ import ( "net/http" "net/http/httputil" "net/url" - "os" "path" "strings" @@ -31,6 +30,16 @@ import ( "soldr/pkg/app/api/utils" ) +type RouterConfig struct { + Debug bool + UseSSL bool + BaseURL string + StaticPath string + StaticURL *url.URL + TemplatesDir string + CertsPath string +} + // @title SOLDR Swagger API // @version 1.0 // @description Swagger API for VXControl SOLDR backend product. @@ -47,6 +56,7 @@ import ( // @BasePath /api/v1 func NewRouter( + cfg RouterConfig, db *gorm.DB, exchanger *srvevents.Exchanger, userActionWriter useraction.Writer, @@ -54,7 +64,7 @@ func NewRouter( s3Conns *mem.S3ConnectionStorage, ) *gin.Engine { gin.SetMode(gin.ReleaseMode) - if _, exists := os.LookupEnv("DEBUG"); exists { + if cfg.Debug { gin.SetMode(gin.DebugMode) } @@ -64,13 +74,8 @@ func NewRouter( cookieStore := cookie.NewStore(utils.MakeCookieStoreKey()) - staticPath := "./static" - if uiStaticPath, ok := os.LookupEnv("API_STATIC_PATH"); ok { - staticPath = uiStaticPath - } - index := func(c *gin.Context) { - data, err := ioutil.ReadFile(path.Join(staticPath, "/index.html")) + data, err := ioutil.ReadFile(path.Join(cfg.StaticPath, "/index.html")) if err != nil { logrus.WithError(err).Errorf("error loading index.html") return @@ -84,20 +89,16 @@ func NewRouter( router.Use(gin.Recovery()) router.Use(sessions.Sessions("auth", cookieStore)) - router.Static("/js", path.Join(staticPath, "js")) - router.Static("/css", path.Join(staticPath, "css")) - router.Static("/fonts", path.Join(staticPath, "fonts")) - router.Static("/images", path.Join(staticPath, "images")) + router.Static("/js", path.Join(cfg.StaticPath, "js")) + router.Static("/css", path.Join(cfg.StaticPath, "css")) + router.Static("/fonts", path.Join(cfg.StaticPath, "fonts")) + router.Static("/images", path.Join(cfg.StaticPath, "images")) // TODO: should be moved to the web service - router.StaticFile("/favicon.ico", path.Join(staticPath, "favicon.ico")) - router.StaticFile("/apple-touch-icon.png", path.Join(staticPath, "apple-touch-icon.png")) + router.StaticFile("/favicon.ico", path.Join(cfg.StaticPath, "favicon.ico")) + router.StaticFile("/apple-touch-icon.png", path.Join(cfg.StaticPath, "apple-touch-icon.png")) - if uiStaticAddr, ok := os.LookupEnv("API_STATIC_URL"); ok { - uiStaticUrl, err := url.Parse(uiStaticAddr) - if err != nil { - logrus.WithError(err).Error("error on parsing URL to redirect requests to the UI static") - } + if cfg.StaticURL.Scheme != "" && cfg.StaticURL.Host != "" { router.NoRoute(func() gin.HandlerFunc { return func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.String(), "/app/") { @@ -106,8 +107,8 @@ func NewRouter( } director := func(req *http.Request) { *req = *c.Request - req.URL.Scheme = uiStaticUrl.Scheme - req.URL.Host = uiStaticUrl.Host + req.URL.Scheme = cfg.StaticURL.Scheme + req.URL.Host = cfg.StaticURL.Host } proxy := &httputil.ReverseProxy{ Director: director, @@ -135,12 +136,17 @@ func NewRouter( serverConnector := client.NewAgentServerClient(db, dbConns, s3Conns) // services - protoService := proto.NewProtoService(db, serverConnector, userActionWriter) + authService := public.NewAuthService(public.AuthServiceConfig{ + SessionTimeout: 3 * 3600, // 3 hours + APIBaseURL: cfg.BaseURL, + SecureCookie: cfg.UseSSL, + }, db) + protoService := proto.NewProtoService(db, serverConnector, userActionWriter, cfg.CertsPath) agentService := private.NewAgentService(db, serverConnector, userActionWriter) binariesService := private.NewBinariesService(db, userActionWriter) eventService := private.NewEventService(serverConnector) groupService := private.NewGroupService(serverConnector, userActionWriter) - moduleService := private.NewModuleService(db, serverConnector, userActionWriter) + moduleService := private.NewModuleService(db, serverConnector, userActionWriter, cfg.TemplatesDir) optionService := private.NewOptionService(db) policyService := private.NewPolicyService(db, serverConnector, userActionWriter) portingService := private.NewPortingService(db, userActionWriter) @@ -153,14 +159,14 @@ func NewRouter( userService := private.NewUserService(db) // set api handlers - api := router.Group(utils.PrefixPathAPI) + api := router.Group(cfg.BaseURL) api.Use(setGlobalDB(db)) { - setPublicGroup(api) + setPublicGroup(api, authService) setSwaggerGroup(api) - setVXProtoGroup(api, db, protoService) + setVXProtoGroup(api, db, protoService, cfg.BaseURL) } privateGroup := api.Group("/") @@ -201,20 +207,20 @@ func NewRouter( return router } -func setPublicGroup(parent *gin.RouterGroup) { +func setPublicGroup(parent *gin.RouterGroup, svc *public.AuthService) { publicGroup := parent.Group("/") { - publicGroup.GET("/info", public.Info) + publicGroup.GET("/info", svc.Info) authGroup := publicGroup.Group("/auth") { - authGroup.POST("/login", public.AuthLogin) - authGroup.GET("/logout", public.AuthLogout) + authGroup.POST("/login", svc.AuthLogin) + authGroup.GET("/logout", svc.AuthLogout) } authPrivateGroup := publicGroup.Group("/auth") authPrivateGroup.Use(authRequired()) { - authPrivateGroup.POST("/switch-service", public.AuthSwitchService) + authPrivateGroup.POST("/switch-service", svc.AuthSwitchService) } } } @@ -227,9 +233,9 @@ func setSwaggerGroup(parent *gin.RouterGroup) { } } -func setVXProtoGroup(parent *gin.RouterGroup, db *gorm.DB, svc *proto.ProtoService) { +func setVXProtoGroup(parent *gin.RouterGroup, db *gorm.DB, svc *proto.ProtoService, apiBaseURL string) { vxProtoGroup := parent.Group("/") - vxProtoGroup.Use(authTokenProtoRequired()) + vxProtoGroup.Use(authTokenProtoRequired(apiBaseURL)) vxProtoGroup.Use(setServiceInfo(db)) { protoAggregateGroup := vxProtoGroup.Group("/vxpws") diff --git a/pkg/app/api/utils/utils.go b/pkg/app/api/utils/utils.go index 7e60941d..2034982c 100644 --- a/pkg/app/api/utils/utils.go +++ b/pkg/app/api/utils/utils.go @@ -44,13 +44,6 @@ const ( TargetVersionInvalid ) -const DefaultSessionTimeout int = 3 * 3600 // 3 hours - -const ( - // URL prefix for each REST API endpoint - PrefixPathAPI = "/api/v1" -) - func GetAgentName(db *gorm.DB, hash string) (string, error) { var agent models.Agent if err := db.Take(&agent, "hash = ?", hash).Error; err != nil { @@ -87,11 +80,6 @@ type errorResp struct { TraceID string `json:"trace_id,omitempty" example:"1234567890abcdef1234567890abcdef"` } // @name ErrorResponse -// IsUseSSL is function to return true if server configured to use TLS for incoming connections -func IsUseSSL() bool { - return os.Getenv("API_USE_SSL") == "true" -} - // UniqueUint64InSlice is function to remove duplicates in slice of uint64 func UniqueUint64InSlice(slice []uint64) []uint64 { keys := make(map[uint64]bool) diff --git a/pkg/app/api/worker/module_syncer.go b/pkg/app/api/worker/module_syncer.go index 6d4b1fa7..c082ef47 100644 --- a/pkg/app/api/worker/module_syncer.go +++ b/pkg/app/api/worker/module_syncer.go @@ -346,19 +346,7 @@ func SyncModulesToPolicies(ctx context.Context, gDB *gorm.DB) { } } -func SyncRetentionEvents(ctx context.Context, gDB *gorm.DB) { - var ( - err error - keepAmountDays int - ) - if retEvents, ok := os.LookupEnv("RETENTION_EVENTS"); !ok { - logrus.Info("events retention policy is not set") - return - } else if keepAmountDays, err = strconv.Atoi(retEvents); err != nil { - logrus.WithError(err).Error("events retention policy must contains amount days") - return - } - +func SyncRetentionEvents(ctx context.Context, gDB *gorm.DB, keepAmountDays int) { mSV := make(map[uint64]*service) rotateEventsInstance := func(ctx context.Context, srv *service) { var event models.Event