From 0b73b4aa4762111bb0e274621b8342df46a88791 Mon Sep 17 00:00:00 2001 From: greysoh Date: Mon, 23 Dec 2024 15:52:16 -0500 Subject: [PATCH] feature: Adds backend system and basic API. This adds the backend API, as well as backend infrastructure, including autostarting and basic communication between the Goroutine + Application. --- .gitignore | 1 + backend/api/backendruntime/runtime.go | 237 +++++++++++++++ backend/api/backendruntime/struct.go | 34 +++ backend/api/controllers/v1/backends/create.go | 271 ++++++++++++++++++ backend/api/jwtcore/jwt.go | 17 +- backend/api/main.go | 189 ++++++++++-- backend/backends.dev.json | 10 + backend/build.sh | 20 ++ backend/commonbackend/unmarshal.go | 2 +- backend/externalbackendlauncher/main.go | 3 +- backend/sshbackend/main.go | 22 +- routes/Hermes API/Backend/Create.bru | 15 +- 12 files changed, 777 insertions(+), 44 deletions(-) create mode 100644 backend/api/backendruntime/runtime.go create mode 100644 backend/api/backendruntime/struct.go create mode 100644 backend/api/controllers/v1/backends/create.go create mode 100644 backend/backends.dev.json create mode 100755 backend/build.sh diff --git a/.gitignore b/.gitignore index df99423..b9f25d5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ backend/sshbackend/sshbackend backend/dummybackend/dummybackend backend/externalbackendlauncher/externalbackendlauncher +backend/api/api # LOM sshfrontend/keys diff --git a/backend/api/backendruntime/runtime.go b/backend/api/backendruntime/runtime.go new file mode 100644 index 0000000..0f871f8 --- /dev/null +++ b/backend/api/backendruntime/runtime.go @@ -0,0 +1,237 @@ +package backendruntime + +import ( + "context" + "fmt" + "net" + "os" + "os/exec" + "time" + + "git.terah.dev/imterah/hermes/backendlauncher" + "git.terah.dev/imterah/hermes/commonbackend" + "github.com/charmbracelet/log" +) + +var ( + AvailableBackends []*Backend + RunningBackends map[uint]*Runtime + TempDir string +) + +func init() { + RunningBackends = make(map[uint]*Runtime) +} + +func handleCommand(commandType string, command interface{}, sock net.Conn, rtcChan chan interface{}) { + bytes, err := commonbackend.Marshal(commandType, command) + + if err != nil { + log.Warnf("Failed to marshal message: %s", err.Error()) + rtcChan <- fmt.Errorf("failed to marshal message: %s", err.Error()) + + return + } + + if _, err := sock.Write(bytes); err != nil { + log.Warnf("Failed to write message: %s", err.Error()) + rtcChan <- fmt.Errorf("failed to write message: %s", err.Error()) + + return + } + + _, data, err := commonbackend.Unmarshal(sock) + + if err != nil { + log.Warnf("Failed to unmarshal message: %s", err.Error()) + rtcChan <- fmt.Errorf("failed to unmarshal message: %s", err.Error()) + + return + } + + rtcChan <- data +} + +func (runtime *Runtime) goRoutineHandler() error { + log.Debug("Starting up backend runtime") + log.Debug("Running socket acquisition") + + logLevel := os.Getenv("HERMES_LOG_LEVEL") + + sockPath, sockListener, err := backendlauncher.GetUnixSocket(TempDir) + + if err != nil { + return err + } + + runtime.currentListener = sockListener + + log.Debugf("Acquired unix socket at: %s", sockPath) + + go func() { + log.Debug("Creating new goroutine for socket connection handling") + + for { + log.Debug("Waiting for Unix socket connections...") + sock, err := runtime.currentListener.Accept() + + if err != nil { + log.Warnf("Failed to accept Unix socket connection in a backend runtime instance: %s", err.Error()) + return + } + + log.Debug("Recieved connection. Initializing...") + + defer sock.Close() + + for { + commandRaw := <-runtime.RuntimeCommands + + log.Debug("Got message from server") + + switch command := commandRaw.(type) { + case *commonbackend.AddProxy: + handleCommand("addProxy", command, sock, runtime.RuntimeCommands) + case *commonbackend.BackendStatusRequest: + handleCommand("backendStatusRequest", command, sock, runtime.RuntimeCommands) + case *commonbackend.BackendStatusResponse: + handleCommand("backendStatusResponse", command, sock, runtime.RuntimeCommands) + case *commonbackend.CheckClientParameters: + handleCommand("checkClientParameters", command, sock, runtime.RuntimeCommands) + case *commonbackend.CheckParametersResponse: + handleCommand("checkParametersResponse", command, sock, runtime.RuntimeCommands) + case *commonbackend.CheckServerParameters: + handleCommand("checkServerParameters", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyClientConnection: + handleCommand("proxyClientConnection", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyConnectionsRequest: + handleCommand("proxyConnectionsRequest", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyConnectionsResponse: + handleCommand("proxyConnectionsResponse", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyInstanceResponse: + handleCommand("proxyInstanceResponse", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyInstanceRequest: + handleCommand("proxyInstanceRequest", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyStatusRequest: + handleCommand("proxyStatusRequest", command, sock, runtime.RuntimeCommands) + case *commonbackend.ProxyStatusResponse: + handleCommand("proxyStatusResponse", command, sock, runtime.RuntimeCommands) + case *commonbackend.RemoveProxy: + handleCommand("removeProxy", command, sock, runtime.RuntimeCommands) + case *commonbackend.Start: + handleCommand("start", command, sock, runtime.RuntimeCommands) + case *commonbackend.Stop: + handleCommand("stop", command, sock, runtime.RuntimeCommands) + default: + log.Warnf("Recieved unknown command type from channel: %q", command) + runtime.RuntimeCommands <- fmt.Errorf("unknown command recieved") + } + } + } + }() + + for { + log.Debug("Starting process...") + + ctx := context.Background() + + runtime.currentProcess = exec.CommandContext(ctx, runtime.ProcessPath) + runtime.currentProcess.Env = append(runtime.currentProcess.Env, fmt.Sprintf("HERMES_API_SOCK=%s", sockPath), fmt.Sprintf("HERMES_LOG_LEVEL=%s", logLevel)) + + runtime.currentProcess.Stdout = runtime.logger + runtime.currentProcess.Stderr = runtime.logger + + err := runtime.currentProcess.Run() + + if err != nil { + if err, ok := err.(*exec.ExitError); ok { + if err.ExitCode() != -1 && err.ExitCode() != 0 { + log.Warnf("A backend process died with exit code '%d' and with error '%s'", err.ExitCode(), err.Error()) + } + } else { + log.Warnf("A backend process died with error: %s", err.Error()) + } + } else { + log.Debug("Process exited gracefully.") + } + + if !runtime.isRuntimeRunning { + return nil + } + + log.Debug("Sleeping 5 seconds, and then restarting process") + time.Sleep(5 * time.Second) + } +} + +func (runtime *Runtime) Start() error { + if runtime.isRuntimeRunning { + return fmt.Errorf("runtime already running") + } + + runtime.RuntimeCommands = make(chan interface{}) + + runtime.logger = &writeLogger{ + Runtime: runtime, + } + + go func() { + err := runtime.goRoutineHandler() + + if err != nil { + log.Errorf("Failed during execution of runtime: %s", err.Error()) + } + }() + + runtime.isRuntimeRunning = true + return nil +} + +func (runtime *Runtime) Stop() error { + if !runtime.isRuntimeRunning { + return fmt.Errorf("runtime not running") + } + + runtime.isRuntimeRunning = false + + if runtime.currentProcess != nil && runtime.currentProcess.Cancel != nil { + err := runtime.currentProcess.Cancel() + + if err != nil { + return fmt.Errorf("failed to stop process: %s", err.Error()) + } + } else { + log.Warn("Failed to kill process (Stop recieved), currentProcess or currentProcess.Cancel is nil") + } + + if runtime.currentListener != nil { + err := runtime.currentListener.Close() + + if err != nil { + return fmt.Errorf("failed to stop listener: %s", err.Error()) + } + } else { + log.Warn("Failed to kill listener, as the listener is nil") + } + + return nil +} + +func NewBackend(path string) *Runtime { + return &Runtime{ + ProcessPath: path, + } +} + +func Init(backends []*Backend) error { + var err error + TempDir, err = os.MkdirTemp("", "hermes-sockets-") + + if err != nil { + return err + } + + AvailableBackends = backends + + return nil +} diff --git a/backend/api/backendruntime/struct.go b/backend/api/backendruntime/struct.go new file mode 100644 index 0000000..68f1316 --- /dev/null +++ b/backend/api/backendruntime/struct.go @@ -0,0 +1,34 @@ +package backendruntime + +import ( + "net" + "os/exec" + "strings" +) + +type Backend struct { + Name string `validate:"required"` + Path string `validate:"required"` +} + +type Runtime struct { + isRuntimeRunning bool + logger *writeLogger + currentProcess *exec.Cmd + currentListener net.Listener + + ProcessPath string + Logs []string + RuntimeCommands chan interface{} +} + +type writeLogger struct { + Runtime *Runtime +} + +func (writer writeLogger) Write(p []byte) (n int, err error) { + logSplit := strings.Split(string(p), "\n") + writer.Runtime.Logs = append(writer.Runtime.Logs, logSplit...) + + return len(p), err +} diff --git a/backend/api/controllers/v1/backends/create.go b/backend/api/controllers/v1/backends/create.go new file mode 100644 index 0000000..2650fb5 --- /dev/null +++ b/backend/api/controllers/v1/backends/create.go @@ -0,0 +1,271 @@ +package backends + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + + "git.terah.dev/imterah/hermes/api/backendruntime" + "git.terah.dev/imterah/hermes/api/dbcore" + "git.terah.dev/imterah/hermes/api/jwtcore" + "git.terah.dev/imterah/hermes/api/permissions" + "git.terah.dev/imterah/hermes/commonbackend" + "github.com/charmbracelet/log" + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type BackendCreationRequest struct { + Token string `validate:"required"` + Name string `validate:"required"` + Description *string + Backend string `validate:"required"` + BackendParameters interface{} `json:"connectionDetails" validate:"required"` +} + +func CreateBackend(c *gin.Context) { + var req BackendCreationRequest + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Failed to parse body: %s", err.Error()), + }) + + return + } + + if err := validator.New().Struct(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Failed to validate body: %s", err.Error()), + }) + + return + } + + user, err := jwtcore.GetUserFromJWT(req.Token) + + if err != nil { + if err.Error() == "token is expired" || err.Error() == "user does not exist" { + c.JSON(http.StatusForbidden, gin.H{ + "error": err.Error(), + }) + + return + } else { + log.Warnf("Failed to get user from the provided JWT token: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to parse token", + }) + + return + } + } + + if !permissions.UserHasPermission(user, "backends.add") { + c.JSON(http.StatusForbidden, gin.H{ + "error": "Missing permissions", + }) + + return + } + + var backendParameters []byte + + switch parameters := req.BackendParameters.(type) { + case string: + backendParameters = []byte(parameters) + case map[string]interface{}: + backendParameters, err = json.Marshal(parameters) + + if err != nil { + log.Warnf("Failed to marshal JSON recieved as BackendParameters: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to prepare parameters", + }) + + return + } + default: + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Invalid type for connectionDetails (recieved %T)", parameters), + }) + + return + } + + var backendRuntimeFilePath string + + for _, runtime := range backendruntime.AvailableBackends { + if runtime.Name == req.Backend { + backendRuntimeFilePath = runtime.Path + } + } + + if backendRuntimeFilePath == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Unsupported backend recieved", + }) + + return + } + + backend := backendruntime.NewBackend(backendRuntimeFilePath) + err = backend.Start() + + if err != nil { + log.Warnf("Failed to start backend: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to start backend", + }) + + return + } + + backend.RuntimeCommands <- &commonbackend.CheckServerParameters{ + Type: "checkServerParameters", + Arguments: backendParameters, + } + + backendParamCheckResponse := <-backend.RuntimeCommands + + switch responseMessage := backendParamCheckResponse.(type) { + case error: + log.Warnf("Failed to get response for backend: %s", responseMessage.Error()) + + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to get status response from backend", + }) + + return + case *commonbackend.CheckParametersResponse: + if responseMessage.InResponseTo != "checkServerParameters" { + log.Errorf("Got illegal response to CheckServerParameters: %s", responseMessage.InResponseTo) + + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to get status response from backend", + }) + + return + } + + if !responseMessage.IsValid { + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + var errorMessage string + + if responseMessage.Message == "" { + errorMessage = "Unkown error while trying to parse connectionDetails" + } else { + errorMessage = fmt.Sprintf("Invalid backend parameters: %s", responseMessage.Message) + } + + c.JSON(http.StatusBadRequest, gin.H{ + "error": errorMessage, + }) + + return + } + default: + log.Warnf("Got illegal response type for backend: %T", responseMessage) + } + + log.Info("Passed backend checks successfully") + + backendInDatabase := &dbcore.Backend{ + UserID: user.ID, + Name: req.Name, + Description: req.Description, + Backend: req.Backend, + BackendParameters: base64.StdEncoding.EncodeToString(backendParameters), + } + + if result := dbcore.DB.Create(&backendInDatabase); result.Error != nil { + log.Warnf("Failed to create backend: %s", result.Error.Error()) + + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to add backend into database", + }) + + return + } + + backend.RuntimeCommands <- &commonbackend.Start{ + Type: "start", + Arguments: backendParameters, + } + + backendStartResponse := <-backend.RuntimeCommands + + switch responseMessage := backendStartResponse.(type) { + case error: + log.Warnf("Failed to get response for backend: %s", responseMessage.Error()) + + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to get status response from backend", + }) + + return + case *commonbackend.BackendStatusResponse: + if !responseMessage.IsRunning { + err = backend.Stop() + + if err != nil { + log.Warnf("Failed to start backend: %s", err.Error()) + } + + var errorMessage string + + if responseMessage.Message == "" { + errorMessage = "Unkown error while trying to start the backend" + } else { + errorMessage = fmt.Sprintf("Failed to start backend: %s", responseMessage.Message) + } + + c.JSON(http.StatusBadRequest, gin.H{ + "error": errorMessage, + }) + + return + } + default: + log.Warnf("Got illegal response type for backend: %T", responseMessage) + } + + backendruntime.RunningBackends[backendInDatabase.ID] = backend + + c.JSON(http.StatusOK, gin.H{ + "success": true, + }) +} diff --git a/backend/api/jwtcore/jwt.go b/backend/api/jwtcore/jwt.go index a40c6de..40c3412 100644 --- a/backend/api/jwtcore/jwt.go +++ b/backend/api/jwtcore/jwt.go @@ -12,7 +12,10 @@ import ( "github.com/golang-jwt/jwt/v5" ) -var JWTKey []byte +var ( + JWTKey []byte + developmentMode bool +) func SetupJWT() error { var err error @@ -32,6 +35,10 @@ func SetupJWT() error { JWTKey = []byte(jwtDataString) } + if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" { + developmentMode = true + } + return nil } @@ -83,8 +90,14 @@ func GetUserFromJWT(token string) (*dbcore.User, error) { } func Generate(uid uint) (string, error) { + timeMultiplier := 3 + + if developmentMode { + timeMultiplier = 60 * 24 + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Minute)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(timeMultiplier) * time.Minute)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Audience: []string{strconv.Itoa(int(uid))}, diff --git a/backend/api/main.go b/backend/api/main.go index d334b33..5f5b339 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -1,44 +1,34 @@ package main import ( + "encoding/base64" + "encoding/json" + "fmt" "os" + "path" + "path/filepath" "strings" + "git.terah.dev/imterah/hermes/api/backendruntime" + "git.terah.dev/imterah/hermes/api/controllers/v1/backends" "git.terah.dev/imterah/hermes/api/controllers/v1/users" "git.terah.dev/imterah/hermes/api/dbcore" "git.terah.dev/imterah/hermes/api/jwtcore" + "git.terah.dev/imterah/hermes/commonbackend" "github.com/charmbracelet/log" "github.com/gin-gonic/gin" + "github.com/urfave/cli/v2" "gorm.io/gorm" ) -func main() { - logLevel := os.Getenv("HERMES_LOG_LEVEL") +func entrypoint(cCtx *cli.Context) error { developmentMode := false if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" { + log.Warn("You have development mode enabled. This may weaken security.") developmentMode = true } - if logLevel != "" { - switch logLevel { - case "debug": - log.SetLevel(log.DebugLevel) - - case "info": - log.SetLevel(log.InfoLevel) - - case "warn": - log.SetLevel(log.WarnLevel) - - case "error": - log.SetLevel(log.ErrorLevel) - - case "fatal": - log.SetLevel(log.FatalLevel) - } - } - log.Info("Hermes is initializing...") log.Debug("Initializing database and opening it...") @@ -51,13 +41,117 @@ func main() { log.Debug("Running database migrations...") if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil { - log.Fatalf("Failed to run database migrations: %s", err) + return fmt.Errorf("Failed to run database migrations: %s", err) } log.Debug("Initializing the JWT subsystem...") if err := jwtcore.SetupJWT(); err != nil { - log.Fatalf("Failed to initialize the JWT subsystem: %s", err.Error()) + return fmt.Errorf("Failed to initialize the JWT subsystem: %s", err.Error()) + } + + log.Debug("Initializing the backend subsystem...") + + backendMetadataPath := cCtx.String("backends-path") + backendMetadata, err := os.ReadFile(backendMetadataPath) + + if err != nil { + return fmt.Errorf("Failed to read backends: %s", err.Error()) + } + + availableBackends := []*backendruntime.Backend{} + err = json.Unmarshal(backendMetadata, &availableBackends) + + if err != nil { + return fmt.Errorf("Failed to parse backends: %s", err.Error()) + } + + for _, backend := range availableBackends { + backend.Path = path.Join(filepath.Dir(backendMetadataPath), backend.Path) + } + + backendruntime.Init(availableBackends) + + log.Debug("Enumerating backends...") + + backendList := []dbcore.Backend{} + + if err := dbcore.DB.Find(&backendList).Error; err != nil { + return fmt.Errorf("Failed to enumerate backends: %s", err.Error()) + } + + for _, backend := range backendList { + log.Infof("Starting up backend #%d: %s", backend.ID, backend.Name) + + var backendRuntimeFilePath string + + for _, runtime := range backendruntime.AvailableBackends { + if runtime.Name == backend.Backend { + backendRuntimeFilePath = runtime.Path + } + } + + if backendRuntimeFilePath == "" { + log.Errorf("Unsupported backend recieved for ID %d: %s", backend.ID, backend.Backend) + continue + } + + backendInstance := backendruntime.NewBackend(backendRuntimeFilePath) + err = backendInstance.Start() + + if err != nil { + log.Errorf("Failed to start backend #%d: %s", backend.ID, err.Error()) + continue + } + + backendParameters, err := base64.StdEncoding.DecodeString(backend.BackendParameters) + + if err != nil { + log.Errorf("Failed to decode backend parameters for backend #%d: %s", backend.ID, err.Error()) + continue + } + + backendInstance.RuntimeCommands <- &commonbackend.Start{ + Type: "start", + Arguments: backendParameters, + } + + backendStartResponse := <-backendInstance.RuntimeCommands + + switch responseMessage := backendStartResponse.(type) { + case error: + log.Warnf("Failed to get response for backend #%d: %s", backend.ID, responseMessage.Error()) + + err = backendInstance.Stop() + + if err != nil { + log.Warnf("Failed to stop backend: %s", err.Error()) + } + + continue + case *commonbackend.BackendStatusResponse: + if !responseMessage.IsRunning { + err = backendInstance.Stop() + + if err != nil { + log.Warnf("Failed to start backend: %s", err.Error()) + } + + if responseMessage.Message == "" { + log.Errorf("Unkown error while trying to start the backend #%d", backend.ID) + } else { + log.Errorf("Failed to start backend: %s", responseMessage.Message) + } + + continue + } + default: + log.Errorf("Got illegal response type for backend #%d: %T", backend.ID, responseMessage) + continue + } + + backendruntime.RunningBackends[backend.ID] = backendInstance + log.Infof("Successfully started backend #%d", backend.ID) } log.Debug("Initializing API...") @@ -97,10 +191,55 @@ func main() { engine.POST("/api/v1/users/remove", users.RemoveUser) engine.POST("/api/v1/users/lookup", users.LookupUser) - log.Infof("Listening on: %s", listeningAddress) + engine.POST("/api/v1/backends/create", backends.CreateBackend) + + log.Infof("Listening on '%s'", listeningAddress) err = engine.Run(listeningAddress) if err != nil { - log.Fatalf("Error running web server: %s", err.Error()) + return fmt.Errorf("Error running web server: %s", err.Error()) + } + + return nil +} + +func main() { + logLevel := os.Getenv("HERMES_LOG_LEVEL") + + if logLevel != "" { + switch logLevel { + case "debug": + log.SetLevel(log.DebugLevel) + + case "info": + log.SetLevel(log.InfoLevel) + + case "warn": + log.SetLevel(log.WarnLevel) + + case "error": + log.SetLevel(log.ErrorLevel) + + case "fatal": + log.SetLevel(log.FatalLevel) + } + } + + app := &cli.App{ + Name: "hermes", + Usage: "port forwarding across boundaries", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "backends-path", + Aliases: []string{"b"}, + Usage: "path to the backend manifest file", + Required: true, + }, + }, + Action: entrypoint, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) } } diff --git a/backend/backends.dev.json b/backend/backends.dev.json new file mode 100644 index 0000000..9ff52ca --- /dev/null +++ b/backend/backends.dev.json @@ -0,0 +1,10 @@ +[ + { + "name": "ssh", + "path": "./sshbackend/sshbackend" + }, + { + "name": "dummy", + "path": "./dummybackend/dummybackend" + } +] diff --git a/backend/build.sh b/backend/build.sh new file mode 100755 index 0000000..a397359 --- /dev/null +++ b/backend/build.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +pushd sshbackend +go build . +strip sshbackend +popd + +pushd dummybackend +go build . +strip dummybackend +popd + +pushd externalbackendlauncher +go build . +strip externalbackendlauncher +popd + +pushd api +go build . +strip api +popd diff --git a/backend/commonbackend/unmarshal.go b/backend/commonbackend/unmarshal.go index 8d9c85b..1e689de 100644 --- a/backend/commonbackend/unmarshal.go +++ b/backend/commonbackend/unmarshal.go @@ -403,7 +403,7 @@ func Unmarshal(conn io.Reader) (string, interface{}, error) { if checkMethodByte[0] == CheckClientParametersID { checkMethod = "checkClientParameters" - } else if checkMethodByte[1] == CheckServerParametersID { + } else if checkMethodByte[0] == CheckServerParametersID { checkMethod = "checkServerParameters" } else { return "", nil, fmt.Errorf("invalid check method recieved") diff --git a/backend/externalbackendlauncher/main.go b/backend/externalbackendlauncher/main.go index 443b789..aa5e8ad 100644 --- a/backend/externalbackendlauncher/main.go +++ b/backend/externalbackendlauncher/main.go @@ -113,6 +113,7 @@ func entrypoint(cCtx *cli.Context) error { if err != nil { log.Warnf("failed to accept socket connection: %s", err.Error()) + continue } defer sock.Close() @@ -272,7 +273,7 @@ func entrypoint(cCtx *cli.Context) error { } log.Info("sleeping 5 seconds, and then restarting process") - time.Sleep(5 * time.Millisecond) + time.Sleep(5 * time.Second) } } diff --git a/backend/sshbackend/main.go b/backend/sshbackend/main.go index d7d66af..963c1bd 100644 --- a/backend/sshbackend/main.go +++ b/backend/sshbackend/main.go @@ -12,6 +12,7 @@ import ( "git.terah.dev/imterah/hermes/backendutil" "git.terah.dev/imterah/hermes/commonbackend" "github.com/charmbracelet/log" + "github.com/go-playground/validator/v10" "golang.org/x/crypto/ssh" ) @@ -32,10 +33,10 @@ type SSHBackend struct { } type SSHBackendData struct { - IP string `json:"ip"` - Port uint16 `json:"port"` - Username string `json:"username"` - PrivateKey string `json:"privateKey"` + IP string `json:"ip" validate:"required"` + Port uint16 `json:"port" validate:"required"` + Username string `json:"username" validate:"required"` + PrivateKey string `json:"privateKey" validate:"required"` ListenOnIPs []string `json:"listenOnIPs"` } @@ -43,9 +44,11 @@ func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) { log.Info("SSHBackend is initializing...") var backendData SSHBackendData - err := json.Unmarshal(bytes, &backendData) + if err := json.Unmarshal(bytes, &backendData); err != nil { + return false, err + } - if err != nil { + if err := validator.New().Struct(&backendData); err != nil { return false, err } @@ -304,6 +307,13 @@ func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonba } } + if err := validator.New().Struct(&backendData); err != nil { + return &commonbackend.CheckParametersResponse{ + IsValid: false, + Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()), + } + } + return &commonbackend.CheckParametersResponse{ IsValid: true, } diff --git a/routes/Hermes API/Backend/Create.bru b/routes/Hermes API/Backend/Create.bru index 15f623f..777809f 100644 --- a/routes/Hermes API/Backend/Create.bru +++ b/routes/Hermes API/Backend/Create.bru @@ -5,24 +5,21 @@ meta { } post { - url: http://127.0.0.1:3000/api/v1/backends/create + url: http://127.0.0.1:8000/api/v1/backends/create body: json auth: none } body:json { { - "token": "9d99397be36747b9e6f1858f1efded4756ea5b479fd5c47a6388041eecb44b4958858c6fe15f23a9cf5e9d67f48443c65342e3a69bfde231114df4bb2ab457", - "name": "Passyfire Reimpl", - "description": "PassyFire never dies", - "backend": "passyfire", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiMSJdLCJleHAiOjE3MzUwNjY1NjksIm5iZiI6MTczNDk4MDE2OSwiaWF0IjoxNzM0OTgwMTY5fQ.-9IiM8N-azqBLwrAJkKqIzZ6yuumEzErKzSefXWpzaQ", + "name": "SSH", + "backend": "ssh", "connectionDetails": { "ip": "127.0.0.1", "port": 22, - - "users": { - "g" - } + "username": "test", + "privateKey": "" } } }