Compare commits

..

24 Commits

Author SHA1 Message Date
Dane Everitt
96256ac63e [security] fix vulnerability when handling remote file redirects
Also adds the ability for an admin to just completely disable this service if it is not needed on the node.
2021-01-09 17:52:27 -08:00
Dane Everitt
6701aa6dc1 Merge branch 'dane/self-upgrade' into develop 2021-01-09 17:38:18 -08:00
Dane Everitt
ff8926bba8 bye bye command 2021-01-09 17:37:58 -08:00
Dane Everitt
217ca72eb3 Merge pull request #85 from pterodactyl/schrej/formatting
organize imports with gopls format
2021-01-09 17:31:46 -08:00
Jakob Schrettenbrunner
648072436f organize imports with gopls format 2021-01-10 01:22:39 +00:00
Dane Everitt
6fe2468a5a foundation for self-upgrade logic 2021-01-08 22:49:19 -08:00
Dane Everitt
948d927eb9 Cleanup command running a bit 2021-01-08 22:19:23 -08:00
Dane Everitt
b2eaa3f7f8 Update CHANGELOG.md 2021-01-08 21:31:06 -08:00
Dane Everitt
93417dddb1 Update CHANGELOG.md 2021-01-08 21:23:25 -08:00
Dane Everitt
044c46fc9a Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2021-01-08 21:21:37 -08:00
Dane Everitt
c9d972d544 Revert usage of ContainerWait, return to io.Copy blocking
Until https://github.com/moby/moby/issues/41827 is resolved this code causes chaos to unfold on machines and causes servers to be non-terminatable.

This logic was intially changed to logical purposes, but this io.Copy logic works perfectly fine (even if not immediately intuitive).
2021-01-08 21:21:09 -08:00
Matthew Penner
0aab4b1ac2 environment(docker): re-attach to container logs after EOF 2021-01-08 08:19:33 -07:00
Matthew Penner
4f4b4fd2e6 environment(docker): cleanup code 2021-01-08 08:15:40 -07:00
Matthew Penner
66c9be357c Potential fix for servers being marked as stopping after being marked as offline 2021-01-07 19:32:15 -07:00
Matthew Penner
1d36811dfe Fix v being shown twice on wings boot 2021-01-07 16:44:09 -07:00
Dane Everitt
6e74123c65 Update CHANGELOG.md 2021-01-06 21:42:09 -08:00
Dane Everitt
b82f5f9a32 [security] deny downloading files from internal locations 2021-01-06 21:34:18 -08:00
Dane Everitt
1937d0366d cleanup; fix environment stats not reporting network TX correctly 2021-01-06 20:47:44 -08:00
Dane Everitt
963a906c30 Less obtuse logic for polling resource usage when attaching a container 2021-01-06 20:36:29 -08:00
Jakob
3f6eb7e41a no need for additional decode (#81)
file paths used to be url-encoded twice, which is no longer the case.
2021-01-03 17:20:16 -08:00
Omar Kamel
a822c7c340 typo in docker-compose file (#82)
minor typo i noticed while messing around
2021-01-03 16:24:28 -08:00
Matthew Penner
b8fb86f5a4 Update Dockerfile to use busybox 1.33.0 2021-01-03 12:46:06 -07:00
Matthew Penner
ee0c7f09b3 Fix user problems when running inside of Docker 2021-01-02 12:58:58 -07:00
Matthew Penner
d3ddf8cf39 Mark server as not transferring after archive failure 2021-01-02 10:11:25 -07:00
63 changed files with 576 additions and 338 deletions

View File

@@ -48,3 +48,9 @@ debug
.DS_Store
*.pprof
*.pdf
Dockerfile
CHANGELOG.md
Makefile
README.md
wings-api.paw

View File

@@ -1,5 +1,30 @@
# Changelog
## v1.2.3
### Fixed
* **[Security]** Fixes a remaining security vulnerability in the code handling remote file downloads for servers relating to redirect validation.
### Added
* Adds a configuration key at `api.disable_remote_download` that can be set to `true` to completely download the remote download system.
## v1.2.2
### Fixed
* Reverts changes to logic handling blocking until a server process is done running when polling stats. This change exposed a bug in the underlying Docker system causing servers to enter a state in which Wings was unable to terminate the process and Docker commands would hang if executed against the container.
### Changed
* Adds logic to handle a console stream unexpectedly returning an EOF when reading console logs. New code should automatically re-attach the stream avoiding issues where the console would stop live updating for servers.
## v1.2.1
### Fixed
* Fixes servers not be properly marked as no longer transfering if an error occurs during the archive process.
* Fixes problems with user detection when running Wings inside a Docker container.
* Fixes filename decoding issues with multiple endpoints related to the file manager (namely move/copy/delete).
* **[Security]** Fixes vulnerability allowing a malicious user to abuse the remote file download utilitity to scan or access resources on the local network.
* Fixes network `tx` stats not correctly being reported (was previously reporting `rx` for both `rx` and `tx`).
### Changed
* Cleans up the logic related to polling resources for the server to make a little more sense and not do pointless `io.Copy()` operations.
## v1.2.0
### Fixed
* Fixes log compression being set on the Docker containers being created to avoid errors on some versions of Docker.

View File

@@ -24,16 +24,9 @@ RUN upx wings
# --------------------------------------- #
# Stage 2 (Final)
FROM busybox:1.32.0
FROM busybox:1.33.0
LABEL org.opencontainers.image.title="Wings"
LABEL org.opencontainers.image.version="$VERSION"
LABEL org.opencontainers.image.description="The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind."
LABEL org.opencontainers.image.url="https://pterodactyl.io"
LABEL org.opencontainers.image.documentation="https://pterodactyl.io/project/introduction.html"
LABEL org.opencontainers.image.vendor="Pterodactyl Software"
LABEL org.opencontainers.image.source="https://github.com/pterodactyl/wings"
LABEL org.opencontainers.image.licenses="MIT"
RUN echo "ID=\"busybox\"" > /etc/os-release
COPY --from=builder /app/wings /usr/bin/

View File

@@ -2,17 +2,18 @@ package api
import (
"bytes"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
)
// Initializes the requester instance.

View File

@@ -2,10 +2,11 @@ package api
import (
"encoding/json"
"github.com/apex/log"
"github.com/pterodactyl/wings/parser"
"regexp"
"strings"
"github.com/apex/log"
"github.com/pterodactyl/wings/parser"
)
type OutputLineMatcher struct {

View File

@@ -4,11 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"sync"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"golang.org/x/sync/errgroup"
"strconv"
"sync"
)
const (

View File

@@ -1,9 +1,10 @@
package api
import (
"regexp"
"emperror.dev/errors"
"github.com/apex/log"
"regexp"
)
type SftpAuthRequest struct {

View File

@@ -1,9 +1,10 @@
package cmd
import (
"github.com/pterodactyl/wings/config"
"os"
"path/filepath"
"github.com/pterodactyl/wings/config"
)
// We've gone through a couple of iterations of where the configuration is stored. This

View File

@@ -4,10 +4,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/pterodactyl/wings/config"
"github.com/spf13/cobra"
"io/ioutil"
"net/http"
"net/url"
@@ -15,6 +11,11 @@ import (
"path"
"regexp"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/pterodactyl/wings/config"
"github.com/spf13/cobra"
)
var (

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/pterodactyl/wings/environment"
"io"
"io/ioutil"
"net/http"
@@ -16,6 +15,8 @@ import (
"strings"
"time"
"github.com/pterodactyl/wings/environment"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/docker/docker/api/types"

View File

@@ -2,8 +2,15 @@ package cmd
import (
"crypto/tls"
"emperror.dev/errors"
"fmt"
log2 "log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"emperror.dev/errors"
"github.com/NYTimes/logrotate"
"github.com/apex/log"
"github.com/apex/log/handlers/multi"
@@ -21,47 +28,54 @@ import (
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"net/http"
"os"
"path"
"path/filepath"
"strings"
)
var (
profiler = ""
configPath = config.DefaultLocation
debug = false
useAutomaticTls = false
tlsHostname = ""
showVersion = false
ignoreCertificateErrors = false
configPath = config.DefaultLocation
debug = false
)
var root = &cobra.Command{
var rootCommand = &cobra.Command{
Use: "wings",
Short: "The wings of the pterodactyl game management panel",
Long: ``,
Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
PreRun: func(cmd *cobra.Command, args []string) {
if useAutomaticTls && len(tlsHostname) == 0 {
fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com")
os.Exit(1)
if tls, _ := cmd.Flags().GetBool("auto-tls"); tls {
if host, _ := cmd.Flags().GetString("tls-hostname"); host == "" {
fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com")
os.Exit(1)
}
}
},
Run: rootCmdRun,
}
func init() {
root.PersistentFlags().BoolVar(&showVersion, "version", false, "show the version and exit")
root.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
root.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
root.PersistentFlags().StringVar(&profiler, "profiler", "", "the profiler to run for this instance")
root.PersistentFlags().BoolVar(&useAutomaticTls, "auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
root.PersistentFlags().StringVar(&tlsHostname, "tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
root.PersistentFlags().BoolVar(&ignoreCertificateErrors, "ignore-certificate-errors", false, "if passed any SSL certificate errors will be ignored by wings")
var versionCommand = &cobra.Command{
Use: "version",
Short: "Prints the current executable version and exits.",
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("wings v%s\nCopyright © 2018 - 2021 Dane Everitt & Contributors\n", system.Version)
},
}
root.AddCommand(configureCmd)
root.AddCommand(diagnosticsCmd)
func Execute() {
if err := rootCommand.Execute(); err != nil {
log2.Fatalf("failed to execute command: %s", err)
}
}
func init() {
rootCommand.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
rootCommand.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
// Flags specifically used when running the API.
rootCommand.Flags().String("profiler", "", "the profiler to run for this instance")
rootCommand.Flags().Bool("auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
rootCommand.Flags().String("tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
rootCommand.Flags().Bool("ignore-certificate-errors", false, "ignore certificate verification errors when executing API calls")
rootCommand.AddCommand(versionCommand)
rootCommand.AddCommand(configureCmd)
rootCommand.AddCommand(diagnosticsCmd)
}
// Get the configuration path based on the arguments provided.
@@ -85,13 +99,8 @@ func readConfiguration() (*config.Configuration, error) {
return config.ReadConfiguration(p)
}
func rootCmdRun(*cobra.Command, []string) {
if showVersion {
fmt.Println(system.Version)
os.Exit(0)
}
switch profiler {
func rootCmdRun(cmd *cobra.Command, _ []string) {
switch cmd.Flag("profiler").Value.String() {
case "cpu":
defer profile.Start(profile.CPUProfile).Stop()
case "mem":
@@ -117,7 +126,6 @@ func rootCmdRun(*cobra.Command, []string) {
if errors.Is(err, os.ErrNotExist) {
exitWithConfigurationNotice()
}
panic(err)
}
}
@@ -141,7 +149,7 @@ func rootCmdRun(*cobra.Command, []string) {
log.Debug("running in debug mode")
}
if ignoreCertificateErrors {
if ok, _ := cmd.Flags().GetBool("ignore-certificate-errors"); ok {
log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified")
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
@@ -280,9 +288,15 @@ func rootCmdRun(*cobra.Command, []string) {
log.WithField("error", err).Error("failed to create backup directory")
}
autotls, _ := cmd.Flags().GetBool("auto-tls")
tlshostname, _ := cmd.Flags().GetString("tls-hostname")
if autotls && tlshostname == "" {
autotls = false
}
log.WithFields(log.Fields{
"use_ssl": c.Api.Ssl.Enabled,
"use_auto_tls": useAutomaticTls && len(tlsHostname) > 0,
"use_auto_tls": autotls,
"host_address": c.Api.Host,
"host_port": c.Api.Port,
}).Info("configuring internal webserver")
@@ -293,7 +307,6 @@ func rootCmdRun(*cobra.Command, []string) {
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
Handler: r,
TLSConfig: &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
// @see https://blog.cloudflare.com/exposing-go-on-the-internet
@@ -313,14 +326,14 @@ func rootCmdRun(*cobra.Command, []string) {
}
// Check if the server should run with TLS but using autocert.
if useAutomaticTls && len(tlsHostname) > 0 {
if autotls {
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")),
HostPolicy: autocert.HostWhitelist(tlsHostname),
HostPolicy: autocert.HostWhitelist(tlshostname),
}
log.WithField("hostname", tlsHostname).
log.WithField("hostname", tlshostname).
Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
// Hook autocert into the main http server.
@@ -336,7 +349,7 @@ func rootCmdRun(*cobra.Command, []string) {
// Start the main http server with TLS using autocert.
if err := s.ListenAndServeTLS("", ""); err != nil {
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}).
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlshostname, "error": err}).
Fatal("failed to configure HTTP server using auto-tls")
}
@@ -364,11 +377,6 @@ func rootCmdRun(*cobra.Command, []string) {
}
}
// Execute calls cobra to handle cli commands
func Execute() error {
return root.Execute()
}
// Configures the global logger for Zap so that we can call it from any location
// in the code without having to pass around a logger instance.
func configureLogging(logDir string, debug bool) error {
@@ -379,20 +387,15 @@ func configureLogging(logDir string, debug bool) error {
p := filepath.Join(logDir, "/wings.log")
w, err := logrotate.NewFile(p)
if err != nil {
panic(errors.WithMessage(err, "failed to open process log file"))
return err
}
log.SetLevel(log.InfoLevel)
if debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
}
log.SetHandler(multi.New(
cli.Default,
cli.New(w.File, false),
))
log.SetHandler(multi.New(cli.Default, cli.New(w.File, false)))
log.WithField("path", p).Info("writing log files to disk")
return nil
@@ -406,7 +409,7 @@ __ [blue][bold]Pterodactyl[reset] _____/___/_______ _______ ______
\_____\ \/\/ / / / __ / ___/
\___\ / / / / /_/ /___ /
\___/\___/___/___/___/___ /______/
/_______/ [bold]v%s[reset]
/_______/ [bold]%s[reset]
Copyright © 2018 - 2021 Dane Everitt & Contributors

View File

@@ -1,12 +1,7 @@
package config
import (
"emperror.dev/errors"
"fmt"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"os/exec"
@@ -14,6 +9,12 @@ import (
"strconv"
"strings"
"sync"
"emperror.dev/errors"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"gopkg.in/yaml.v2"
)
const DefaultLocation = "/etc/pterodactyl/config.yml"
@@ -87,11 +88,16 @@ type ApiConfiguration struct {
// SSL configuration for the daemon.
Ssl struct {
Enabled bool `default:"false"`
Enabled bool `json:"enabled" yaml:"enabled"`
CertificateFile string `json:"cert" yaml:"cert"`
KeyFile string `json:"key" yaml:"key"`
}
// Determines if functionality for allowing remote download of files into server directories
// is enabled on this instance. If set to "true" remote downloads will not be possible for
// servers.
DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
// The maximum size for files uploaded through the Panel in bytes.
UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
}
@@ -223,6 +229,36 @@ func (c *Configuration) GetPath() string {
// If files are not owned by this user there will be issues with permissions on Docker
// mount points.
func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
sysName, err := getSystemName()
if err != nil {
return nil, err
}
// Our way of detecting if wings is running inside of Docker.
if sysName == "busybox" {
uid := os.Getenv("WINGS_UID")
if uid == "" {
uid = "988"
}
gid := os.Getenv("WINGS_GID")
if gid == "" {
gid = "988"
}
username := os.Getenv("WINGS_USERNAME")
if username == "" {
username = "pterodactyl"
}
u := &user.User{
Uid: uid,
Gid: gid,
Username: username,
}
return u, c.setSystemUser(u)
}
u, err := user.Lookup(c.System.Username)
// If an error is returned but it isn't the unknown user error just abort
@@ -233,17 +269,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
return nil, err
}
sysName, err := getSystemName()
if err != nil {
return nil, err
}
command := fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username)
command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", c.System.Username)
// Alpine Linux is the only OS we currently support that doesn't work with the useradd command, so
// in those cases we just modify the command a bit to work as expected.
if strings.HasPrefix(sysName, "alpine") {
command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /bin/false %[1]s", c.System.Username)
command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /sbin/nologin %[1]s", c.System.Username)
// We have to create the group first on Alpine, so do that here before continuing on
// to the user creation process.
@@ -267,8 +298,15 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
// Set the system user into the configuration and then write it to the disk so that
// it is persisted on boot.
func (c *Configuration) setSystemUser(u *user.User) error {
uid, _ := strconv.Atoi(u.Uid)
gid, _ := strconv.Atoi(u.Gid)
uid, err := strconv.Atoi(u.Uid)
if err != nil {
return err
}
gid, err := strconv.Atoi(u.Gid)
if err != nil {
return err
}
c.Lock()
c.System.Username = u.Username

View File

@@ -3,6 +3,7 @@ package config
import (
"encoding/base64"
"encoding/json"
"github.com/docker/docker/api/types"
)

View File

@@ -2,9 +2,7 @@ package config
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"html/template"
"io/ioutil"
"os"
@@ -13,6 +11,9 @@ import (
"path/filepath"
"regexp"
"time"
"emperror.dev/errors"
"github.com/apex/log"
)
// Defines basic system configuration settings.

View File

@@ -1,4 +1,5 @@
version: '3.8'
services:
wings:
image: ghcr.io/pterodactyl/wings:latest
@@ -11,7 +12,9 @@ services:
tty: true
environment:
TZ: "UTC"
DEBUG: "false"
WINGS_UID: 988
WINGS_GID: 988
WINGS_USERNAME: pterodactyl
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
@@ -21,8 +24,9 @@ services:
- "/tmp/pterodactyl/:/tmp/pterodactyl/"
# you may need /srv/daemon-data if you are upgrading from an old daemon
#- "/srv/daemon-data/:/srv/daemon-data/"
# Required for ssl if you user let's encrypt. uncomment to use.
# Required for ssl if you use let's encrypt. uncomment to use.
#- "/etc/letsencrypt/:/etc/letsencrypt/"
networks:
wings0:
name: wings0
@@ -31,4 +35,4 @@ networks:
config:
- subnet: "172.21.0.0/16"
driver_opts:
com.docker.network.bridge.name: wings0
com.docker.network.bridge.name: wings0

View File

@@ -2,9 +2,10 @@ package environment
import (
"fmt"
"strconv"
"github.com/docker/go-connections/nat"
"github.com/pterodactyl/wings/config"
"strconv"
)
// Defines the allocations available for a given server. When using the Docker environment

View File

@@ -2,10 +2,11 @@ package environment
import (
"context"
"github.com/apex/log"
"strconv"
"sync"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"

View File

@@ -1,20 +0,0 @@
package docker
import "io"
type Console struct {
HandlerFunc *func(string)
}
var _ io.Writer = Console{}
func (c Console) Write(b []byte) (int, error) {
if c.HandlerFunc != nil {
l := make([]byte, len(b))
copy(l, b)
(*c.HandlerFunc)(string(l))
}
return len(b), nil
}

View File

@@ -26,10 +26,26 @@ type imagePullStatus struct {
Progress string `json:"progress"`
}
// A custom console writer that allows us to keep a function blocked until the
// given stream is properly closed. This does nothing special, only exists to
// make a noop io.Writer.
type noopWriter struct{}
var _ io.Writer = noopWriter{}
// Implement the required Write function to satisfy the io.Writer interface.
func (nw noopWriter) Write(b []byte) (int, error) {
return len(b), nil
}
// Attaches to the docker container itself and ensures that we can pipe data in and out
// of the process stream. This should not be used for reading console data as you *will*
// miss important output at the beginning because of the time delay with attaching to the
// output.
//
// Calling this function will poll resources for the container in the background until the
// provided context is canceled by the caller. Failure to cancel said context will cause
// background memory leaks as the goroutine will not exit.
func (e *Environment) Attach() error {
if e.IsAttached() {
return nil
@@ -53,10 +69,8 @@ func (e *Environment) Attach() error {
e.SetStream(&st)
}
c := new(Console)
go func(console *Console) {
go func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
defer e.stream.Close()
defer func() {
@@ -64,27 +78,32 @@ func (e *Environment) Attach() error {
e.SetStream(nil)
}()
// Poll resources in a separate thread since this will block the copy call below
// from being reached until it is completed if not run in a separate process. However,
// we still want it to be stopped when the copy operation below is finished running which
// indicates that the container is no longer running.
go func(ctx context.Context) {
go func() {
if err := e.pollResources(ctx); err != nil {
l := log.WithField("environment_id", e.Id)
if !errors.Is(err, context.Canceled) {
l.WithField("error", err).Error("error during environment resource polling")
e.log().WithField("error", err).Error("error during environment resource polling")
} else {
l.Warn("stopping server resource polling: context canceled")
e.log().Warn("stopping server resource polling: context canceled")
}
}
}(ctx)
}()
// Stream the reader output to the console which will then fire off events and handle console
// throttling and sending the output to the user.
if _, err := io.Copy(console, e.stream.Reader); err != nil {
log.WithField("environment_id", e.Id).WithField("error", err).Error("error while copying environment output to console")
// Block the completion of this routine until the container is no longer running. This allows
// the pollResources function to run until it needs to be stopped. Because the container
// can be polled for resource usage, even when stopped, we need to have this logic present
// in order to cancel the context and therefore stop the routine that is spawned.
//
// For now, DO NOT use client#ContainerWait from the Docker package. There is a nasty
// bug causing containers to hang on deletion and cause servers to lock up on the system.
//
// This weird code isn't intuitive, but it keeps the function from ending until the container
// is stopped and therefore the stream reader ends up closed.
// @see https://github.com/moby/moby/issues/41827
c := new(noopWriter)
if _, err := io.Copy(c, e.stream.Reader); err != nil {
e.log().WithField("error", err).Error("could not copy from environment stream to noop writer")
}
}(c)
}()
return nil
}
@@ -259,6 +278,8 @@ func (e *Environment) Destroy() error {
Force: true,
})
e.SetState(environment.ProcessOfflineState)
// Don't trigger a destroy failure if we try to delete a container that does not
// exist on the system. We're just a step ahead of ourselves in that case.
//
@@ -267,8 +288,6 @@ func (e *Environment) Destroy() error {
return nil
}
e.SetState(environment.ProcessOfflineState)
return err
}
@@ -280,7 +299,6 @@ func (e *Environment) followOutput() error {
if err != nil {
return err
}
return errors.New(fmt.Sprintf("no such container: %s", e.Id))
}
@@ -295,19 +313,39 @@ func (e *Environment) followOutput() error {
if err != nil {
return err
}
go func(reader io.ReadCloser) {
defer reader.Close()
evts := e.Events()
err := system.ScanReader(reader, func(line string) {
evts.Publish(environment.ConsoleOutputEvent, line)
})
if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
}
}(reader)
go e.scanOutput(reader)
return nil
}
func (e *Environment) scanOutput(reader io.ReadCloser) {
defer reader.Close()
events := e.Events()
err := system.ScanReader(reader, func(line string) {
events.Publish(environment.ConsoleOutputEvent, line)
})
if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
return
}
// Return here if the server is offline or currently stopping.
if e.State() == environment.ProcessStoppingState || e.State() == environment.ProcessOfflineState {
return
}
// Close the current reader before starting a new one, the defer will still run
// but it will do nothing if we already closed the stream.
_ = reader.Close()
// Start following the output of the server again.
go e.followOutput()
}
// Pulls the image from Docker. If there is an error while pulling the image from the source
// but the image already exists locally, we will report that error to the logger but continue
// with the process.
@@ -391,9 +429,11 @@ func (e *Environment) ensureImageExists(image string) error {
// I'm not sure what the best approach here is, but this will block execution until the image
// is done being pulled, which is what we need.
scanner := bufio.NewScanner(out)
for scanner.Scan() {
s := imagePullStatus{}
fmt.Println(scanner.Text())
if err := json.Unmarshal(scanner.Bytes(), &s); err == nil {
e.Events().Publish(environment.DockerImagePullStatus, s.Status+" "+s.Progress)
}

View File

@@ -2,6 +2,7 @@ package docker
import (
"context"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/api"
@@ -70,6 +71,10 @@ func New(id string, m *Metadata, c *environment.Configuration) (*Environment, er
return e, nil
}
func (e *Environment) log() *log.Entry {
return log.WithField("environment", e.Type()).WithField("container_id", e.Id)
}
func (e *Environment) Type() string {
return "docker"
}
@@ -77,8 +82,9 @@ func (e *Environment) Type() string {
// Set if this process is currently attached to the process.
func (e *Environment) SetStream(s *types.HijackedResponse) {
e.mu.Lock()
defer e.mu.Unlock()
e.stream = s
e.mu.Unlock()
}
// Determine if the this process is currently attached to the container.
@@ -93,6 +99,7 @@ func (e *Environment) Events() *events.EventBus {
e.eventMu.Do(func() {
e.emitter = events.New()
})
return e.emitter
}
@@ -169,12 +176,14 @@ func (e *Environment) Config() *environment.Configuration {
// Sets the stop configuration for the environment.
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
e.mu.Lock()
defer e.mu.Unlock()
e.meta.Stop = c
e.mu.Unlock()
}
func (e *Environment) SetImage(i string) {
e.mu.Lock()
defer e.mu.Unlock()
e.meta.Image = i
e.mu.Unlock()
}

View File

@@ -20,10 +20,9 @@ import (
//
// This process will also confirm that the server environment exists and is in a bootable
// state. This ensures that unexpected container deletion while Wings is running does
// not result in the server becoming unbootable.
// not result in the server becoming un-bootable.
func (e *Environment) OnBeforeStart() error {
// Always destroy and re-create the server container to ensure that synced data from
// the Panel is usee.
// Always destroy and re-create the server container to ensure that synced data from the Panel is used.
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
if !client.IsErrNotFound(err) {
return errors.WithMessage(err, "failed to remove server docker container during pre-boot")
@@ -49,6 +48,7 @@ func (e *Environment) OnBeforeStart() error {
// call to OnBeforeStart().
func (e *Environment) Start() error {
sawError := false
// If sawError is set to true there was an error somewhere in the pipeline that
// got passed up, but we also want to ensure we set the server to be offline at
// that point.
@@ -235,7 +235,7 @@ func (e *Environment) Terminate(signal os.Signal) error {
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil {
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil && !client.IsErrNotFound(err) {
return err
}

View File

@@ -4,12 +4,10 @@ import (
"context"
"emperror.dev/errors"
"encoding/json"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/pterodactyl/wings/environment"
"io"
"math"
"sync/atomic"
)
// Attach to the instance and then automatically emit an event whenever the resource usage for the
@@ -19,63 +17,51 @@ func (e *Environment) pollResources(ctx context.Context) error {
return errors.New("cannot enable resource polling on a stopped server")
}
l := log.WithField("container_id", e.Id)
l.Debug("starting resource polling for container")
defer l.Debug("stopped resource polling for container")
e.log().Info("starting resource polling for container")
defer e.log().Debug("stopped resource polling for container")
stats, err := e.client.ContainerStats(context.Background(), e.Id, true)
stats, err := e.client.ContainerStats(ctx, e.Id, true)
if err != nil {
return err
}
defer stats.Body.Close()
dec := json.NewDecoder(stats.Body)
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
var v *types.StatsJSON
var v types.StatsJSON
if err := dec.Decode(&v); err != nil {
if err != io.EOF {
l.WithField("error", err).Warn("error while processing Docker stats output for container")
if err != io.EOF && !errors.Is(err, context.Canceled) {
e.log().WithField("error", err).Warn("error while processing Docker stats output for container")
} else {
l.Debug("io.EOF encountered during stats decode, stopping polling...")
e.log().Debug("io.EOF encountered during stats decode, stopping polling...")
}
return nil
}
// Disable collection if the server is in an offline state and this process is still running.
if e.st.Load() == environment.ProcessOfflineState {
l.Debug("process in offline state while resource polling is still active; stopping poll")
e.log().Debug("process in offline state while resource polling is still active; stopping poll")
return nil
}
var rx uint64
var tx uint64
for _, nw := range v.Networks {
atomic.AddUint64(&rx, nw.RxBytes)
atomic.AddUint64(&tx, nw.RxBytes)
}
st := environment.Stats{
Memory: calculateDockerMemory(v.MemoryStats),
MemoryLimit: v.MemoryStats.Limit,
CpuAbsolute: calculateDockerAbsoluteCpu(&v.PreCPUStats, &v.CPUStats),
Network: struct {
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
}{
RxBytes: rx,
TxBytes: tx,
},
CpuAbsolute: calculateDockerAbsoluteCpu(v.PreCPUStats, v.CPUStats),
Network: environment.NetworkStats{},
}
for _, nw := range v.Networks {
st.Network.RxBytes += nw.RxBytes
st.Network.TxBytes += nw.TxBytes
}
if b, err := json.Marshal(st); err != nil {
l.WithField("error", err).Warn("error while marshaling stats object for environment")
e.log().WithField("error", err).Warn("error while marshaling stats object for environment")
} else {
e.Events().Publish(environment.ResourceEvent, string(b))
}
@@ -108,7 +94,7 @@ func calculateDockerMemory(stats types.MemoryStats) uint64 {
// by the defined CPU limits on the container.
//
// @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166
func calculateDockerAbsoluteCpu(pStats *types.CPUStats, stats *types.CPUStats) float64 {
func calculateDockerAbsoluteCpu(pStats types.CPUStats, stats types.CPUStats) float64 {
// Calculate the change in CPU usage between the current and previous reading.
cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage)

View File

@@ -19,8 +19,9 @@ var ErrNotAttached = errors.New("not attached to instance")
func (e *Environment) setStream(s *types.HijackedResponse) {
e.mu.Lock()
defer e.mu.Unlock()
e.stream = s
e.mu.Unlock()
}
// Sends the specified command to the stdin of the running container instance. There is no
@@ -71,7 +72,7 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
// Docker stores the logs for server output in a JSON format. This function will iterate over the JSON
// that was read from the log file and parse it into a more human readable format.
func (e *Environment) parseLogToStrings(b []byte) ([]string, error) {
var hasError = false
hasError := false
var out []string
scanner := bufio.NewScanner(bytes.NewReader(b))

View File

@@ -1,8 +1,9 @@
package environment
import (
"github.com/pterodactyl/wings/events"
"os"
"github.com/pterodactyl/wings/events"
)
const (

View File

@@ -2,9 +2,10 @@ package environment
import (
"fmt"
"github.com/apex/log"
"math"
"strconv"
"github.com/apex/log"
)
type Mount struct {

View File

@@ -24,8 +24,10 @@ type Stats struct {
// Disk int64 `json:"disk_bytes"`
// Current network transmit in & out for a container.
Network struct {
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
} `json:"network"`
Network NetworkStats `json:"network"`
}
type NetworkStats struct {
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
}

View File

@@ -2,9 +2,10 @@ package events
import (
"encoding/json"
"github.com/gammazero/workerpool"
"strings"
"sync"
"github.com/gammazero/workerpool"
)
type Event struct {

View File

@@ -1,8 +1,9 @@
package events
import (
"github.com/gammazero/workerpool"
"reflect"
"github.com/gammazero/workerpool"
)
type CallbackPool struct {

1
go.mod
View File

@@ -63,6 +63,7 @@ require (
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/ugorji/go v1.2.2 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad

View File

@@ -1,8 +1,9 @@
package installer
import (
"emperror.dev/errors"
"encoding/json"
"emperror.dev/errors"
"github.com/asaskevich/govalidator"
"github.com/buger/jsonparser"
"github.com/pterodactyl/wings/api"

View File

@@ -2,16 +2,17 @@ package parser
import (
"bytes"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/iancoleman/strcase"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/iancoleman/strcase"
)
// Regex to match anything that has a value matching the format of {{ config.$1 }} which

View File

@@ -2,8 +2,14 @@ package parser
import (
"bufio"
"emperror.dev/errors"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/beevik/etree"
"github.com/buger/jsonparser"
@@ -12,11 +18,6 @@ import (
"github.com/pterodactyl/wings/config"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
// The file parsing options that are available for a server configuration file.

View File

@@ -4,17 +4,65 @@ import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
"io"
"net"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
var client = &http.Client{
Timeout: time.Hour * 12,
// Disallow any redirect on a HTTP call. This is a security requirement: do not modify
// this logic without first ensuring that the new target location IS NOT within the current
// instance's local network.
//
// This specific error response just causes the client to not follow the redirect and
// returns the actual redirect response to the caller. Not perfect, but simple and most
// people won't be using URLs that redirect anyways hopefully?
//
// We'll re-evaluate this down the road if needed.
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
var instance = &Downloader{
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Regex to match the end of an IPv4/IPv6 address. This allows the port to be removed
// so that we are just working with the raw IP address in question.
var ipMatchRegex = regexp.MustCompile(`(:\d+)$`)
// Internal IP ranges that should be blocked if the resource requested resolves within.
var internalRanges = []*net.IPNet{
mustParseCIDR("127.0.0.1/8"),
mustParseCIDR("10.0.0.0/8"),
mustParseCIDR("172.16.0.0/12"),
mustParseCIDR("192.168.0.0/16"),
mustParseCIDR("169.254.0.0/16"),
mustParseCIDR("::1/128"),
mustParseCIDR("fe80::/10"),
mustParseCIDR("fc00::/7"),
}
const ErrInternalResolution = errors.Sentinel("downloader: destination resolves to internal network location")
const ErrInvalidIPAddress = errors.Sentinel("downloader: invalid IP address")
const ErrDownloadFailed = errors.Sentinel("downloader: download request failed")
type Counter struct {
total int
onWrite func(total int)
@@ -27,12 +75,6 @@ func (c *Counter) Write(p []byte) (int, error) {
return n, nil
}
type Downloader struct {
mu sync.RWMutex
downloadCache map[string]*Download
serverCache map[string][]string
}
type DownloadRequest struct {
URL *url.URL
Directory string
@@ -47,16 +89,6 @@ type Download struct {
cancelFunc *context.CancelFunc
}
var client = &http.Client{Timeout: time.Hour * 12}
var instance = &Downloader{
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Starts a new tracked download which allows for cancelation later on by calling
// the Downloader.Cancel function.
func New(s *server.Server, r DownloadRequest) *Download {
@@ -108,15 +140,24 @@ func (dl *Download) Execute() error {
dl.cancelFunc = &cancel
defer dl.Cancel()
// Always ensure that we're checking the destination for the download to avoid a malicious
// user from accessing internal network resources.
if err := dl.isExternalNetwork(ctx); err != nil {
return err
}
// At this point we have verified the destination is not within the local network, so we can
// now make a request to that URL and pull down the file, saving it to the server's data
// directory.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, dl.req.URL.String(), nil)
if err != nil {
return errors.WrapIf(err, "downloader: failed to create request")
}
req.Header.Set("User-Agent", "Pterodactyl Panel (https://pterodactyl.io)")
res, err := client.Do(req) // lgtm [go/request-forgery]
res, err := client.Do(req)
if err != nil {
return errors.New("downloader: failed opening request to download file")
return ErrDownloadFailed
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
@@ -178,6 +219,52 @@ func (dl *Download) counter(contentLength int64) *Counter {
}
}
// Verifies that a given download resolves to a location not within the current local
// network for the machine. If the final destination of a resource is within the local
// network an ErrInternalResolution error is returned.
func (dl *Download) isExternalNetwork(ctx context.Context) error {
dialer := &net.Dialer{
LocalAddr: nil,
}
host := dl.req.URL.Host
if !ipMatchRegex.MatchString(host) {
if dl.req.URL.Scheme == "https" {
host = host + ":443"
} else {
host = host + ":80"
}
}
c, err := dialer.DialContext(ctx, "tcp", host)
if err != nil {
return errors.WithStack(err)
}
c.Close()
ip := net.ParseIP(ipMatchRegex.ReplaceAllString(c.RemoteAddr().String(), ""))
if ip == nil {
return errors.WithStack(ErrInvalidIPAddress)
}
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast() {
return errors.WithStack(ErrInternalResolution)
}
for _, block := range internalRanges {
if block.Contains(ip) {
return errors.WithStack(ErrInternalResolution)
}
}
return nil
}
// Defines a global downloader struct that keeps track of all currently processing downloads
// for the machine.
type Downloader struct {
mu sync.RWMutex
downloadCache map[string]*Download
serverCache map[string][]string
}
// Tracks a download in the internal cache for this instance.
func (d *Downloader) track(dl *Download) {
d.mu.Lock()
@@ -222,3 +309,11 @@ func (d *Downloader) remove(dlid string) {
d.serverCache[sid] = out
}
}
func mustParseCIDR(ip string) *net.IPNet {
_, block, err := net.ParseCIDR(ip)
if err != nil {
panic(fmt.Errorf("downloader: failed to parse CIDR: %s", err))
}
return block
}

View File

@@ -1,16 +1,17 @@
package router
import (
"emperror.dev/errors"
"fmt"
"net/http"
"os"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"net/http"
"os"
"strings"
)
type RequestError struct {

View File

@@ -1,14 +1,15 @@
package router
import (
"io"
"net/http"
"strings"
"emperror.dev/errors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server"
"io"
"net/http"
"strings"
)
type Middleware struct{}
@@ -119,6 +120,21 @@ func (m *Middleware) ServerExists() gin.HandlerFunc {
}
}
// 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.

View File

@@ -88,9 +88,9 @@ func Configure() *gin.Engine {
files.POST("/decompress", postServerDecompressFiles)
files.POST("/chmod", postServerChmodFile)
files.GET("/pull", getServerPullingFiles)
files.POST("/pull", postServerPullRemoteFile)
files.DELETE("/pull/:download", deleteServerPullRemoteFile)
files.GET("/pull", m.CheckRemoteDownloadEnabled(), getServerPullingFiles)
files.POST("/pull", m.CheckRemoteDownloadEnabled(), postServerPullRemoteFile)
files.DELETE("/pull/:download", m.CheckRemoteDownloadEnabled(), deleteServerPullRemoteFile)
}
backup := server.Group("/backup")

View File

@@ -3,12 +3,13 @@ package router
import (
"bufio"
"errors"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server/backup"
"net/http"
"os"
"strconv"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server/backup"
)
// Handle a download request for a server backup.

View File

@@ -3,15 +3,16 @@ package router
import (
"bytes"
"context"
"net/http"
"os"
"strconv"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"net/http"
"os"
"strconv"
)
type serverProcData struct {

View File

@@ -1,13 +1,14 @@
package router
import (
"emperror.dev/errors"
"fmt"
"net/http"
"os"
"emperror.dev/errors"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/backup"
"net/http"
"os"
)
// Backs up a server.

View File

@@ -2,14 +2,6 @@ package router
import (
"context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
"mime/multipart"
"net/http"
"net/url"
@@ -18,16 +10,21 @@ import (
"path/filepath"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
)
// Returns the contents of a file on the server.
func getServerFileContents(c *gin.Context) {
s := ExtractServer(c)
f, err := url.QueryUnescape(c.Query("file"))
if err != nil {
WithError(c, err)
return
}
f := c.Query("file")
p := "/" + strings.TrimLeft(f, "/")
st, err := s.Filesystem().Stat(p)
if err != nil {
@@ -64,11 +61,7 @@ func getServerFileContents(c *gin.Context) {
// Returns the contents of a directory for a server.
func getServerListDirectory(c *gin.Context) {
s := ExtractServer(c)
dir, err := url.QueryUnescape(c.Query("directory"))
if err != nil {
WithError(c, err)
return
}
dir := c.Query("directory")
if stats, err := s.Filesystem().ListDirectory(dir); err != nil {
WithError(c, err)
} else {
@@ -212,11 +205,7 @@ func postServerDeleteFiles(c *gin.Context) {
func postServerWriteFile(c *gin.Context) {
s := GetServer(c.Param("server"))
f, err := url.QueryUnescape(c.Query("file"))
if err != nil {
NewServerError(err, s).Abort(c)
return
}
f := c.Query("file")
f = "/" + strings.TrimLeft(f, "/")
if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil {

View File

@@ -3,10 +3,11 @@ package router
import (
"context"
"encoding/json"
"time"
"github.com/gin-gonic/gin"
ws "github.com/gorilla/websocket"
"github.com/pterodactyl/wings/router/websocket"
"time"
)
// Upgrades a connection to a websocket and passes events along between.

View File

@@ -2,14 +2,15 @@ package router
import (
"bytes"
"net/http"
"strings"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system"
"net/http"
"strings"
)
// Returns information about the system that wings is running on.

View File

@@ -3,10 +3,19 @@ package router
import (
"bufio"
"crypto/sha256"
"emperror.dev/errors"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@@ -19,14 +28,6 @@ import (
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
)
// Number of ticks in the progress bar
@@ -129,6 +130,9 @@ func postServerArchive(c *gin.Context) {
return
}
// Mark the server as not being transferred so it can actually be used.
s.SetTransferring(false)
s.Events().Publish(server.TransferStatusEvent, "failure")
sendTransferLog("Attempting to notify panel of archive failure..")

View File

@@ -2,15 +2,16 @@ package server
import (
"crypto/sha256"
"emperror.dev/errors"
"encoding/hex"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
"io"
"io/ioutil"
"os"
"path/filepath"
"emperror.dev/errors"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
)
// Archiver represents a Server Archiver.

View File

@@ -1,12 +1,13 @@
package server
import (
"io/ioutil"
"os"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server/backup"
"io/ioutil"
"os"
)
// Notifies the panel of a backup's state and returns an error if one is encountered

View File

@@ -1,8 +1,9 @@
package server
import (
"github.com/gammazero/workerpool"
"runtime"
"github.com/gammazero/workerpool"
)
// Parent function that will update all of the defined configuration files for a server

View File

@@ -1,8 +1,9 @@
package server
import (
"github.com/pterodactyl/wings/environment"
"sync"
"github.com/pterodactyl/wings/environment"
)
type Configuration struct {

View File

@@ -2,14 +2,15 @@ package server
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"sync"
"sync/atomic"
"time"
"emperror.dev/errors"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
)
var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
@@ -114,7 +115,7 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
func (s *Server) Throttler() *ConsoleThrottler {
s.throttleOnce.Do(func() {
s.throttler = &ConsoleThrottler{
isThrottled: system.NewAtomicBool(false),
isThrottled: system.NewAtomicBool(false),
ConsoleThrottles: config.Get().Throttles,
}
})

View File

@@ -2,11 +2,12 @@ package server
import (
"fmt"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"strconv"
"sync"
"time"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
)
type CrashHandler struct {

View File

@@ -1,8 +1,9 @@
package server
import (
"github.com/pterodactyl/wings/server/filesystem"
"os"
"github.com/pterodactyl/wings/server/filesystem"
)
func (s *Server) Filesystem() *filesystem.Filesystem {

View File

@@ -4,6 +4,13 @@ import (
"bufio"
"bytes"
"context"
"html/template"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/docker/docker/api/types"
@@ -14,12 +21,6 @@ import (
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
"html/template"
"io"
"os"
"path/filepath"
"strconv"
"strings"
)
// Executes the installation stack for a server process. Bubbles any errors up to the calling

View File

@@ -2,14 +2,15 @@ package server
import (
"encoding/json"
"regexp"
"strconv"
"sync"
"github.com/apex/log"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/events"
"regexp"
"strconv"
"sync"
)
var dockerEvents = []string{
@@ -64,9 +65,11 @@ func (s *Server) StartEventListeners() {
// to terminate again.
if s.Environment.State() != environment.ProcessStoppingState {
s.Environment.SetState(environment.ProcessStoppingState)
go func() {
s.Log().Warn("stopping server instance, violating throttle limits")
s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.")
// Completely skip over server power actions and terminate the running instance. This gives the
// server 15 seconds to finish stopping gracefully before it is forcefully terminated.
if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil {

View File

@@ -1,9 +1,14 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gammazero/workerpool"
"github.com/pterodactyl/wings/api"
@@ -11,10 +16,6 @@ import (
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/server/filesystem"
"os"
"path/filepath"
"runtime"
"time"
)
var servers = NewCollection(nil)

View File

@@ -1,11 +1,12 @@
package server
import (
"path/filepath"
"strings"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"path/filepath"
"strings"
)
// To avoid confusion when working with mounts, assume that a server.Mount has not been properly

View File

@@ -2,12 +2,13 @@ package server
import (
"context"
"os"
"time"
"emperror.dev/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"golang.org/x/sync/semaphore"
"os"
"time"
)
type PowerAction string

View File

@@ -1,10 +1,11 @@
package server
import (
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
"sync"
"sync/atomic"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
)
// Defines the current resource usage for a given server instance. If a server is offline you

View File

@@ -2,8 +2,11 @@ package server
import (
"context"
"emperror.dev/errors"
"fmt"
"strings"
"sync"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/creasty/defaults"
"github.com/pterodactyl/wings/api"
@@ -14,8 +17,6 @@ import (
"github.com/pterodactyl/wings/server/filesystem"
"github.com/pterodactyl/wings/system"
"golang.org/x/sync/semaphore"
"strings"
"sync"
)
// High level definition for a server instance being controlled by Wings.

View File

@@ -2,12 +2,13 @@ package server
import (
"encoding/json"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"io"
"io/ioutil"
"os"
"sync"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
)
var stateMutex sync.Mutex

View File

@@ -1,8 +1,9 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"emperror.dev/errors"
"github.com/buger/jsonparser"
"github.com/imdario/mergo"
"github.com/pterodactyl/wings/environment"

View File

@@ -2,8 +2,9 @@ package server
import (
"context"
"github.com/google/uuid"
"sync"
"github.com/google/uuid"
)
type WebsocketBag struct {

View File

@@ -1,14 +1,15 @@
package sftp
import (
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"io"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
)
type FileSystem struct {

View File

@@ -6,11 +6,6 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"github.com/pterodactyl/wings/api"
"golang.org/x/crypto/ssh"
"io"
"io/ioutil"
"net"
@@ -18,6 +13,12 @@ import (
"path"
"strings"
"time"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"github.com/pterodactyl/wings/api"
"golang.org/x/crypto/ssh"
)
type Settings struct {

View File

@@ -1,8 +1,9 @@
package system
import (
"github.com/docker/docker/pkg/parsers/kernel"
"runtime"
"github.com/docker/docker/pkg/parsers/kernel"
)
type Information struct {