Compare commits
18 Commits
release/v1
...
v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee280f1deb | ||
|
|
8bd1ebe360 | ||
|
|
93664fd112 | ||
|
|
3a738e44d6 | ||
|
|
067ca5bb60 | ||
|
|
355c10c1e4 | ||
|
|
04933c153b | ||
|
|
f85509a0c7 | ||
|
|
225a89be72 | ||
|
|
f1344f1a82 | ||
|
|
d874af85db | ||
|
|
54b6033392 | ||
|
|
10a2ffc0a7 | ||
|
|
ede1cdc76f | ||
|
|
9e98287172 | ||
|
|
6653466ca8 | ||
|
|
f54a736353 | ||
|
|
d3360f0fd9 |
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## v1.6.1
|
||||
### Fixed
|
||||
* Fixes error that would sometimes occur when starting a server that would cause the temporary power action lock to never be released due to a blocked channel.
|
||||
* Fixes a bug causing the CPU usage of Wings to get stuck at 100% when a server is deleted while the installation process is running.
|
||||
|
||||
### Changed
|
||||
* Cleans up a lot of the logic for handling events between the server and environment process to make it easier to make modifications to down the road.
|
||||
* Cleans up logic handling the `StopAndWait` logic for stopping a server gracefully before terminating the process if it does not respond.
|
||||
|
||||
## v1.6.0
|
||||
### Fixed
|
||||
* Internal logic for processing a server start event has been adjusted to attach to the Docker container before attempting to start the container. This should fix issues where a server would get stuck after pulling the container image.
|
||||
|
||||
83
cmd/root.go
83
cmd/root.go
@@ -9,11 +9,13 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/NYTimes/logrotate"
|
||||
@@ -28,6 +30,7 @@ import (
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
"github.com/pterodactyl/wings/internal/notify"
|
||||
"github.com/pterodactyl/wings/loggers/cli"
|
||||
"github.com/pterodactyl/wings/remote"
|
||||
"github.com/pterodactyl/wings/router"
|
||||
@@ -324,43 +327,57 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
}
|
||||
|
||||
// Check if the server should run with TLS but using autocert.
|
||||
if autotls {
|
||||
m := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Cache: autocert.DirCache(path.Join(sys.RootDirectory, "/.tls-cache")),
|
||||
HostPolicy: autocert.HostWhitelist(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.
|
||||
s.TLSConfig.GetCertificate = m.GetCertificate
|
||||
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto) // enable tls-alpn ACME challenges
|
||||
|
||||
// Start the autocert server.
|
||||
go func() {
|
||||
if err := http.ListenAndServe(":http", m.HTTPHandler(nil)); err != nil {
|
||||
log.WithError(err).Error("failed to serve autocert http server")
|
||||
go func(s *http.Server, api config.ApiConfiguration, sys config.SystemConfiguration, autotls bool, tlshostname string) {
|
||||
if autotls {
|
||||
m := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Cache: autocert.DirCache(path.Join(sys.RootDirectory, "/.tls-cache")),
|
||||
HostPolicy: autocert.HostWhitelist(tlshostname),
|
||||
}
|
||||
}()
|
||||
// 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}).Fatal("failed to configure HTTP server using auto-tls")
|
||||
|
||||
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.
|
||||
s.TLSConfig.GetCertificate = m.GetCertificate
|
||||
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto) // enable tls-alpn ACME challenges
|
||||
|
||||
// Start the autocert server.
|
||||
go func() {
|
||||
if err := http.ListenAndServe(":http", m.HTTPHandler(nil)); err != nil {
|
||||
log.WithError(err).Error("failed to serve autocert http server")
|
||||
}
|
||||
}()
|
||||
// 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}).Fatal("failed to configure HTTP server using auto-tls")
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
// Check if main http server should run with TLS. Otherwise reset the TLS
|
||||
// config on the server and then serve it over normal HTTP.
|
||||
if api.Ssl.Enabled {
|
||||
if err := s.ListenAndServeTLS(api.Ssl.CertificateFile, api.Ssl.KeyFile); err != nil {
|
||||
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
|
||||
}
|
||||
return
|
||||
}
|
||||
s.TLSConfig = nil
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to configure HTTP server")
|
||||
}
|
||||
}(s, api, sys, autotls, tlshostname)
|
||||
|
||||
if err := notify.Readiness(); err != nil {
|
||||
log.WithField("error", err).Error("failed to notify systemd of readiness state")
|
||||
}
|
||||
|
||||
// Check if main http server should run with TLS. Otherwise reset the TLS
|
||||
// config on the server and then serve it over normal HTTP.
|
||||
if api.Ssl.Enabled {
|
||||
if err := s.ListenAndServeTLS(api.Ssl.CertificateFile, api.Ssl.KeyFile); err != nil {
|
||||
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
|
||||
}
|
||||
return
|
||||
}
|
||||
s.TLSConfig = nil
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to configure HTTP server")
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-c
|
||||
|
||||
if err := notify.Stopping(); err != nil {
|
||||
log.WithField("error", err).Error("failed to notify systemd of stopping state")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,8 +89,8 @@ type ApiConfiguration struct {
|
||||
// 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"`
|
||||
// The maximum size for files uploaded through the Panel in MB.
|
||||
UploadLimit int64 `default:"100" json:"upload_limit" yaml:"upload_limit"`
|
||||
}
|
||||
|
||||
// RemoteQueryConfiguration defines the configuration settings for remote requests
|
||||
@@ -132,6 +132,10 @@ type SystemConfiguration struct {
|
||||
// Directory where local backups will be stored on the machine.
|
||||
BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"`
|
||||
|
||||
// TmpDirectory specifies where temporary files for Pterodactyl installation processes
|
||||
// should be created. This supports environments running docker-in-docker.
|
||||
TmpDirectory string `default:"/tmp/pterodactyl" yaml:"tmp_directory"`
|
||||
|
||||
// The user that should own all of the server files, and be used for containers.
|
||||
Username string `default:"pterodactyl" yaml:"username"`
|
||||
|
||||
|
||||
@@ -116,4 +116,4 @@ func parseErrorFromResponse(res *http.Response, body []byte) error {
|
||||
}
|
||||
|
||||
return errors.Wrap(errors.New(emsg), "Error response from daemon")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type Metadata struct {
|
||||
var _ environment.ProcessEnvironment = (*Environment)(nil)
|
||||
|
||||
type Environment struct {
|
||||
mu sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
|
||||
// The public identifier for this environment. In this case it is the Docker container
|
||||
// name that will be used for all instances created under it.
|
||||
|
||||
@@ -218,7 +218,7 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
|
||||
}
|
||||
}()
|
||||
|
||||
doTermination := func (s string) error {
|
||||
doTermination := func(s string) error {
|
||||
e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...")
|
||||
return e.Terminate(ctx, os.Kill)
|
||||
}
|
||||
|
||||
105
go.mod
105
go.mod
@@ -3,116 +3,113 @@ module github.com/pterodactyl/wings
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
emperror.dev/errors v0.8.0
|
||||
github.com/AlecAivazis/survey/v2 v2.2.15
|
||||
emperror.dev/errors v0.8.1
|
||||
github.com/AlecAivazis/survey/v2 v2.3.4
|
||||
github.com/Jeffail/gabs/v2 v2.6.1
|
||||
github.com/NYTimes/logrotate v1.0.0
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/cenkalti/backoff/v4 v4.1.1
|
||||
github.com/cenkalti/backoff/v4 v4.1.2
|
||||
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
|
||||
github.com/creasty/defaults v1.5.1
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/creasty/defaults v1.5.2
|
||||
github.com/docker/docker v20.10.14+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
|
||||
github.com/gabriel-vasile/mimetype v1.3.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/gammazero/workerpool v1.1.2
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.1
|
||||
github.com/gin-gonic/gin v1.7.2
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/icza/dyno v0.0.0-20210726202311-f1bafe5d9996
|
||||
github.com/juju/ratelimit v1.0.1
|
||||
github.com/karrick/godirwalk v1.16.1
|
||||
github.com/klauspost/pgzip v1.2.5
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mholt/archiver/v3 v3.5.0
|
||||
github.com/magiconair/properties v1.8.6
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/profile v1.6.0
|
||||
github.com/pkg/sftp v1.13.2
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/pkg/sftp v1.13.4
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require github.com/goccy/go-json v0.9.4
|
||||
require github.com/goccy/go-json v0.9.6
|
||||
|
||||
require golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
|
||||
require golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.20 // indirect
|
||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/containerd/containerd v1.5.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/containerd v1.6.2 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gammazero/deque v0.1.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/gammazero/deque v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.8.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/mux v1.7.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.13.2 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magefile/mage v1.11.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/magefile/mage v1.13.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/nwaples/rardecode v1.1.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.30.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.1 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210729151513-df9385d47c1b // indirect
|
||||
google.golang.org/grpc v1.39.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
19
internal/notify/notify.go
Normal file
19
internal/notify/notify.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Package notify handles notifying the operating system of the program's state.
|
||||
//
|
||||
// For linux based operating systems, this is done through the systemd socket
|
||||
// set by "NOTIFY_SOCKET" environment variable.
|
||||
//
|
||||
// Currently, no other operating systems are supported.
|
||||
package notify
|
||||
|
||||
func Readiness() error {
|
||||
return readiness()
|
||||
}
|
||||
|
||||
func Reloading() error {
|
||||
return reloading()
|
||||
}
|
||||
|
||||
func Stopping() error {
|
||||
return stopping()
|
||||
}
|
||||
48
internal/notify/notify_linux.go
Normal file
48
internal/notify/notify_linux.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func notify(path string, r io.Reader) error {
|
||||
s := &net.UnixAddr{
|
||||
Name: path,
|
||||
Net: "unixgram",
|
||||
}
|
||||
c, err := net.DialUnix(s.Net, nil, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if _, err := io.Copy(c, r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func socketNotify(payload string) error {
|
||||
v, ok := os.LookupEnv("NOTIFY_SOCKET")
|
||||
if !ok || v == "" {
|
||||
return nil
|
||||
}
|
||||
if err := notify(v, strings.NewReader(payload)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readiness() error {
|
||||
return socketNotify("READY=1")
|
||||
}
|
||||
|
||||
func reloading() error {
|
||||
return socketNotify("RELOADING=1")
|
||||
}
|
||||
|
||||
func stopping() error {
|
||||
return socketNotify("STOPPING=1")
|
||||
}
|
||||
16
internal/notify/notify_other.go
Normal file
16
internal/notify/notify_other.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package notify
|
||||
|
||||
func readiness() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloading() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopping() error {
|
||||
return nil
|
||||
}
|
||||
@@ -71,6 +71,7 @@ type SftpAuthRequest struct {
|
||||
IP string `json:"ip"`
|
||||
SessionID []byte `json:"session_id"`
|
||||
ClientVersion []byte `json:"client_version"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// SftpAuthResponse is returned by the Panel when a pair of SFTP credentials
|
||||
@@ -78,8 +79,8 @@ type SftpAuthRequest struct {
|
||||
// matched as well as the permissions that are assigned to the authenticated
|
||||
// user for the SFTP subsystem.
|
||||
type SftpAuthResponse struct {
|
||||
SSHKeys []string `json:"ssh_keys"`
|
||||
Server string `json:"server"`
|
||||
Token string `json:"token"`
|
||||
Permissions []string `json:"permissions"`
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/pterodactyl/wings/server"
|
||||
)
|
||||
@@ -77,10 +78,13 @@ func (c *Counter) Write(p []byte) (int, error) {
|
||||
type DownloadRequest struct {
|
||||
Directory string
|
||||
URL *url.URL
|
||||
FileName string
|
||||
UseHeader bool
|
||||
}
|
||||
|
||||
type Download struct {
|
||||
Identifier string
|
||||
path string
|
||||
mu sync.RWMutex
|
||||
req DownloadRequest
|
||||
server *server.Server
|
||||
@@ -172,8 +176,28 @@ func (dl *Download) Execute() error {
|
||||
}
|
||||
}
|
||||
|
||||
fnameparts := strings.Split(dl.req.URL.Path, "/")
|
||||
p := filepath.Join(dl.req.Directory, fnameparts[len(fnameparts)-1])
|
||||
if dl.req.UseHeader {
|
||||
if contentDisposition := res.Header.Get("Content-Disposition"); contentDisposition != "" {
|
||||
_, params, err := mime.ParseMediaType(contentDisposition)
|
||||
if err != nil {
|
||||
return errors.WrapIf(err, "downloader: invalid \"Content-Disposition\" header")
|
||||
}
|
||||
|
||||
if v, ok := params["filename"]; ok {
|
||||
dl.path = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if dl.path == "" {
|
||||
if dl.req.FileName != "" {
|
||||
dl.path = dl.req.FileName
|
||||
} else {
|
||||
parts := strings.Split(dl.req.URL.Path, "/")
|
||||
dl.path = parts[len(parts)-1]
|
||||
}
|
||||
}
|
||||
|
||||
p := dl.Path()
|
||||
dl.server.Log().WithField("path", p).Debug("writing remote file to disk")
|
||||
|
||||
r := io.TeeReader(res.Body, dl.counter(res.ContentLength))
|
||||
@@ -205,6 +229,10 @@ func (dl *Download) Progress() float64 {
|
||||
return dl.progress
|
||||
}
|
||||
|
||||
func (dl *Download) Path() string {
|
||||
return filepath.Join(dl.req.Directory, dl.path)
|
||||
}
|
||||
|
||||
// Handles a write event by updating the progress completed percentage and firing off
|
||||
// events to the server websocket as needed.
|
||||
func (dl *Download) counter(contentLength int64) *Counter {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -255,9 +257,12 @@ func postServerPullRemoteFile(c *gin.Context) {
|
||||
s := ExtractServer(c)
|
||||
var data struct {
|
||||
// Deprecated
|
||||
Directory string `binding:"required_without=RootPath,omitempty" json:"directory"`
|
||||
RootPath string `binding:"required_without=Directory,omitempty" json:"root"`
|
||||
URL string `binding:"required" json:"url"`
|
||||
Directory string `binding:"required_without=RootPath,omitempty" json:"directory"`
|
||||
RootPath string `binding:"required_without=Directory,omitempty" json:"root"`
|
||||
URL string `binding:"required" json:"url"`
|
||||
FileName string `json:"file_name"`
|
||||
UseHeader bool `json:"use_header"`
|
||||
Foreground bool `json:"foreground"`
|
||||
}
|
||||
if err := c.BindJSON(&data); err != nil {
|
||||
return
|
||||
@@ -295,21 +300,41 @@ func postServerPullRemoteFile(c *gin.Context) {
|
||||
dl := downloader.New(s, downloader.DownloadRequest{
|
||||
Directory: data.RootPath,
|
||||
URL: u,
|
||||
FileName: data.FileName,
|
||||
UseHeader: data.UseHeader,
|
||||
})
|
||||
|
||||
// Execute this pull in a separate thread since it may take a long time to complete.
|
||||
go func() {
|
||||
download := func() error {
|
||||
s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk")
|
||||
if err := dl.Execute(); err != nil {
|
||||
s.Log().WithField("download_id", dl.Identifier).WithField("error", err).Error("failed to pull remote file")
|
||||
return err
|
||||
} else {
|
||||
s.Log().WithField("download_id", dl.Identifier).Info("completed pull of remote file")
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
if !data.Foreground {
|
||||
go func() {
|
||||
_ = download()
|
||||
}()
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"identifier": dl.Identifier,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"identifier": dl.Identifier,
|
||||
})
|
||||
if err := download(); err != nil {
|
||||
NewServerError(err, s).Abort(c)
|
||||
return
|
||||
}
|
||||
|
||||
st, err := s.Filesystem().Stat(dl.Path())
|
||||
if err != nil {
|
||||
NewServerError(err, s).AbortFilesystemError(c)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, &st)
|
||||
}
|
||||
|
||||
// Stops a remote file download if it exists and belongs to this server.
|
||||
@@ -537,8 +562,16 @@ func postServerUploadFiles(c *gin.Context) {
|
||||
|
||||
directory := c.Query("directory")
|
||||
|
||||
maxFileSize := config.Get().Api.UploadLimit
|
||||
maxFileSizeBytes := maxFileSize * 1024 * 1024
|
||||
var totalSize int64
|
||||
for _, header := range headers {
|
||||
if header.Size > maxFileSizeBytes {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||
"error": "File " + header.Filename + " is larger than the maximum file upload size of " + strconv.FormatInt(maxFileSize, 10) + " MB.",
|
||||
})
|
||||
return
|
||||
}
|
||||
totalSize += header.Size
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
ws "github.com/gorilla/websocket"
|
||||
"github.com/goccy/go-json"
|
||||
ws "github.com/gorilla/websocket"
|
||||
|
||||
"github.com/pterodactyl/wings/router/middleware"
|
||||
"github.com/pterodactyl/wings/router/websocket"
|
||||
|
||||
@@ -164,6 +164,5 @@ func (h *Handler) listenForServerEvents(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestName(t *testing.T) {
|
||||
})
|
||||
|
||||
g.It("calls strike once per time period", func() {
|
||||
t := newConsoleThrottle(1, time.Millisecond * 20)
|
||||
t := newConsoleThrottle(1, time.Millisecond*20)
|
||||
|
||||
var times int
|
||||
t.strike = func() {
|
||||
@@ -53,10 +53,10 @@ func TestName(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkConsoleThrottle(b *testing.B) {
|
||||
t := newConsoleThrottle(10, time.Millisecond * 10)
|
||||
t := newConsoleThrottle(10, time.Millisecond*10)
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
t.Allow()
|
||||
}
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
t.Allow()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,4 @@ func (s *Server) DestroyAllSinks() {
|
||||
for _, sink := range s.sinks {
|
||||
sink.Destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Server) Install(sync bool) error {
|
||||
if sync {
|
||||
s.Log().Info("syncing server state with remote source before executing installation process")
|
||||
if err := s.Sync(); err != nil {
|
||||
return err
|
||||
return errors.WrapIf(err, "install: failed to sync server state with Panel")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (s *Server) Install(sync bool) error {
|
||||
// error to this log entry. Otherwise ignore it in this log since whatever is calling
|
||||
// this function should handle the error and will end up logging the same one.
|
||||
if err == nil {
|
||||
l.WithField("error", serr)
|
||||
l.WithField("error", err)
|
||||
}
|
||||
|
||||
l.Warn("failed to notify panel of server install state")
|
||||
@@ -72,7 +72,7 @@ func (s *Server) Install(sync bool) error {
|
||||
// the install is completed.
|
||||
s.Events().Publish(InstallCompletedEvent, "")
|
||||
|
||||
return err
|
||||
return errors.WithStackIf(err)
|
||||
}
|
||||
|
||||
// Reinstalls a server's software by utilizing the install script for the server egg. This
|
||||
@@ -81,7 +81,7 @@ func (s *Server) Reinstall() error {
|
||||
if s.Environment.State() != environment.ProcessOfflineState {
|
||||
s.Log().Debug("waiting for server instance to enter a stopped state")
|
||||
if err := s.Environment.WaitForStop(s.Context(), time.Second*10, true); err != nil {
|
||||
return err
|
||||
return errors.WrapIf(err, "install: failed to stop running environment")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ func (ip *InstallationProcess) Run() error {
|
||||
|
||||
// Returns the location of the temporary data for the installation process.
|
||||
func (ip *InstallationProcess) tempDir() string {
|
||||
return filepath.Join(os.TempDir(), "pterodactyl/", ip.Server.ID())
|
||||
return filepath.Join(config.Get().System.TmpDirectory, ip.Server.ID())
|
||||
}
|
||||
|
||||
// Writes the installation script to a temporary file on the host machine so that it
|
||||
|
||||
@@ -125,7 +125,7 @@ func (h *Handler) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Filecmd hander for basic SFTP system calls related to files, but not anything to do with reading
|
||||
// Filecmd handler for basic SFTP system calls related to files, but not anything to do with reading
|
||||
// or writing to those files.
|
||||
func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||
if h.ro {
|
||||
|
||||
260
sftp/server.go
260
sftp/server.go
@@ -1,11 +1,17 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
@@ -16,7 +22,6 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
@@ -51,37 +56,28 @@ func New(m *server.Manager) *SFTPServer {
|
||||
// SFTP connections. This will automatically generate an ED25519 key if one does
|
||||
// not already exist on the system for host key verification purposes.
|
||||
func (c *SFTPServer) Run() error {
|
||||
if _, err := os.Stat(c.PrivateKeyPath()); os.IsNotExist(err) {
|
||||
if err := c.generateED25519PrivateKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "sftp: could not stat private key file")
|
||||
}
|
||||
pb, err := os.ReadFile(c.PrivateKeyPath())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sftp: could not read private key file")
|
||||
}
|
||||
private, err := ssh.ParsePrivateKey(pb)
|
||||
keys, err := c.loadPrivateKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf := &ssh.ServerConfig{
|
||||
NoClientAuth: false,
|
||||
MaxAuthTries: 6,
|
||||
PasswordCallback: c.passwordCallback,
|
||||
NoClientAuth: false,
|
||||
MaxAuthTries: 6,
|
||||
PasswordCallback: c.passwordCallback,
|
||||
PublicKeyCallback: c.publicKeyCallback,
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
conf.AddHostKey(k)
|
||||
}
|
||||
conf.AddHostKey(private)
|
||||
|
||||
listener, err := net.Listen("tcp", c.Listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
public := string(ssh.MarshalAuthorizedKey(private.PublicKey()))
|
||||
log.WithField("listen", c.Listen).WithField("public_key", strings.Trim(public, "\n")).Info("sftp server listening for connections")
|
||||
|
||||
log.WithField("listen", c.Listen).Info("sftp server listening for connections")
|
||||
for {
|
||||
if conn, _ := listener.Accept(); conn != nil {
|
||||
go func(conn net.Conn) {
|
||||
@@ -107,7 +103,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||
// If its not a session channel we just move on because its not something we
|
||||
// know how to handle at this point.
|
||||
if ch.ChannelType() != "session" {
|
||||
ch.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
_ = ch.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -121,7 +117,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||
// Channels have a type that is dependent on the protocol. For SFTP
|
||||
// this is "subsystem" with a payload that (should) be "sftp". Discard
|
||||
// anything else we receive ("pty", "shell", etc)
|
||||
req.Reply(req.Type == "subsystem" && string(req.Payload[4:]) == "sftp", nil)
|
||||
_ = req.Reply(req.Type == "subsystem" && string(req.Payload[4:]) == "sftp", nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
@@ -139,6 +135,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||
return s.ID() == uuid
|
||||
})
|
||||
if srv == nil {
|
||||
_ = conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -146,37 +143,160 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||
// them access to the underlying filesystem.
|
||||
handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv).Handlers())
|
||||
if err := handler.Serve(); err == io.EOF {
|
||||
handler.Close()
|
||||
_ = handler.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a new ED25519 private key that is used for host authentication when
|
||||
// a user connects to the SFTP server.
|
||||
func (c *SFTPServer) generateED25519PrivateKey() error {
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to generate ED25519 private key")
|
||||
func (c *SFTPServer) loadPrivateKeys() ([]ssh.Signer, error) {
|
||||
if _, err := os.Stat(path.Join(c.BasePath, ".sftp/id_rsa")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.generateRSAPrivateKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath()), 0o755); err != nil {
|
||||
return errors.Wrap(err, "sftp: could not create internal sftp data directory")
|
||||
}
|
||||
o, err := os.OpenFile(c.PrivateKeyPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
rsaBytes, err := ioutil.ReadFile(path.Join(c.BasePath, ".sftp/id_rsa"))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
return nil, errors.Wrap(err, "sftp/server: could not read private key file")
|
||||
}
|
||||
rsaPrivateKey, err := ssh.ParsePrivateKey(rsaBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(c.BasePath, ".sftp/id_ecdsa")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.generateECDSAPrivateKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ecdsaBytes, err := ioutil.ReadFile(path.Join(c.BasePath, ".sftp/id_ecdsa"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sftp/server: could not read private key file")
|
||||
}
|
||||
ecdsaPrivateKey, err := ssh.ParsePrivateKey(ecdsaBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(c.BasePath, ".sftp/id_ed25519")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.generateEd25519PrivateKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ed25519Bytes, err := ioutil.ReadFile(path.Join(c.BasePath, ".sftp/id_ed25519"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sftp/server: could not read private key file")
|
||||
}
|
||||
ed25519PrivateKey, err := ssh.ParsePrivateKey(ed25519Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []ssh.Signer{
|
||||
rsaPrivateKey,
|
||||
ecdsaPrivateKey,
|
||||
ed25519PrivateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateRSAPrivateKey generates a RSA-4096 private key that will be used by the SFTP server.
|
||||
func (c *SFTPServer) generateRSAPrivateKey() error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath("rsa")), 0o755); err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not create .sftp directory")
|
||||
}
|
||||
o, err := os.OpenFile(c.PrivateKeyPath("rsa"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
b, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to marshal private key into bytes")
|
||||
}
|
||||
if err := pem.Encode(o, &pem.Block{Type: "PRIVATE KEY", Bytes: b}); err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to write ED25519 private key to disk")
|
||||
if err := pem.Encode(o, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateECDSAPrivateKey generates a ECDSA-P256 private key that will be used by the SFTP server.
|
||||
func (c *SFTPServer) generateECDSAPrivateKey() error {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath("ecdsa")), 0o755); err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not create .sftp directory")
|
||||
}
|
||||
o, err := os.OpenFile(c.PrivateKeyPath("ecdsa"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pem.Encode(o, &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: privBytes,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateEd25519PrivateKey generates an ed25519 private key that will be used by the SFTP server.
|
||||
func (c *SFTPServer) generateEd25519PrivateKey() error {
|
||||
_, key, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath("ed25519")), 0o755); err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not create .sftp directory")
|
||||
}
|
||||
o, err := os.OpenFile(c.PrivateKeyPath("ed25519"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pem.Encode(o, &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: privBytes,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrivateKeyPath returns the path the host private key for this server instance.
|
||||
func (c *SFTPServer) PrivateKeyPath(name string) string {
|
||||
return path.Join(c.BasePath, ".sftp", "id_"+name)
|
||||
}
|
||||
|
||||
// A function capable of validating user credentials with the Panel API.
|
||||
func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
request := remote.SftpAuthRequest{
|
||||
@@ -185,6 +305,7 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||
IP: conn.RemoteAddr().String(),
|
||||
SessionID: conn.SessionID(),
|
||||
ClientVersion: conn.ClientVersion(),
|
||||
Type: "password",
|
||||
}
|
||||
|
||||
logger := log.WithFields(log.Fields{"subsystem": "sftp", "username": conn.User(), "ip": conn.RemoteAddr().String()})
|
||||
@@ -195,6 +316,11 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||
return nil, &remote.SftpInvalidCredentialsError{}
|
||||
}
|
||||
|
||||
if len(pass) < 1 {
|
||||
logger.Warn("failed to validate user credentials (invalid format)")
|
||||
return nil, &remote.SftpInvalidCredentialsError{}
|
||||
}
|
||||
|
||||
resp, err := c.manager.Client().ValidateSftpCredentials(context.Background(), request)
|
||||
if err != nil {
|
||||
if _, ok := err.(*remote.SftpInvalidCredentialsError); ok {
|
||||
@@ -217,7 +343,55 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||
return sshPerm, nil
|
||||
}
|
||||
|
||||
// PrivateKeyPath returns the path the host private key for this server instance.
|
||||
func (c *SFTPServer) PrivateKeyPath() string {
|
||||
return path.Join(c.BasePath, ".sftp/id_ed25519")
|
||||
func (c *SFTPServer) publicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
request := remote.SftpAuthRequest{
|
||||
User: conn.User(),
|
||||
Pass: "KEKW",
|
||||
IP: conn.RemoteAddr().String(),
|
||||
SessionID: conn.SessionID(),
|
||||
ClientVersion: conn.ClientVersion(),
|
||||
Type: "publicKey",
|
||||
}
|
||||
|
||||
logger := log.WithFields(log.Fields{"subsystem": "sftp", "username": conn.User(), "ip": conn.RemoteAddr().String()})
|
||||
logger.Debug("validating public key for SFTP connection")
|
||||
|
||||
if !validUsernameRegexp.MatchString(request.User) {
|
||||
logger.Warn("failed to validate user credentials (invalid format)")
|
||||
return nil, &remote.SftpInvalidCredentialsError{}
|
||||
}
|
||||
|
||||
resp, err := c.manager.Client().ValidateSftpCredentials(context.Background(), request)
|
||||
if err != nil {
|
||||
if _, ok := err.(*remote.SftpInvalidCredentialsError); ok {
|
||||
logger.Warn("failed to validate user credentials (invalid username or password)")
|
||||
} else {
|
||||
logger.WithField("error", err).Error("encountered an error while trying to validate user credentials")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp.SSHKeys) < 1 {
|
||||
return nil, &remote.SftpInvalidCredentialsError{}
|
||||
}
|
||||
|
||||
for _, k := range resp.SSHKeys {
|
||||
storedPublicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(key.Marshal(), storedPublicKey.Marshal()) {
|
||||
continue
|
||||
}
|
||||
|
||||
return &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"uuid": resp.Server,
|
||||
"user": conn.User(),
|
||||
"permissions": strings.Join(resp.Permissions, ","),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, &remote.SftpInvalidCredentialsError{}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package system
|
||||
|
||||
var Version = "1.6.1"
|
||||
var Version = "develop"
|
||||
|
||||
@@ -42,7 +42,6 @@ func (l *Locker) Acquire() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// TryAcquire will attempt to acquire a power-lock until the context provided
|
||||
// is canceled.
|
||||
func (l *Locker) TryAcquire(ctx context.Context) error {
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestPower(t *testing.T) {
|
||||
|
||||
l.Acquire()
|
||||
go func() {
|
||||
time.AfterFunc(time.Millisecond * 50, func() {
|
||||
time.AfterFunc(time.Millisecond*50, func() {
|
||||
l.Release()
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -44,7 +44,7 @@ func (r *Rate) Try() bool {
|
||||
// Reset resets the internal state of the rate limiter back to zero.
|
||||
func (r *Rate) Reset() {
|
||||
r.mu.Lock()
|
||||
r.count = 0
|
||||
r.last = time.Now()
|
||||
r.count = 0
|
||||
r.last = time.Now()
|
||||
r.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestRate(t *testing.T) {
|
||||
g.It("resets back to zero when called", func() {
|
||||
r := NewRate(10, time.Second)
|
||||
for i := 0; i < 100; i++ {
|
||||
if i % 10 == 0 {
|
||||
if i%10 == 0 {
|
||||
r.Reset()
|
||||
}
|
||||
g.Assert(r.Try()).IsTrue()
|
||||
|
||||
Reference in New Issue
Block a user