From 1899b1ab4b6c14217e6fa3289e344a0059212bdf Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 8 Sep 2019 17:40:06 -0700 Subject: [PATCH] Add logic to handle authenticating a websocket with the panel --- config/config.go | 12 ++++++++++ http.go | 5 +--- websocket.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++- wings.go | 8 +++++++ 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index efb185d..e006000 100644 --- a/config/config.go +++ b/config/config.go @@ -200,6 +200,18 @@ func ReadConfiguration(path string) (*Configuration, error) { 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 // owner of all data in the root data directory and is used as the user within containers. // diff --git a/http.go b/http.go index 8d16e58..593834e 100644 --- a/http.go +++ b/http.go @@ -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 // exposing the token in the URL directly. Neat. 📸 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" { w.Header().Set("WWW-Authenticate", "Bearer") @@ -408,7 +405,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router { router.GET("/", rt.routeIndex) router.GET("/api/servers", rt.AuthenticateToken(rt.routeAllServers)) 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/files/contents", rt.AuthenticateRequest(rt.routeServerFileRead)) router.GET("/api/servers/:server/files/list-directory", rt.AuthenticateRequest(rt.routeServerListDirectory)) diff --git a/websocket.go b/websocket.go index 0bb53f8..34b4a40 100644 --- a/websocket.go +++ b/websocket.go @@ -1,11 +1,16 @@ package main import ( + "bytes" + "encoding/json" "errors" + "fmt" "github.com/gorilla/websocket" "github.com/julienschmidt/httprouter" + "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/server" "go.uber.org/zap" + "io/ioutil" "net/http" "os" "strings" @@ -40,6 +45,54 @@ type WebsocketHandler struct { 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 // 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) { @@ -50,6 +103,8 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http } defer c.Close() + fmt.Println("running websocket route") + s := rt.Servers.Get(ps.ByName("server")) handler := WebsocketHandler{ Server: s, @@ -74,7 +129,7 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http handleResourceUse := func(data string) { handler.SendJson(&WebsocketMessage{ 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 { j := WebsocketMessage{inbound: true} + fmt.Println("running for{} loop...") if _, _, err := c.ReadMessage(); err != nil { 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 // from the socket, which is NOT what we want to do. if err := c.ReadJSON(&j); err != nil { + fmt.Println("ReadJSON() error") continue } @@ -112,6 +169,8 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http } else { zap.S().Debugw("handled event", zap.String("event", j.Event), zap.Strings("args", j.Args)) } + + fmt.Println("finished looping...") } } diff --git a/wings.go b/wings.go index 0f947a3..b0d22a7 100644 --- a/wings.go +++ b/wings.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "flag" "fmt" "github.com/gorilla/websocket" @@ -37,8 +38,15 @@ func main() { if c.Debug { 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) if su, err := c.EnsurePterodactylUser(); err != nil { zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err))