Add logic to handle authenticating a websocket with the panel

This commit is contained in:
Dane Everitt 2019-09-08 17:40:06 -07:00
parent 806afc6ed6
commit 1899b1ab4b
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
4 changed files with 81 additions and 5 deletions

View File

@ -200,6 +200,18 @@ func ReadConfiguration(path string) (*Configuration, error) {
return c, nil return c, nil
} }
var _config *Configuration
// Set the global configuration instance.
func Set(c *Configuration) {
_config = c
}
// Get the global configuration instance.
func Get() *Configuration {
return _config
}
// Ensures that the Pterodactyl core user exists on the system. This user will be the // Ensures that the Pterodactyl core user exists on the system. This user will be the
// owner of all data in the root data directory and is used as the user within containers. // owner of all data in the root data directory and is used as the user within containers.
// //

View File

@ -67,9 +67,6 @@ func (rt *Router) AuthenticateToken(h httprouter.Handle) httprouter.Handle {
// protocol header and use that to pass the authorization token along to Wings without // protocol header and use that to pass the authorization token along to Wings without
// exposing the token in the URL directly. Neat. 📸 // exposing the token in the URL directly. Neat. 📸
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2) auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if r.Header.Get("Sec-WebSocket-Protocol") != "" {
auth = []string{"Bearer", r.Header.Get("Sec-WebSocket-Protocol")}
}
if len(auth) != 2 || auth[0] != "Bearer" { if len(auth) != 2 || auth[0] != "Bearer" {
w.Header().Set("WWW-Authenticate", "Bearer") w.Header().Set("WWW-Authenticate", "Bearer")
@ -408,7 +405,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
router.GET("/", rt.routeIndex) router.GET("/", rt.routeIndex)
router.GET("/api/servers", rt.AuthenticateToken(rt.routeAllServers)) router.GET("/api/servers", rt.AuthenticateToken(rt.routeAllServers))
router.GET("/api/servers/:server", rt.AuthenticateRequest(rt.routeServer)) router.GET("/api/servers/:server", rt.AuthenticateRequest(rt.routeServer))
router.GET("/api/servers/:server/ws", rt.AuthenticateRequest(rt.routeWebsocket)) router.GET("/api/servers/:server/ws/:token", rt.AuthenticateServer(rt.AuthenticateWebsocket(rt.routeWebsocket)))
router.GET("/api/servers/:server/logs", rt.AuthenticateRequest(rt.routeServerLogs)) router.GET("/api/servers/:server/logs", rt.AuthenticateRequest(rt.routeServerLogs))
router.GET("/api/servers/:server/files/contents", rt.AuthenticateRequest(rt.routeServerFileRead)) router.GET("/api/servers/:server/files/contents", rt.AuthenticateRequest(rt.routeServerFileRead))
router.GET("/api/servers/:server/files/list-directory", rt.AuthenticateRequest(rt.routeServerListDirectory)) router.GET("/api/servers/:server/files/list-directory", rt.AuthenticateRequest(rt.routeServerListDirectory))

View File

@ -1,11 +1,16 @@
package main package main
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -40,6 +45,54 @@ type WebsocketHandler struct {
Connection *websocket.Conn Connection *websocket.Conn
} }
type socketCredentials struct {
ServerUuid string `json:"server_uuid"`
}
func (rt *Router) AuthenticateWebsocket(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s := rt.Servers.Get(ps.ByName("server"))
j, err := json.Marshal(socketCredentials{ServerUuid: s.Uuid})
if err != nil {
zap.S().Errorw("failed to marshal json", zap.Error(err))
http.Error(w, "failed to marshal json", http.StatusInternalServerError)
return
}
url := strings.TrimRight(config.Get().PanelLocation, "/") + "/api/remote/websocket/" + ps.ByName("token")
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(j))
if err != nil {
zap.S().Errorw("failed to generate a new HTTP request when validating websocket credentials", zap.Error(err))
http.Error(w, "failed to generate HTTP request", http.StatusInternalServerError)
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+config.Get().AuthenticationToken)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
zap.S().Errorw("failed to perform client HTTP request", zap.Error(err))
http.Error(w, "failed to perform client HTTP request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
b, _ := ioutil.ReadAll(resp.Body)
zap.S().Warnw("failed to validate token with server", zap.String("response", string(b)))
http.Error(w, "failed to validate token with server", resp.StatusCode)
return
}
h(w, r, ps)
}
}
// Handle a request for a specific server websocket. This will handle inbound requests as well // Handle a request for a specific server websocket. This will handle inbound requests as well
// as ensure that any console output is also passed down the wire on the socket. // as ensure that any console output is also passed down the wire on the socket.
func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
@ -50,6 +103,8 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
} }
defer c.Close() defer c.Close()
fmt.Println("running websocket route")
s := rt.Servers.Get(ps.ByName("server")) s := rt.Servers.Get(ps.ByName("server"))
handler := WebsocketHandler{ handler := WebsocketHandler{
Server: s, Server: s,
@ -74,7 +129,7 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
handleResourceUse := func(data string) { handleResourceUse := func(data string) {
handler.SendJson(&WebsocketMessage{ handler.SendJson(&WebsocketMessage{
Event: server.StatsEvent, Event: server.StatsEvent,
Args: []string{data}, Args: []string{data},
}) })
} }
@ -91,6 +146,7 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
for { for {
j := WebsocketMessage{inbound: true} j := WebsocketMessage{inbound: true}
fmt.Println("running for{} loop...")
if _, _, err := c.ReadMessage(); err != nil { if _, _, err := c.ReadMessage(); err != nil {
if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived, websocket.CloseServiceRestart) { if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived, websocket.CloseServiceRestart) {
@ -103,6 +159,7 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
// specific socket request. If we did a break here the client would get disconnected // specific socket request. If we did a break here the client would get disconnected
// from the socket, which is NOT what we want to do. // from the socket, which is NOT what we want to do.
if err := c.ReadJSON(&j); err != nil { if err := c.ReadJSON(&j); err != nil {
fmt.Println("ReadJSON() error")
continue continue
} }
@ -112,6 +169,8 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
} else { } else {
zap.S().Debugw("handled event", zap.String("event", j.Event), zap.Strings("args", j.Args)) zap.S().Debugw("handled event", zap.String("event", j.Event), zap.Strings("args", j.Args))
} }
fmt.Println("finished looping...")
} }
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"crypto/tls"
"flag" "flag"
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -37,8 +38,15 @@ func main() {
if c.Debug { if c.Debug {
zap.S().Debugw("running in debug mode") zap.S().Debugw("running in debug mode")
zap.S().Infow("certificate checking is disabled")
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
} }
config.Set(c)
zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.User) zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.User)
if su, err := c.EnsurePterodactylUser(); err != nil { if su, err := c.EnsurePterodactylUser(); err != nil {
zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err)) zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err))