Compare commits
2 commits
5495fc4ae2
...
af6ee6ab66
Author | SHA1 | Date | |
---|---|---|---|
af6ee6ab66 | |||
d73380e647 |
7 changed files with 656 additions and 1 deletions
|
@ -1,4 +1,4 @@
|
||||||
package forward
|
package proxies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
127
backend/api/controllers/v1/proxies/create.go
Normal file
127
backend/api/controllers/v1/proxies/create.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package proxies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyCreationRequest struct {
|
||||||
|
Token string `validate:"required" json:"token"`
|
||||||
|
Name string `validate:"required" json:"name"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Protcol string `validate:"required" json:"protcol"`
|
||||||
|
SourceIP string `validate:"required" json:"source_ip"`
|
||||||
|
SourcePort uint16 `validate:"required" json:"source_port"`
|
||||||
|
DestinationPort uint16 `validate:"required" json:"destination_port"`
|
||||||
|
ProviderID uint `validate:"required" json:"provider_id"`
|
||||||
|
AutoStart bool `json:"auto_start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyCreationResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Id uint `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateProxy(c *gin.Context) {
|
||||||
|
var req ProxyCreationRequest
|
||||||
|
|
||||||
|
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, "routes.add") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Protcol != "tcp" && req.Protcol != "udp" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Body protocol must be either 'tcp' or 'udp'",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var backend dbcore.Backend
|
||||||
|
backendRequest := dbcore.DB.Where("id = ?", req.ProviderID).First(&backend)
|
||||||
|
if backendRequest.Error != nil {
|
||||||
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error)
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to find if provider exists",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
backendExists := backendRequest.RowsAffected > 0
|
||||||
|
if !backendExists {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Could not find provider",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := &dbcore.Proxy{
|
||||||
|
UserID: user.ID,
|
||||||
|
BackendID: req.ProviderID,
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
Protocol: req.Protcol,
|
||||||
|
SourceIP: req.SourceIP,
|
||||||
|
SourcePort: req.SourcePort,
|
||||||
|
DestinationPort: req.DestinationPort,
|
||||||
|
AutoStart: req.AutoStart,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := dbcore.DB.Create(proxy); result.Error != nil {
|
||||||
|
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to add forward rule to database",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, &ProxyCreationResponse{
|
||||||
|
Success: true,
|
||||||
|
Id: proxy.ID,
|
||||||
|
})
|
||||||
|
}
|
174
backend/api/controllers/v1/proxies/lookup.go
Normal file
174
backend/api/controllers/v1/proxies/lookup.go
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
package proxies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyLookupRequest struct {
|
||||||
|
Token string `validate:"required" json:"token"`
|
||||||
|
Id *uint `json:"id"`
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Protocol *string `json:"protocol"`
|
||||||
|
SourceIP *string `json:"source_ip"`
|
||||||
|
SourcePort *uint16 `json:"source_port"`
|
||||||
|
DestinationPort *uint16 `json:"destination_port"`
|
||||||
|
ProviderID *uint `json:"provider_id"`
|
||||||
|
AutoStart *bool `json:"auto_start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SanitizedProxy struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Protcol string `json:"protcol"`
|
||||||
|
SourceIP string `json:"source_ip"`
|
||||||
|
SourcePort uint16 `json:"source_port"`
|
||||||
|
DestinationPort uint16 `json:"destination_port"`
|
||||||
|
ProviderID uint `json:"provider_id"`
|
||||||
|
AutoStart bool `json:"auto_start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyLookupResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Data []*SanitizedProxy `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupProxy(c *gin.Context) {
|
||||||
|
var req ProxyLookupRequest
|
||||||
|
|
||||||
|
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, "routes.visible") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *req.Protcol != "tcp" && *req.Protcol != "udp" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Protocol specified in body must either be 'tcp' or 'udp'",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies := []dbcore.Proxy{}
|
||||||
|
queryString := []string{}
|
||||||
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
if req.Id != nil {
|
||||||
|
queryString = append(queryString, "id = ?")
|
||||||
|
queryParameters = append(queryParameters, req.Id)
|
||||||
|
}
|
||||||
|
if req.Name != nil {
|
||||||
|
queryString = append(queryString, "name = ?")
|
||||||
|
queryParameters = append(queryParameters, req.Name)
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
queryString = append(queryString, "description = ?")
|
||||||
|
queryParameters = append(queryParameters, req.Description)
|
||||||
|
}
|
||||||
|
if req.SourceIP != nil {
|
||||||
|
queryString = append(queryString, "name = ?")
|
||||||
|
queryParameters = append(queryParameters, req.Name)
|
||||||
|
}
|
||||||
|
if req.SourcePort != nil {
|
||||||
|
queryString = append(queryString, "sourceport = ?")
|
||||||
|
queryParameters = append(queryParameters, req.SourcePort)
|
||||||
|
}
|
||||||
|
if req.DestinationPort != nil {
|
||||||
|
queryString = append(queryString, "destinationport = ?")
|
||||||
|
queryParameters = append(queryParameters, req.DestinationPort)
|
||||||
|
}
|
||||||
|
if req.ProviderID != nil {
|
||||||
|
queryString = append(queryString, "backendid = ?")
|
||||||
|
queryParameters = append(queryParameters, req.ProviderID)
|
||||||
|
}
|
||||||
|
if req.AutoStart != nil {
|
||||||
|
queryString = append(queryString, "autostart = ?")
|
||||||
|
queryParameters = append(queryParameters, req.AutoStart)
|
||||||
|
}
|
||||||
|
if req.Protocol != nil {
|
||||||
|
queryString = append(queryString, "protocol = ?")
|
||||||
|
queryParameters = append(queryParameters, req.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
||||||
|
log.Warnf("failed to get proxies: %s", err.Error())
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "failed to get forward rules",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizedProxies := make([]*SanitizedProxy, len(proxies))
|
||||||
|
|
||||||
|
for proxyIndex, proxy := range proxies {
|
||||||
|
description := ""
|
||||||
|
if proxy.Description != nil {
|
||||||
|
description = *proxy.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizedProxies[proxyIndex] = &SanitizedProxy{
|
||||||
|
Id: proxy.ID,
|
||||||
|
Name: proxy.Name,
|
||||||
|
Description: &description,
|
||||||
|
Protcol: proxy.Protocol,
|
||||||
|
SourceIP: proxy.SourceIP,
|
||||||
|
SourcePort: proxy.SourcePort,
|
||||||
|
DestinationPort: proxy.DestinationPort,
|
||||||
|
ProviderID: proxy.BackendID,
|
||||||
|
AutoStart: proxy.AutoStart,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, &ProxyLookupResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: sanitizedProxies,
|
||||||
|
})
|
||||||
|
}
|
106
backend/api/controllers/v1/proxies/remove.go
Normal file
106
backend/api/controllers/v1/proxies/remove.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package proxies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyRemovalRequest struct {
|
||||||
|
Token string `validate:"required" json:"token"`
|
||||||
|
ID uint `validate:"required" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyRemovalResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveProxy(c *gin.Context) {
|
||||||
|
var req ProxyRemovalRequest
|
||||||
|
|
||||||
|
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, "routes.remove") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy *dbcore.Proxy
|
||||||
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
|
if proxyRequest.Error != nil {
|
||||||
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error)
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to find if forward rule exists",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyExists := proxyRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !proxyExists {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Forward rule doesn't exist",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbcore.DB.Delete(proxy).Error; err != nil {
|
||||||
|
log.Warnf("failed to delete proxy: %s", err.Error())
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to delete forward rule",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, &ProxyRemovalResponse{
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
133
backend/api/controllers/v1/proxies/start.go
Normal file
133
backend/api/controllers/v1/proxies/start.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package proxies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 ProxyStartRequest struct {
|
||||||
|
Token string `validate:"required" json:"token"`
|
||||||
|
ID uint `validate:"required" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyStartResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartProxy(c *gin.Context) {
|
||||||
|
var req ProxyStartRequest
|
||||||
|
|
||||||
|
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, "routes.start") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy *dbcore.Proxy
|
||||||
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
|
if proxyRequest.Error != nil {
|
||||||
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error)
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to find if forward rule exists",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyExists := proxyRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !proxyExists {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Forward rule doesn't exist",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := backendruntime.RunningBackends[proxy.BackendID]
|
||||||
|
|
||||||
|
backend.RuntimeCommands <- commonbackend.AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
|
SourceIP: proxy.SourceIP,
|
||||||
|
SourcePort: proxy.SourcePort,
|
||||||
|
DestPort: proxy.DestinationPort,
|
||||||
|
Protocol: proxy.Protocol,
|
||||||
|
}
|
||||||
|
|
||||||
|
backendResponse := <-backend.RuntimeCommands
|
||||||
|
|
||||||
|
switch responseMessage := backendResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, responseMessage.Error())
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "failed to get response from backend",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
case *commonbackend.ProxyStatusResponse:
|
||||||
|
if !responseMessage.IsActive {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "failed to start proxy",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, &ProxyStartResponse{
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
108
backend/api/controllers/v1/proxies/stop.go
Normal file
108
backend/api/controllers/v1/proxies/stop.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package proxies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 ProxyStopRequest struct {
|
||||||
|
Token string `validate:"required" json:"token"`
|
||||||
|
ID uint `validate:"required" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyStopResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopProxy(c *gin.Context) {
|
||||||
|
var req ProxyStopRequest
|
||||||
|
|
||||||
|
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, "routes.stop") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy *dbcore.Proxy
|
||||||
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
|
if proxyRequest.Error != nil {
|
||||||
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error)
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to find if forward rule exists",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyExists := proxyRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !proxyExists {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Forward rule doesn't exist",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := backendruntime.RunningBackends[proxy.BackendID]
|
||||||
|
|
||||||
|
backend.RuntimeCommands <- commonbackend.RemoveProxy{
|
||||||
|
Type: "removeProxy",
|
||||||
|
SourceIP: proxy.SourceIP,
|
||||||
|
SourcePort: proxy.SourcePort,
|
||||||
|
DestPort: proxy.DestinationPort,
|
||||||
|
Protocol: proxy.Protocol,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, &ProxyStopResponse{
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/api/controllers/v1/backends"
|
"git.terah.dev/imterah/hermes/api/controllers/v1/backends"
|
||||||
|
"git.terah.dev/imterah/hermes/api/controllers/v1/proxies"
|
||||||
"git.terah.dev/imterah/hermes/api/controllers/v1/users"
|
"git.terah.dev/imterah/hermes/api/controllers/v1/users"
|
||||||
"git.terah.dev/imterah/hermes/api/dbcore"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/api/jwtcore"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
@ -195,6 +196,12 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
engine.POST("/api/v1/backends/remove", backends.RemoveBackend)
|
engine.POST("/api/v1/backends/remove", backends.RemoveBackend)
|
||||||
engine.POST("/api/v1/backends/lookup", backends.LookupBackend)
|
engine.POST("/api/v1/backends/lookup", backends.LookupBackend)
|
||||||
|
|
||||||
|
engine.POST("/api/v1/forward/create", proxies.CreateProxy)
|
||||||
|
engine.POST("/api/v1/forward/lookup", proxies.LookupProxy)
|
||||||
|
engine.POST("/api/v1/forward/remove", proxies.RemoveProxy)
|
||||||
|
engine.POST("/api/v1/forward/start", proxies.StartProxy)
|
||||||
|
engine.POST("/api/v1/forward/stop", proxies.StopProxy)
|
||||||
|
|
||||||
log.Infof("Listening on '%s'", listeningAddress)
|
log.Infof("Listening on '%s'", listeningAddress)
|
||||||
err = engine.Run(listeningAddress)
|
err = engine.Run(listeningAddress)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue