Begin implementing SFTP server code
This commit is contained in:
parent
4a68eabd1b
commit
865c1b3bad
42
api/api.go
42
api/api.go
|
@ -24,6 +24,12 @@ type PanelRequest struct {
|
||||||
Response *http.Response
|
Response *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsRequestError (err error) bool {
|
||||||
|
_, ok := err.(*PanelRequest)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// Builds the base request instance that can be used with the HTTP client.
|
// Builds the base request instance that can be used with the HTTP client.
|
||||||
func (r *PanelRequest) GetClient() *http.Client {
|
func (r *PanelRequest) GetClient() *http.Client {
|
||||||
return &http.Client{Timeout: time.Second * 30}
|
return &http.Client{Timeout: time.Second * 30}
|
||||||
|
@ -60,6 +66,21 @@ func (r *PanelRequest) Get(url string) (*http.Response, error) {
|
||||||
return c.Do(req)
|
return c.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PanelRequest) Post(url string, data []byte) (*http.Response, error) {
|
||||||
|
c := r.GetClient()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, r.GetEndpoint(url), bytes.NewBuffer(data))
|
||||||
|
req = r.SetHeaders(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("POST request to endpoint", zap.String("endpoint", r.GetEndpoint(url)), zap.Any("headers", req.Header))
|
||||||
|
|
||||||
|
return c.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
// Determines if the API call encountered an error. If no request has been made
|
// Determines if the API call encountered an error. If no request has been made
|
||||||
// the response will be false.
|
// the response will be false.
|
||||||
func (r *PanelRequest) HasError() bool {
|
func (r *PanelRequest) HasError() bool {
|
||||||
|
@ -67,7 +88,7 @@ func (r *PanelRequest) HasError() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Response.StatusCode >= 300 || r.Response.StatusCode < 200;
|
return r.Response.StatusCode >= 300 || r.Response.StatusCode < 200
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the body from the response and returns it, then replaces it on the response
|
// Reads the body from the response and returns it, then replaces it on the response
|
||||||
|
@ -86,29 +107,38 @@ func (r *PanelRequest) ReadBody() ([]byte, error) {
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PanelRequest) HttpResponseCode() int {
|
||||||
|
if r.Response == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Response.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the error message from the API call as a string. The error message will be formatted
|
// Returns the error message from the API call as a string. The error message will be formatted
|
||||||
// similar to the below example:
|
// similar to the below example:
|
||||||
//
|
//
|
||||||
// HttpNotFoundException: The requested resource does not exist. (HTTP/404)
|
// HttpNotFoundException: The requested resource does not exist. (HTTP/404)
|
||||||
func (r *PanelRequest) Error() (string, error) {
|
func (r *PanelRequest) Error() string {
|
||||||
body, err := r.ReadBody()
|
body, err := r.ReadBody()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("got body", zap.ByteString("b", body))
|
zap.S().Debugw("got body", zap.ByteString("b", body))
|
||||||
_, valueType, _, err := jsonparser.Get(body, "errors")
|
_, valueType, _, err := jsonparser.Get(body, "errors")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if valueType != jsonparser.Object {
|
if valueType != jsonparser.Object {
|
||||||
return "no error object present on response", nil
|
return "no error object present on response"
|
||||||
}
|
}
|
||||||
|
|
||||||
code, _ := jsonparser.GetString(body, "errors.0.code")
|
code, _ := jsonparser.GetString(body, "errors.0.code")
|
||||||
status, _ := jsonparser.GetString(body, "errors.0.status")
|
status, _ := jsonparser.GetString(body, "errors.0.status")
|
||||||
detail, _ := jsonparser.GetString(body, "errors.0.detail")
|
detail, _ := jsonparser.GetString(body, "errors.0.detail")
|
||||||
|
|
||||||
return fmt.Sprintf("%s: %s (HTTP/%s)", code, detail, status), nil
|
return fmt.Sprintf("%s: %s (HTTP/%s)", code, detail, status)
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/parser"
|
"github.com/pterodactyl/wings/parser"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -39,10 +40,9 @@ func (r *PanelRequest) GetServerConfiguration(uuid string) (*ServerConfiguration
|
||||||
r.Response = resp
|
r.Response = resp
|
||||||
|
|
||||||
if r.HasError() {
|
if r.HasError() {
|
||||||
e, err := r.Error()
|
zap.S().Warnw("got error", zap.String("message", r.Error()))
|
||||||
zap.S().Warnw("got error", zap.String("message", e), zap.Error(err))
|
|
||||||
|
|
||||||
return nil, err
|
return nil, errors.WithStack(errors.New(r.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &ServerConfiguration{}
|
res := &ServerConfiguration{}
|
||||||
|
|
39
api/sftp_endpoints.go
Normal file
39
api/sftp_endpoints.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/pterodactyl/sftp-server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *PanelRequest) ValidateSftpCredentials(request sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
||||||
|
b, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.Post("/sftp/auth/login", b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
r.Response = resp
|
||||||
|
|
||||||
|
if r.HasError() {
|
||||||
|
if r.HttpResponseCode() == 403 {
|
||||||
|
return nil, sftp_server.InvalidCredentialsError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.WithStack(errors.New(r.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
response := new(sftp_server.AuthenticationResponse)
|
||||||
|
body, _ := r.ReadBody()
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
4
go.mod
4
go.mod
|
@ -2,6 +2,8 @@ module github.com/pterodactyl/wings
|
||||||
|
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
|
replace github.com/pterodactyl/sftp-server => ../sftp-server
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/Jeffail/gabs/v2 v2.2.0
|
github.com/Jeffail/gabs/v2 v2.2.0
|
||||||
|
@ -41,7 +43,7 @@ require (
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce
|
||||||
github.com/sirupsen/logrus v1.0.5 // indirect
|
github.com/sirupsen/logrus v1.0.5 // indirect
|
||||||
go.uber.org/zap v1.9.1
|
go.uber.org/zap v1.9.1
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac // indirect
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac // indirect
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -65,6 +65,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
|
||||||
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/pkg/errors"
|
||||||
sftpserver "github.com/pterodactyl/sftp-server/src/server"
|
"github.com/pterodactyl/sftp-server"
|
||||||
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"go.uber.org/zap"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Initialize(config *config.Configuration) error {
|
func Initialize(config *config.Configuration) error {
|
||||||
c := sftpserver.Configuration{
|
c := &sftp_server.Server{
|
||||||
Data: []byte("{}"),
|
User: sftp_server.SftpUser{
|
||||||
Cache: cache.New(5*time.Minute, 10*time.Minute),
|
|
||||||
User: sftpserver.SftpUser{
|
|
||||||
Uid: config.System.User.Uid,
|
Uid: config.System.User.Uid,
|
||||||
Gid: config.System.User.Gid,
|
Gid: config.System.User.Gid,
|
||||||
},
|
},
|
||||||
Settings: sftpserver.Settings{
|
Settings: sftp_server.Settings{
|
||||||
BasePath: config.System.Data,
|
BasePath: config.System.Data,
|
||||||
ReadOnly: config.System.Sftp.ReadOnly,
|
ReadOnly: config.System.Sftp.ReadOnly,
|
||||||
BindAddress: config.System.Sftp.Address,
|
BindAddress: config.System.Sftp.Address,
|
||||||
|
@ -24,7 +23,30 @@ func Initialize(config *config.Configuration) error {
|
||||||
ServerDataFolder: path.Join(config.System.Data, "/servers"),
|
ServerDataFolder: path.Join(config.System.Data, "/servers"),
|
||||||
DisableDiskCheck: config.System.Sftp.DisableDiskChecking,
|
DisableDiskCheck: config.System.Sftp.DisableDiskChecking,
|
||||||
},
|
},
|
||||||
|
CredentialValidator: validateCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Initalize()
|
if err := sftp_server.New(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ConfigureLogger(func() *zap.SugaredLogger {
|
||||||
|
return zap.S().Named("sftp")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize the SFTP server in a background thread since this is
|
||||||
|
// a long running operation.
|
||||||
|
go func(instance *sftp_server.Server) {
|
||||||
|
if err := c.Initalize(); err != nil {
|
||||||
|
zap.S().Named("sftp").Errorw("failed to initialize SFTP subsystem", zap.Error(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates a set of credentials for a SFTP login aganist Pterodactyl Panel and returns
|
||||||
|
// the server's UUID if the credentials were valid.
|
||||||
|
func validateCredentials(c sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
||||||
|
return api.NewRequester().ValidateSftpCredentials(c)
|
||||||
}
|
}
|
||||||
|
|
6
wings.go
6
wings.go
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Entrypoint for the Wings application. Configures the logger and checks any
|
// Entrypoint for the Wings application. Configures the logger and checks any
|
||||||
|
@ -141,10 +140,7 @@ func main() {
|
||||||
|
|
||||||
// If the SFTP subsystem should be started, do so now.
|
// If the SFTP subsystem should be started, do so now.
|
||||||
if c.System.Sftp.UseInternalSystem {
|
if c.System.Sftp.UseInternalSystem {
|
||||||
if err := sftp.Initialize(c); err != nil {
|
sftp.Initialize(c)
|
||||||
zap.S().Fatalw("failed to initialize SFTP subsystem", zap.Error(errors.WithStack(err)))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Router{
|
r := &Router{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user