485 lines
15 KiB
Go
485 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
|
|
"next-terminal/server/config"
|
|
"next-terminal/server/global/cache"
|
|
"next-terminal/server/log"
|
|
"next-terminal/server/model"
|
|
"next-terminal/server/repository"
|
|
"next-terminal/server/service"
|
|
"next-terminal/server/utils"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/echo/v4/middleware"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
var (
|
|
userRepository *repository.UserRepository
|
|
userGroupRepository *repository.UserGroupRepository
|
|
resourceSharerRepository *repository.ResourceSharerRepository
|
|
assetRepository *repository.AssetRepository
|
|
credentialRepository *repository.CredentialRepository
|
|
propertyRepository *repository.PropertyRepository
|
|
commandRepository *repository.CommandRepository
|
|
sessionRepository *repository.SessionRepository
|
|
accessSecurityRepository *repository.AccessSecurityRepository
|
|
accessGatewayRepository *repository.AccessGatewayRepository
|
|
jobRepository *repository.JobRepository
|
|
jobLogRepository *repository.JobLogRepository
|
|
loginLogRepository *repository.LoginLogRepository
|
|
storageRepository *repository.StorageRepository
|
|
strategyRepository *repository.StrategyRepository
|
|
|
|
jobService *service.JobService
|
|
propertyService *service.PropertyService
|
|
userService *service.UserService
|
|
sessionService *service.SessionService
|
|
mailService *service.MailService
|
|
assetService *service.AssetService
|
|
credentialService *service.CredentialService
|
|
storageService *service.StorageService
|
|
accessGatewayService *service.AccessGatewayService
|
|
)
|
|
|
|
func SetupRoutes(db *gorm.DB) *echo.Echo {
|
|
|
|
InitRepository(db)
|
|
InitService()
|
|
|
|
cache.GlobalCache.OnEvicted(userService.OnEvicted)
|
|
|
|
if err := InitDBData(); err != nil {
|
|
log.Errorf("初始化数据异常: %v", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
if err := ReloadData(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
e := echo.New()
|
|
e.HideBanner = true
|
|
//e.Logger = log.GetEchoLogger()
|
|
//e.Use(log.Hook())
|
|
e.File("/", "web/build/index.html")
|
|
e.File("/asciinema.html", "web/build/asciinema.html")
|
|
e.File("/", "web/build/index.html")
|
|
e.File("/favicon.ico", "web/build/favicon.ico")
|
|
e.Static("/static", "web/build/static")
|
|
|
|
e.Use(middleware.Recover())
|
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
Skipper: middleware.DefaultSkipper,
|
|
AllowOrigins: []string{"*"},
|
|
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
|
}))
|
|
e.Use(ErrorHandler)
|
|
e.Use(TcpWall)
|
|
e.Use(Auth)
|
|
|
|
e.POST("/login", LoginEndpoint)
|
|
e.POST("/loginWithTotp", loginWithTotpEndpoint)
|
|
|
|
e.GET("/tunnel", TunEndpoint)
|
|
e.GET("/ssh", SSHEndpoint)
|
|
e.GET("/ssh-monitor", SshMonitor)
|
|
e.POST("/logout", LogoutEndpoint)
|
|
e.POST("/change-password", ChangePasswordEndpoint)
|
|
e.GET("/reload-totp", ReloadTOTPEndpoint)
|
|
e.POST("/reset-totp", ResetTOTPEndpoint)
|
|
e.POST("/confirm-totp", ConfirmTOTPEndpoint)
|
|
e.GET("/info", InfoEndpoint)
|
|
|
|
account := e.Group("/account")
|
|
{
|
|
account.GET("/assets", AccountAssetEndpoint)
|
|
account.GET("/storage", AccountStorageEndpoint)
|
|
}
|
|
|
|
users := e.Group("/users", Admin)
|
|
{
|
|
users.POST("", UserCreateEndpoint)
|
|
users.GET("/paging", UserPagingEndpoint)
|
|
users.PUT("/:id", UserUpdateEndpoint)
|
|
users.PATCH("/:id/status", UserUpdateStatusEndpoint)
|
|
users.DELETE("/:id", UserDeleteEndpoint)
|
|
users.GET("/:id", UserGetEndpoint)
|
|
users.POST("/:id/change-password", UserChangePasswordEndpoint)
|
|
users.POST("/:id/reset-totp", UserResetTotpEndpoint)
|
|
}
|
|
|
|
userGroups := e.Group("/user-groups", Admin)
|
|
{
|
|
userGroups.POST("", UserGroupCreateEndpoint)
|
|
userGroups.GET("/paging", UserGroupPagingEndpoint)
|
|
userGroups.PUT("/:id", UserGroupUpdateEndpoint)
|
|
userGroups.DELETE("/:id", UserGroupDeleteEndpoint)
|
|
userGroups.GET("/:id", UserGroupGetEndpoint)
|
|
}
|
|
|
|
assets := e.Group("/assets", Admin)
|
|
{
|
|
assets.GET("", AssetAllEndpoint)
|
|
assets.POST("", AssetCreateEndpoint)
|
|
assets.POST("/import", AssetImportEndpoint)
|
|
assets.GET("/paging", AssetPagingEndpoint)
|
|
assets.POST("/:id/tcping", AssetTcpingEndpoint)
|
|
assets.PUT("/:id", AssetUpdateEndpoint)
|
|
assets.GET("/:id", AssetGetEndpoint)
|
|
assets.DELETE("/:id", AssetDeleteEndpoint)
|
|
assets.POST("/:id/change-owner", AssetChangeOwnerEndpoint)
|
|
}
|
|
|
|
e.GET("/tags", AssetTagsEndpoint)
|
|
|
|
commands := e.Group("/commands")
|
|
{
|
|
commands.GET("", CommandAllEndpoint)
|
|
commands.GET("/paging", CommandPagingEndpoint)
|
|
commands.POST("", CommandCreateEndpoint)
|
|
commands.PUT("/:id", CommandUpdateEndpoint)
|
|
commands.DELETE("/:id", CommandDeleteEndpoint)
|
|
commands.GET("/:id", CommandGetEndpoint)
|
|
commands.POST("/:id/change-owner", CommandChangeOwnerEndpoint, Admin)
|
|
}
|
|
|
|
credentials := e.Group("/credentials", Admin)
|
|
{
|
|
credentials.GET("", CredentialAllEndpoint)
|
|
credentials.GET("/paging", CredentialPagingEndpoint)
|
|
credentials.POST("", CredentialCreateEndpoint)
|
|
credentials.PUT("/:id", CredentialUpdateEndpoint)
|
|
credentials.DELETE("/:id", CredentialDeleteEndpoint)
|
|
credentials.GET("/:id", CredentialGetEndpoint)
|
|
credentials.POST("/:id/change-owner", CredentialChangeOwnerEndpoint)
|
|
}
|
|
|
|
sessions := e.Group("/sessions")
|
|
{
|
|
sessions.GET("/paging", Admin(SessionPagingEndpoint))
|
|
sessions.POST("/:id/disconnect", Admin(SessionDisconnectEndpoint))
|
|
sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
|
|
sessions.GET("/:id/recording", Admin(SessionRecordingEndpoint))
|
|
sessions.GET("/:id", Admin(SessionGetEndpoint))
|
|
sessions.POST("/:id/reviewed", Admin(SessionReviewedEndpoint))
|
|
sessions.POST("/:id/unreviewed", Admin(SessionUnViewedEndpoint))
|
|
sessions.POST("/clear", Admin(SessionClearEndpoint))
|
|
sessions.POST("/reviewed", Admin(SessionReviewedAllEndpoint))
|
|
|
|
sessions.POST("", SessionCreateEndpoint)
|
|
sessions.POST("/:id/connect", SessionConnectEndpoint)
|
|
sessions.POST("/:id/resize", SessionResizeEndpoint)
|
|
sessions.GET("/:id/stats", SessionStatsEndpoint)
|
|
|
|
sessions.POST("/:id/ls", SessionLsEndpoint)
|
|
sessions.GET("/:id/download", SessionDownloadEndpoint)
|
|
sessions.POST("/:id/upload", SessionUploadEndpoint)
|
|
sessions.POST("/:id/edit", SessionEditEndpoint)
|
|
sessions.POST("/:id/mkdir", SessionMkDirEndpoint)
|
|
sessions.POST("/:id/rm", SessionRmEndpoint)
|
|
sessions.POST("/:id/rename", SessionRenameEndpoint)
|
|
}
|
|
|
|
resourceSharers := e.Group("/resource-sharers", Admin)
|
|
{
|
|
resourceSharers.GET("", RSGetSharersEndPoint)
|
|
resourceSharers.POST("/remove-resources", ResourceRemoveByUserIdAssignEndPoint)
|
|
resourceSharers.POST("/add-resources", ResourceAddByUserIdAssignEndPoint)
|
|
}
|
|
|
|
loginLogs := e.Group("login-logs", Admin)
|
|
{
|
|
loginLogs.GET("/paging", LoginLogPagingEndpoint)
|
|
loginLogs.DELETE("/:id", LoginLogDeleteEndpoint)
|
|
loginLogs.POST("/clear", LoginLogClearEndpoint)
|
|
}
|
|
|
|
e.GET("/properties", Admin(PropertyGetEndpoint))
|
|
e.PUT("/properties", Admin(PropertyUpdateEndpoint))
|
|
|
|
overview := e.Group("overview", Admin)
|
|
{
|
|
overview.GET("/counter", OverviewCounterEndPoint)
|
|
overview.GET("/asset", OverviewAssetEndPoint)
|
|
overview.GET("/access", OverviewAccessEndPoint)
|
|
}
|
|
|
|
jobs := e.Group("/jobs", Admin)
|
|
{
|
|
jobs.POST("", JobCreateEndpoint)
|
|
jobs.GET("/paging", JobPagingEndpoint)
|
|
jobs.PUT("/:id", JobUpdateEndpoint)
|
|
jobs.POST("/:id/change-status", JobChangeStatusEndpoint)
|
|
jobs.POST("/:id/exec", JobExecEndpoint)
|
|
jobs.DELETE("/:id", JobDeleteEndpoint)
|
|
jobs.GET("/:id", JobGetEndpoint)
|
|
jobs.GET("/:id/logs", JobGetLogsEndpoint)
|
|
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
|
|
}
|
|
|
|
securities := e.Group("/securities", Admin)
|
|
{
|
|
securities.POST("", SecurityCreateEndpoint)
|
|
securities.GET("/paging", SecurityPagingEndpoint)
|
|
securities.PUT("/:id", SecurityUpdateEndpoint)
|
|
securities.DELETE("/:id", SecurityDeleteEndpoint)
|
|
securities.GET("/:id", SecurityGetEndpoint)
|
|
}
|
|
|
|
storages := e.Group("/storages")
|
|
{
|
|
storages.GET("/paging", StoragePagingEndpoint, Admin)
|
|
storages.POST("", StorageCreateEndpoint, Admin)
|
|
storages.DELETE("/:id", StorageDeleteEndpoint, Admin)
|
|
storages.PUT("/:id", StorageUpdateEndpoint, Admin)
|
|
storages.GET("/shares", StorageSharesEndpoint, Admin)
|
|
storages.GET("/:id", StorageGetEndpoint, Admin)
|
|
|
|
storages.POST("/:storageId/ls", StorageLsEndpoint)
|
|
storages.GET("/:storageId/download", StorageDownloadEndpoint)
|
|
storages.POST("/:storageId/upload", StorageUploadEndpoint)
|
|
storages.POST("/:storageId/mkdir", StorageMkDirEndpoint)
|
|
storages.POST("/:storageId/rm", StorageRmEndpoint)
|
|
storages.POST("/:storageId/rename", StorageRenameEndpoint)
|
|
storages.POST("/:storageId/edit", StorageEditEndpoint)
|
|
}
|
|
|
|
strategies := e.Group("/strategies", Admin)
|
|
{
|
|
strategies.GET("", StrategyAllEndpoint)
|
|
strategies.GET("/paging", StrategyPagingEndpoint)
|
|
strategies.POST("", StrategyCreateEndpoint)
|
|
strategies.DELETE("/:id", StrategyDeleteEndpoint)
|
|
strategies.PUT("/:id", StrategyUpdateEndpoint)
|
|
}
|
|
|
|
accessGateways := e.Group("/access-gateways", Admin)
|
|
{
|
|
accessGateways.GET("", AccessGatewayAllEndpoint)
|
|
accessGateways.POST("", AccessGatewayCreateEndpoint)
|
|
accessGateways.GET("/paging", AccessGatewayPagingEndpoint)
|
|
accessGateways.PUT("/:id", AccessGatewayUpdateEndpoint)
|
|
accessGateways.DELETE("/:id", AccessGatewayDeleteEndpoint)
|
|
accessGateways.GET("/:id", AccessGatewayGetEndpoint)
|
|
accessGateways.POST("/:id/reconnect", AccessGatewayReconnectEndpoint)
|
|
}
|
|
|
|
backup := e.Group("/backup", Admin)
|
|
{
|
|
backup.GET("/export", BackupExportEndpoint)
|
|
backup.POST("/import", BackupImportEndpoint)
|
|
}
|
|
|
|
return e
|
|
}
|
|
|
|
func ReloadData() error {
|
|
if err := ReloadAccessSecurity(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ReloadToken(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func InitRepository(db *gorm.DB) {
|
|
userRepository = repository.NewUserRepository(db)
|
|
userGroupRepository = repository.NewUserGroupRepository(db)
|
|
resourceSharerRepository = repository.NewResourceSharerRepository(db)
|
|
assetRepository = repository.NewAssetRepository(db)
|
|
credentialRepository = repository.NewCredentialRepository(db)
|
|
propertyRepository = repository.NewPropertyRepository(db)
|
|
commandRepository = repository.NewCommandRepository(db)
|
|
sessionRepository = repository.NewSessionRepository(db)
|
|
accessSecurityRepository = repository.NewAccessSecurityRepository(db)
|
|
accessGatewayRepository = repository.NewAccessGatewayRepository(db)
|
|
jobRepository = repository.NewJobRepository(db)
|
|
jobLogRepository = repository.NewJobLogRepository(db)
|
|
loginLogRepository = repository.NewLoginLogRepository(db)
|
|
storageRepository = repository.NewStorageRepository(db)
|
|
strategyRepository = repository.NewStrategyRepository(db)
|
|
}
|
|
|
|
func InitService() {
|
|
propertyService = service.NewPropertyService(propertyRepository)
|
|
userService = service.NewUserService(userRepository, loginLogRepository)
|
|
sessionService = service.NewSessionService(sessionRepository)
|
|
mailService = service.NewMailService(propertyRepository)
|
|
assetService = service.NewAssetService(assetRepository)
|
|
jobService = service.NewJobService(jobRepository, jobLogRepository, assetRepository, credentialRepository, assetService)
|
|
credentialService = service.NewCredentialService(credentialRepository)
|
|
storageService = service.NewStorageService(storageRepository, userRepository, propertyRepository)
|
|
accessGatewayService = service.NewAccessGatewayService(accessGatewayRepository)
|
|
}
|
|
|
|
func InitDBData() (err error) {
|
|
if err := propertyService.DeleteDeprecatedProperty(); err != nil {
|
|
return err
|
|
}
|
|
if err := accessGatewayService.ReConnectAll(); err != nil {
|
|
return err
|
|
}
|
|
if err := propertyService.InitProperties(); err != nil {
|
|
return err
|
|
}
|
|
if err := userService.InitUser(); err != nil {
|
|
return err
|
|
}
|
|
if err := jobService.InitJob(); err != nil {
|
|
return err
|
|
}
|
|
if err := userService.FixUserOnlineState(); err != nil {
|
|
return err
|
|
}
|
|
if err := sessionService.FixSessionState(); err != nil {
|
|
return err
|
|
}
|
|
if err := sessionService.EmptyPassword(); err != nil {
|
|
return err
|
|
}
|
|
if err := credentialService.Encrypt(); err != nil {
|
|
return err
|
|
}
|
|
if err := assetService.Encrypt(); err != nil {
|
|
return err
|
|
}
|
|
if err := storageService.InitStorages(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ResetPassword(username string) error {
|
|
user, err := userRepository.FindByUsername(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
password := "next-terminal"
|
|
passwd, err := utils.Encoder.Encode([]byte(password))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u := &model.User{
|
|
Password: string(passwd),
|
|
ID: user.ID,
|
|
}
|
|
if err := userRepository.Update(u); err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("用户「%v」密码初始化为: %v", user.Username, password)
|
|
return nil
|
|
}
|
|
|
|
func ResetTotp(username string) error {
|
|
user, err := userRepository.FindByUsername(username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u := &model.User{
|
|
TOTPSecret: "-",
|
|
ID: user.ID,
|
|
}
|
|
if err := userRepository.Update(u); err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("用户「%v」已重置TOTP", user.Username)
|
|
return nil
|
|
}
|
|
|
|
func ChangeEncryptionKey(oldEncryptionKey, newEncryptionKey string) error {
|
|
|
|
oldPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(oldEncryptionKey))))
|
|
newPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(newEncryptionKey))))
|
|
|
|
credentials, err := credentialRepository.FindAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := range credentials {
|
|
credential := credentials[i]
|
|
if err := credentialRepository.Decrypt(&credential, oldPassword); err != nil {
|
|
return err
|
|
}
|
|
if err := credentialRepository.Encrypt(&credential, newPassword); err != nil {
|
|
return err
|
|
}
|
|
if err := credentialRepository.UpdateById(&credential, credential.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
assets, err := assetRepository.FindAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := range assets {
|
|
asset := assets[i]
|
|
if err := assetRepository.Decrypt(&asset, oldPassword); err != nil {
|
|
return err
|
|
}
|
|
if err := assetRepository.Encrypt(&asset, newPassword); err != nil {
|
|
return err
|
|
}
|
|
if err := assetRepository.UpdateById(&asset, asset.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Infof("encryption key has being changed.")
|
|
return nil
|
|
}
|
|
|
|
func SetupDB() *gorm.DB {
|
|
|
|
var logMode logger.Interface
|
|
if config.GlobalCfg.Debug {
|
|
logMode = logger.Default.LogMode(logger.Info)
|
|
} else {
|
|
logMode = logger.Default.LogMode(logger.Silent)
|
|
}
|
|
|
|
fmt.Printf("当前数据库模式为:%v\n", config.GlobalCfg.DB)
|
|
var err error
|
|
var db *gorm.DB
|
|
if config.GlobalCfg.DB == "mysql" {
|
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=60s",
|
|
config.GlobalCfg.Mysql.Username,
|
|
config.GlobalCfg.Mysql.Password,
|
|
config.GlobalCfg.Mysql.Hostname,
|
|
config.GlobalCfg.Mysql.Port,
|
|
config.GlobalCfg.Mysql.Database,
|
|
)
|
|
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
|
Logger: logMode,
|
|
})
|
|
} else {
|
|
db, err = gorm.Open(sqlite.Open(config.GlobalCfg.Sqlite.File), &gorm.Config{
|
|
Logger: logMode,
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
log.Errorf("连接数据库异常: %v", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&model.User{}, &model.Asset{}, &model.AssetAttribute{}, &model.Session{}, &model.Command{},
|
|
&model.Credential{}, &model.Property{}, &model.ResourceSharer{}, &model.UserGroup{}, &model.UserGroupMember{},
|
|
&model.LoginLog{}, &model.Job{}, &model.JobLog{}, &model.AccessSecurity{}, &model.AccessGateway{},
|
|
&model.Storage{}, &model.Strategy{}); err != nil {
|
|
log.Errorf("初始化数据库表结构异常: %v", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
return db
|
|
}
|