More middleware cleanup and movement
This commit is contained in:
parent
f6669213e8
commit
b17cf5b93d
|
@ -1,96 +1,25 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/pterodactyl/wings/router/middleware"
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Middleware struct{}
|
// GetServer is a helper function to fetch a server out of the servers
|
||||||
|
// collection stored in memory. This function should not be used in new
|
||||||
// RequireAuthorization authenticates the request token against the given
|
// controllers, prefer ExtractServer where possible.
|
||||||
// permission string, ensuring that if it is a server permission, the token has
|
// Deprecated
|
||||||
// control over that server. If it is a global token, this will ensure that the
|
|
||||||
// request is using a properly signed global token.
|
|
||||||
func (m *Middleware) RequireAuthorization() gin.HandlerFunc {
|
|
||||||
token := config.Get().AuthenticationToken
|
|
||||||
return func(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// All requests to Wings must be authorized with the authentication token present in
|
|
||||||
// the Wings configuration file. Remeber, all requests to Wings come from the Panel
|
|
||||||
// backend, or using a signed JWT for temporary authentication.
|
|
||||||
if auth[1] == token {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
||||||
"error": "You are not authorized to access this endpoint.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to fetch a server out of the servers collection stored in memory.
|
|
||||||
//
|
|
||||||
// This function should not be used in new controllers, prefer ExtractServer where
|
|
||||||
// possible.
|
|
||||||
func GetServer(uuid string) *server.Server {
|
func GetServer(uuid string) *server.Server {
|
||||||
return server.GetServers().Find(func(s *server.Server) bool {
|
return server.GetServers().Find(func(s *server.Server) bool {
|
||||||
return uuid == s.Id()
|
return uuid == s.Id()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the requested server exists in this setup. Returns a 404 if we cannot
|
// ExtractServer returns the server instance from the gin context. If there is
|
||||||
// locate it.
|
// no server set in the context (e.g. calling from a controller not protected by
|
||||||
func (m *Middleware) ServerExists() gin.HandlerFunc {
|
// ServerExists) this function will panic.
|
||||||
return func(c *gin.Context) {
|
// Deprecated
|
||||||
u, err := uuid.Parse(c.Param("server"))
|
|
||||||
if err == nil {
|
|
||||||
if s := GetServer(u.String()); s != nil {
|
|
||||||
c.Set("server", s)
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": "The resource you requested does not exist.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if remote file downloading is enabled on this instance before allowing access
|
|
||||||
// to the given endpoint.
|
|
||||||
func (m *Middleware) CheckRemoteDownloadEnabled() gin.HandlerFunc {
|
|
||||||
disabled := config.Get().Api.DisableRemoteDownload
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
if disabled {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": "This functionality is not currently enabled on this instance.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the server instance from the gin context. If there is no server set in the
|
|
||||||
// context (e.g. calling from a controller not protected by ServerExists) this function
|
|
||||||
// will panic.
|
|
||||||
func ExtractServer(c *gin.Context) *server.Server {
|
func ExtractServer(c *gin.Context) *server.Server {
|
||||||
if s, ok := c.Get("server"); ok {
|
return middleware.ExtractServer(c)
|
||||||
return s.(*server.Server)
|
|
||||||
}
|
|
||||||
panic(errors.New("cannot extract server, missing on gin context"))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/subtle"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -150,7 +151,9 @@ func (re *RequestError) asFilesystemError() (int, string) {
|
||||||
// so that you can easily cross-reference the errors.
|
// so that you can easily cross-reference the errors.
|
||||||
func AttachRequestID() gin.HandlerFunc {
|
func AttachRequestID() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Header("X-Request-Id", uuid.New().String())
|
id := uuid.New().String()
|
||||||
|
c.Header("X-Request-Id", id)
|
||||||
|
c.Set("logger", log.WithField("request_id", id))
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,3 +231,84 @@ func SetAccessControlHeaders() gin.HandlerFunc {
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerExists will ensure that the requested server exists in this setup.
|
||||||
|
// Returns a 404 if we cannot locate it. If the server is found it is set into
|
||||||
|
// the request context, and the logger for the context is also updated to include
|
||||||
|
// the server ID in the fields list.
|
||||||
|
func ServerExists() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
s := server.GetServers().Find(func(s *server.Server) bool {
|
||||||
|
return c.Param("server") == s.Id()
|
||||||
|
})
|
||||||
|
if s == nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "The requested resource does not exist on this instance."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Set("logger", ExtractLogger(c).WithField("server_id", s.Id()))
|
||||||
|
c.Set("server", s)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireAuthorization authenticates the request token against the given
|
||||||
|
// permission string, ensuring that if it is a server permission, the token has
|
||||||
|
// control over that server. If it is a global token, this will ensure that the
|
||||||
|
// request is using a properly signed global token.
|
||||||
|
func RequireAuthorization() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// We don't put this value outside this function since the node's authentication
|
||||||
|
// token can be changed on the fly and the config.Get() call returns a copy, so
|
||||||
|
// if it is rotated this value will never properly get updated.
|
||||||
|
token := config.Get().AuthenticationToken
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// All requests to Wings must be authorized with the authentication token present in
|
||||||
|
// the Wings configuration file. Remeber, all requests to Wings come from the Panel
|
||||||
|
// backend, or using a signed JWT for temporary authentication.
|
||||||
|
if subtle.ConstantTimeCompare([]byte(auth[1]), []byte(token)) != 1 {
|
||||||
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "You are not authorized to access this endpoint."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteDownloadEnabled checks if remote downloads are enabled for this instance
|
||||||
|
// and if not aborts the request.
|
||||||
|
func RemoteDownloadEnabled() gin.HandlerFunc {
|
||||||
|
disabled := config.Get().Api.DisableRemoteDownload
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if disabled {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "This functionality is not currently enabled on this instance."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractLogger pulls the logger out of the request context and returns it. By
|
||||||
|
// default this will include the request ID, but may also include the server ID
|
||||||
|
// if that middleware has been used in the chain by the time it is called.
|
||||||
|
func ExtractLogger(c *gin.Context) *log.Entry {
|
||||||
|
v, ok := c.Get("logger")
|
||||||
|
if !ok {
|
||||||
|
panic("middleware/middleware: cannot extract logger: not present in request context")
|
||||||
|
}
|
||||||
|
return v.(*log.Entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractServer will return the server from the gin.Context or panic if it is
|
||||||
|
// not present.
|
||||||
|
func ExtractServer(c *gin.Context) *server.Server {
|
||||||
|
v, ok := c.Get("server")
|
||||||
|
if !ok {
|
||||||
|
panic("middleware/middleware: cannot extract server: not present in request context")
|
||||||
|
}
|
||||||
|
return v.(*server.Server)
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import (
|
||||||
func Configure() *gin.Engine {
|
func Configure() *gin.Engine {
|
||||||
gin.SetMode("release")
|
gin.SetMode("release")
|
||||||
|
|
||||||
m := Middleware{}
|
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
router.Use(middleware.AttachRequestID(), middleware.CaptureErrors(), middleware.SetAccessControlHeaders())
|
router.Use(middleware.AttachRequestID(), middleware.CaptureErrors(), middleware.SetAccessControlHeaders())
|
||||||
|
@ -41,16 +40,16 @@ func Configure() *gin.Engine {
|
||||||
// This route is special it sits above all of the other requests because we are
|
// This route is special it sits above all of the other requests because we are
|
||||||
// using a JWT to authorize access to it, therefore it needs to be publicly
|
// using a JWT to authorize access to it, therefore it needs to be publicly
|
||||||
// accessible.
|
// accessible.
|
||||||
router.GET("/api/servers/:server/ws", m.ServerExists(), getServerWebsocket)
|
router.GET("/api/servers/:server/ws", middleware.ServerExists(), getServerWebsocket)
|
||||||
|
|
||||||
// This request is called by another daemon when a server is going to be transferred out.
|
// This request is called by another daemon when a server is going to be transferred out.
|
||||||
// This request does not need the AuthorizationMiddleware as the panel should never call it
|
// This request does not need the AuthorizationMiddleware as the panel should never call it
|
||||||
// and requests are authenticated through a JWT the panel issues to the other daemon.
|
// and requests are authenticated through a JWT the panel issues to the other daemon.
|
||||||
router.GET("/api/servers/:server/archive", m.ServerExists(), getServerArchive)
|
router.GET("/api/servers/:server/archive", middleware.ServerExists(), getServerArchive)
|
||||||
|
|
||||||
// All of the routes beyond this mount will use an authorization middleware
|
// All of the routes beyond this mount will use an authorization middleware
|
||||||
// and will not be accessible without the correct Authorization header provided.
|
// and will not be accessible without the correct Authorization header provided.
|
||||||
protected := router.Use(m.RequireAuthorization())
|
protected := router.Use(middleware.RequireAuthorization())
|
||||||
protected.POST("/api/update", postUpdateConfiguration)
|
protected.POST("/api/update", postUpdateConfiguration)
|
||||||
protected.GET("/api/system", getSystemInformation)
|
protected.GET("/api/system", getSystemInformation)
|
||||||
protected.GET("/api/servers", getAllServers)
|
protected.GET("/api/servers", getAllServers)
|
||||||
|
@ -60,7 +59,7 @@ func Configure() *gin.Engine {
|
||||||
// These are server specific routes, and require that the request be authorized, and
|
// These are server specific routes, and require that the request be authorized, and
|
||||||
// that the server exist on the Daemon.
|
// that the server exist on the Daemon.
|
||||||
server := router.Group("/api/servers/:server")
|
server := router.Group("/api/servers/:server")
|
||||||
server.Use(m.RequireAuthorization(), m.ServerExists())
|
server.Use(middleware.RequireAuthorization(), middleware.ServerExists())
|
||||||
{
|
{
|
||||||
server.GET("", getServer)
|
server.GET("", getServer)
|
||||||
server.PATCH("", patchServer)
|
server.PATCH("", patchServer)
|
||||||
|
@ -90,9 +89,9 @@ func Configure() *gin.Engine {
|
||||||
files.POST("/decompress", postServerDecompressFiles)
|
files.POST("/decompress", postServerDecompressFiles)
|
||||||
files.POST("/chmod", postServerChmodFile)
|
files.POST("/chmod", postServerChmodFile)
|
||||||
|
|
||||||
files.GET("/pull", m.CheckRemoteDownloadEnabled(), getServerPullingFiles)
|
files.GET("/pull", middleware.RemoteDownloadEnabled(), getServerPullingFiles)
|
||||||
files.POST("/pull", m.CheckRemoteDownloadEnabled(), postServerPullRemoteFile)
|
files.POST("/pull", middleware.RemoteDownloadEnabled(), postServerPullRemoteFile)
|
||||||
files.DELETE("/pull/:download", m.CheckRemoteDownloadEnabled(), deleteServerPullRemoteFile)
|
files.DELETE("/pull/:download", middleware.RemoteDownloadEnabled(), deleteServerPullRemoteFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
backup := server.Group("/backup")
|
backup := server.Group("/backup")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user