Dane caused this monstrosity to occur.. Port over remaining transfer/archive code to gin, delete http.go
This commit is contained in:
@@ -46,7 +46,7 @@ func AuthorizationMiddleware(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Helper function to fetch a server out of the servers collection stored in memory.
|
||||
func GetServer (uuid string) *server.Server {
|
||||
func GetServer(uuid string) *server.Server {
|
||||
return server.GetServers().Find(func(s *server.Server) bool {
|
||||
return uuid == s.Uuid
|
||||
})
|
||||
@@ -64,4 +64,4 @@ func ServerExists(c *gin.Context) {
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func Configure() *gin.Engine {
|
||||
protected.GET("/api/system", getSystemInformation)
|
||||
protected.GET("/api/servers", getAllServers)
|
||||
protected.POST("/api/servers", postCreateServer)
|
||||
protected.POST("/api/transfer", postTransfer)
|
||||
|
||||
// These are server specific routes, and require that the request be authorized, and
|
||||
// that the server exist on the Daemon.
|
||||
@@ -54,4 +55,4 @@ func Configure() *gin.Engine {
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func patchServer(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Performs a server installation in a backgrounded thread.
|
||||
// Performs a server installation in a background thread.
|
||||
func postServerInstall(c *gin.Context) {
|
||||
s := GetServer(c.Param("server"))
|
||||
|
||||
@@ -213,4 +213,4 @@ func deleteServer(c *gin.Context) {
|
||||
}(uuid)
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pterodactyl/wings/api"
|
||||
"github.com/pterodactyl/wings/server"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Backs up a server.
|
||||
@@ -28,37 +25,3 @@ func postServerBackup(c *gin.Context) {
|
||||
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func getServerArchive(c *gin.Context) {
|
||||
}
|
||||
|
||||
func postServerArchive(c *gin.Context) {
|
||||
s := GetServer(c.Param("server"))
|
||||
|
||||
go func() {
|
||||
start := time.Now()
|
||||
|
||||
if err := s.Archiver.Archive(); err != nil {
|
||||
zap.S().Errorw("failed to get archive for server", zap.String("server", s.Uuid), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw("successfully created archive for server", zap.String("server", s.Uuid), zap.Duration("time", time.Now().Sub(start).Round(time.Microsecond)))
|
||||
|
||||
r := api.NewRequester()
|
||||
rerr, err := r.SendArchiveStatus(s.Uuid, true)
|
||||
if rerr != nil || err != nil {
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", s.Uuid), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw("panel returned an error when sending the archive status", zap.String("server", s.Uuid), zap.Error(errors.New(rerr.String())))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", s.Uuid))
|
||||
}()
|
||||
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
308
router/router_transfer.go
Normal file
308
router/router_transfer.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/pterodactyl/wings/api"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/installer"
|
||||
"github.com/pterodactyl/wings/router/tokens"
|
||||
"github.com/pterodactyl/wings/server"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getServerArchive(c *gin.Context) {
|
||||
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
||||
|
||||
if len(auth) != 2 || auth[0] != "Bearer" {
|
||||
c.Header("WWW-Authenticate", "Bearer")
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "The required authorization heads were not present in the request.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token := tokens.TransferPayload{}
|
||||
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
|
||||
TrackedError(err).AbortWithServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
if token.Subject != c.Param("server") {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "You are not authorized to access this endpoint.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s := GetServer(c.Param("server"))
|
||||
|
||||
st, err := s.Archiver.Stat()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
// zap.S().Errorw("failed to stat archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
||||
TrackedServerError(err, s).SetMessage("failed to stat archive").AbortWithServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
checksum, err := s.Archiver.Checksum()
|
||||
if err != nil {
|
||||
// zap.S().Errorw("failed to calculate checksum", zap.String("server", s.Uuid), zap.Error(err))
|
||||
TrackedServerError(err, s).SetMessage("failed to calculate checksum").AbortWithServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(s.Archiver.ArchivePath())
|
||||
if err != nil {
|
||||
tserr := TrackedServerError(err, s)
|
||||
if !os.IsNotExist(err) {
|
||||
tserr.SetMessage("failed to open archive for reading")
|
||||
// zap.S().Errorw("failed to open archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
||||
} else {
|
||||
tserr.SetMessage("failed to open archive")
|
||||
}
|
||||
|
||||
tserr.AbortWithServerError(c)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
c.Header("X-Checksum", checksum)
|
||||
c.Header("X-Mime-Type", st.Mimetype)
|
||||
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
||||
c.Header("Content-Disposition", "attachment; filename="+s.Archiver.ArchiveName())
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
|
||||
bufio.NewReader(file).WriteTo(c.Writer)
|
||||
}
|
||||
|
||||
func postServerArchive(c *gin.Context) {
|
||||
s := GetServer(c.Param("server"))
|
||||
|
||||
go func(server *server.Server) {
|
||||
start := time.Now()
|
||||
|
||||
if err := server.Archiver.Archive(); err != nil {
|
||||
zap.S().Errorw("failed to get archive for server", zap.String("server", s.Uuid), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw(
|
||||
"successfully created archive for server",
|
||||
zap.String("server", server.Uuid),
|
||||
zap.Duration("time", time.Now().Sub(start).Round(time.Microsecond)),
|
||||
)
|
||||
|
||||
r := api.NewRequester()
|
||||
rerr, err := r.SendArchiveStatus(server.Uuid, true)
|
||||
if rerr != nil || err != nil {
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", server.Uuid), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw(
|
||||
"panel returned an error when sending the archive status",
|
||||
zap.String("server", server.Uuid),
|
||||
zap.Error(errors.New(rerr.String())),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", server.Uuid))
|
||||
}(s)
|
||||
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func postTransfer(c *gin.Context) {
|
||||
zap.S().Debug("incoming transfer from panel")
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
buf.ReadFrom(c.Request.Body)
|
||||
|
||||
go func(data []byte) {
|
||||
serverID, _ := jsonparser.GetString(data, "server_id")
|
||||
url, _ := jsonparser.GetString(data, "url")
|
||||
token, _ := jsonparser.GetString(data, "token")
|
||||
|
||||
// Create an http client with no timeout.
|
||||
client := &http.Client{Timeout: 0}
|
||||
|
||||
hasError := true
|
||||
defer func() {
|
||||
if !hasError {
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw("server transfer has failed", zap.String("server", serverID))
|
||||
rerr, err := api.NewRequester().SendTransferFailure(serverID)
|
||||
if rerr != nil || err != nil {
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to notify panel with transfer failure", zap.String("server", serverID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw("panel returned an error when notifying of a transfer failure", zap.String("server", serverID), zap.Error(errors.New(rerr.String())))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw("successfully notified panel about transfer failure", zap.String("server", serverID))
|
||||
}()
|
||||
|
||||
// Make a new GET request to the URL the panel gave us.
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to create http request", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Add the authorization header.
|
||||
req.Header.Set("Authorization", token)
|
||||
|
||||
// Execute the http request.
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to send http request", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Handle non-200 status codes.
|
||||
if res.StatusCode != 200 {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to read response body", zap.Int("status", res.StatusCode), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw("failed to request server archive", zap.Int("status", res.StatusCode), zap.String("body", string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the path to the archive.
|
||||
archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID + ".tar.gz")
|
||||
|
||||
// Check if the archive already exists and delete it if it does.
|
||||
_, err = os.Stat(archivePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
zap.S().Errorw("failed to stat file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(archivePath); err != nil {
|
||||
zap.S().Errorw("failed to delete old file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create the file.
|
||||
file, err := os.Create(archivePath)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to open file on disk", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the file.
|
||||
_, err = io.Copy(file, res.Body)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to copy file to disk", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Close the file so it can be opened to verify the checksum.
|
||||
if err := file.Close(); err != nil {
|
||||
zap.S().Errorw("failed to close archive file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
zap.S().Debug("server archive has been downloaded, computing checksum..", zap.String("server", serverID))
|
||||
|
||||
// Open the archive file for computing a checksum.
|
||||
file, err = os.Open(archivePath)
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to open file on disk", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Compute the sha256 checksum of the file.
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
zap.S().Errorw("failed to copy file for checksum verification", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the two checksums.
|
||||
if hex.EncodeToString(hash.Sum(nil)) != res.Header.Get("X-Checksum") {
|
||||
zap.S().Errorw("checksum failed verification")
|
||||
return
|
||||
}
|
||||
|
||||
// Close the file.
|
||||
if err := file.Close(); err != nil {
|
||||
zap.S().Errorw("failed to close archive file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Infow("server archive transfer was successful", zap.String("server", serverID))
|
||||
|
||||
// Get the server data from the request.
|
||||
serverData, t, _, _ := jsonparser.Get(data, "server")
|
||||
if t != jsonparser.Object {
|
||||
zap.S().Errorw("invalid server data passed in request")
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debug(string(serverData))
|
||||
|
||||
// Create a new server installer (note this does not execute the install script)
|
||||
i, err := installer.New(serverData)
|
||||
if err != nil {
|
||||
zap.S().Warnw("failed to validate the received server data", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Add the server to the collection.
|
||||
server.GetServers().Add(i.Server())
|
||||
|
||||
// Create the server's environment (note this does not execute the install script)
|
||||
i.Execute()
|
||||
|
||||
// Un-archive the archive. That sounds weird..
|
||||
archiver.NewTarGz().Unarchive(archivePath, i.Server().Filesystem.Path())
|
||||
|
||||
rerr, err := api.NewRequester().SendTransferSuccess(serverID)
|
||||
if rerr != nil || err != nil {
|
||||
if err != nil {
|
||||
zap.S().Errorw("failed to notify panel with transfer success", zap.String("server", serverID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Errorw("panel returned an error when notifying of a transfer success", zap.String("server", serverID), zap.Error(errors.New(rerr.String())))
|
||||
return
|
||||
}
|
||||
|
||||
zap.S().Debugw("successfully notified panel about transfer success", zap.String("server", serverID))
|
||||
hasError = false
|
||||
}(buf.Bytes())
|
||||
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
cache2 "github.com/patrickmn/go-cache"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenStore struct {
|
||||
cache *cache2.Cache
|
||||
cache *cache.Cache
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
var _tokens *TokenStore
|
||||
|
||||
// Returns the global unqiue token store cache. This is used to validate
|
||||
// Returns the global unique token store cache. This is used to validate
|
||||
// one time token usage by storing any received tokens in a local memory
|
||||
// cache until they are ready to expire.
|
||||
func getTokenStore() *TokenStore {
|
||||
if _tokens == nil {
|
||||
_tokens = &TokenStore{
|
||||
cache: cache2.New(time.Minute*60, time.Minute*5),
|
||||
cache: cache.New(time.Minute*60, time.Minute*5),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
14
router/tokens/transfer.go
Normal file
14
router/tokens/transfer.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"github.com/gbrlsnchs/jwt/v3"
|
||||
)
|
||||
|
||||
type TransferPayload struct {
|
||||
jwt.Payload
|
||||
}
|
||||
|
||||
// GetPayload returns the JWT payload.
|
||||
func (p *TransferPayload) GetPayload() *jwt.Payload {
|
||||
return &p.Payload
|
||||
}
|
||||
Reference in New Issue
Block a user