Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ee280f1deb | ||
|
355c10c1e4 | ||
|
04933c153b | ||
|
f1344f1a82 | ||
|
d874af85db | ||
|
54b6033392 | ||
|
10a2ffc0a7 | ||
|
ede1cdc76f | ||
|
9e98287172 | ||
|
6653466ca8 | ||
|
f54a736353 | ||
|
d3360f0fd9 |
83
cmd/root.go
83
cmd/root.go
|
@ -9,11 +9,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/NYTimes/logrotate"
|
"github.com/NYTimes/logrotate"
|
||||||
|
@ -28,6 +30,7 @@ import (
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
|
"github.com/pterodactyl/wings/internal/notify"
|
||||||
"github.com/pterodactyl/wings/loggers/cli"
|
"github.com/pterodactyl/wings/loggers/cli"
|
||||||
"github.com/pterodactyl/wings/remote"
|
"github.com/pterodactyl/wings/remote"
|
||||||
"github.com/pterodactyl/wings/router"
|
"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.
|
// Check if the server should run with TLS but using autocert.
|
||||||
if autotls {
|
go func(s *http.Server, api config.ApiConfiguration, sys config.SystemConfiguration, autotls bool, tlshostname string) {
|
||||||
m := autocert.Manager{
|
if autotls {
|
||||||
Prompt: autocert.AcceptTOS,
|
m := autocert.Manager{
|
||||||
Cache: autocert.DirCache(path.Join(sys.RootDirectory, "/.tls-cache")),
|
Prompt: autocert.AcceptTOS,
|
||||||
HostPolicy: autocert.HostWhitelist(tlshostname),
|
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")
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
// Start the main http server with TLS using autocert.
|
log.WithField("hostname", tlshostname).Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
|
||||||
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")
|
// 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
|
c := make(chan os.Signal, 1)
|
||||||
// config on the server and then serve it over normal HTTP.
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
if api.Ssl.Enabled {
|
<-c
|
||||||
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")
|
if err := notify.Stopping(); err != nil {
|
||||||
}
|
log.WithField("error", err).Error("failed to notify systemd of stopping state")
|
||||||
return
|
|
||||||
}
|
|
||||||
s.TLSConfig = nil
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
log.WithField("error", err).Fatal("failed to configure HTTP server")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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"`
|
IP string `json:"ip"`
|
||||||
SessionID []byte `json:"session_id"`
|
SessionID []byte `json:"session_id"`
|
||||||
ClientVersion []byte `json:"client_version"`
|
ClientVersion []byte `json:"client_version"`
|
||||||
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SftpAuthResponse is returned by the Panel when a pair of SFTP credentials
|
// 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
|
// matched as well as the permissions that are assigned to the authenticated
|
||||||
// user for the SFTP subsystem.
|
// user for the SFTP subsystem.
|
||||||
type SftpAuthResponse struct {
|
type SftpAuthResponse struct {
|
||||||
|
SSHKeys []string `json:"ssh_keys"`
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Token string `json:"token"`
|
|
||||||
Permissions []string `json:"permissions"`
|
Permissions []string `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,5 @@ func (h *Handler) listenForServerEvents(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ func (h *Handler) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
||||||
return f, nil
|
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.
|
// or writing to those files.
|
||||||
func (h *Handler) Filecmd(request *sftp.Request) error {
|
func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||||
if h.ro {
|
if h.ro {
|
||||||
|
|
260
sftp/server.go
260
sftp/server.go
|
@ -1,11 +1,17 @@
|
||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -16,7 +22,6 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/config"
|
"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
|
// SFTP connections. This will automatically generate an ED25519 key if one does
|
||||||
// not already exist on the system for host key verification purposes.
|
// not already exist on the system for host key verification purposes.
|
||||||
func (c *SFTPServer) Run() error {
|
func (c *SFTPServer) Run() error {
|
||||||
if _, err := os.Stat(c.PrivateKeyPath()); os.IsNotExist(err) {
|
keys, err := c.loadPrivateKeys()
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := &ssh.ServerConfig{
|
conf := &ssh.ServerConfig{
|
||||||
NoClientAuth: false,
|
NoClientAuth: false,
|
||||||
MaxAuthTries: 6,
|
MaxAuthTries: 6,
|
||||||
PasswordCallback: c.passwordCallback,
|
PasswordCallback: c.passwordCallback,
|
||||||
|
PublicKeyCallback: c.publicKeyCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
conf.AddHostKey(k)
|
||||||
}
|
}
|
||||||
conf.AddHostKey(private)
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", c.Listen)
|
listener, err := net.Listen("tcp", c.Listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
public := string(ssh.MarshalAuthorizedKey(private.PublicKey()))
|
log.WithField("listen", c.Listen).Info("sftp server listening for connections")
|
||||||
log.WithField("listen", c.Listen).WithField("public_key", strings.Trim(public, "\n")).Info("sftp server listening for connections")
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if conn, _ := listener.Accept(); conn != nil {
|
if conn, _ := listener.Accept(); conn != nil {
|
||||||
go func(conn net.Conn) {
|
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
|
// If its not a session channel we just move on because its not something we
|
||||||
// know how to handle at this point.
|
// know how to handle at this point.
|
||||||
if ch.ChannelType() != "session" {
|
if ch.ChannelType() != "session" {
|
||||||
ch.Reject(ssh.UnknownChannelType, "unknown channel type")
|
_ = ch.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||||
continue
|
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
|
// Channels have a type that is dependent on the protocol. For SFTP
|
||||||
// this is "subsystem" with a payload that (should) be "sftp". Discard
|
// this is "subsystem" with a payload that (should) be "sftp". Discard
|
||||||
// anything else we receive ("pty", "shell", etc)
|
// 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)
|
}(requests)
|
||||||
|
|
||||||
|
@ -139,6 +135,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||||
return s.ID() == uuid
|
return s.ID() == uuid
|
||||||
})
|
})
|
||||||
if srv == nil {
|
if srv == nil {
|
||||||
|
_ = conn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,37 +143,160 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||||
// them access to the underlying filesystem.
|
// them access to the underlying filesystem.
|
||||||
handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv).Handlers())
|
handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv).Handlers())
|
||||||
if err := handler.Serve(); err == io.EOF {
|
if err := handler.Serve(); err == io.EOF {
|
||||||
handler.Close()
|
_ = handler.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a new ED25519 private key that is used for host authentication when
|
func (c *SFTPServer) loadPrivateKeys() ([]ssh.Signer, error) {
|
||||||
// a user connects to the SFTP server.
|
if _, err := os.Stat(path.Join(c.BasePath, ".sftp/id_rsa")); err != nil {
|
||||||
func (c *SFTPServer) generateED25519PrivateKey() error {
|
if !os.IsNotExist(err) {
|
||||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return errors.Wrap(err, "sftp: failed to generate ED25519 private key")
|
|
||||||
|
if err := c.generateRSAPrivateKey(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath()), 0o755); err != nil {
|
rsaBytes, err := ioutil.ReadFile(path.Join(c.BasePath, ".sftp/id_rsa"))
|
||||||
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)
|
|
||||||
if err != nil {
|
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()
|
defer o.Close()
|
||||||
|
|
||||||
b, err := x509.MarshalPKCS8PrivateKey(priv)
|
if err := pem.Encode(o, &pem.Block{
|
||||||
if err != nil {
|
Type: "RSA PRIVATE KEY",
|
||||||
return errors.Wrap(err, "sftp: failed to marshal private key into bytes")
|
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||||
}
|
}); err != nil {
|
||||||
if err := pem.Encode(o, &pem.Block{Type: "PRIVATE KEY", Bytes: b}); err != nil {
|
return err
|
||||||
return errors.Wrap(err, "sftp: failed to write ED25519 private key to disk")
|
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// A function capable of validating user credentials with the Panel API.
|
||||||
func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
request := remote.SftpAuthRequest{
|
request := remote.SftpAuthRequest{
|
||||||
|
@ -185,6 +305,7 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||||
IP: conn.RemoteAddr().String(),
|
IP: conn.RemoteAddr().String(),
|
||||||
SessionID: conn.SessionID(),
|
SessionID: conn.SessionID(),
|
||||||
ClientVersion: conn.ClientVersion(),
|
ClientVersion: conn.ClientVersion(),
|
||||||
|
Type: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.WithFields(log.Fields{"subsystem": "sftp", "username": conn.User(), "ip": conn.RemoteAddr().String()})
|
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{}
|
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)
|
resp, err := c.manager.Client().ValidateSftpCredentials(context.Background(), request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*remote.SftpInvalidCredentialsError); ok {
|
if _, ok := err.(*remote.SftpInvalidCredentialsError); ok {
|
||||||
|
@ -217,7 +343,55 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||||
return sshPerm, nil
|
return sshPerm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateKeyPath returns the path the host private key for this server instance.
|
func (c *SFTPServer) publicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
func (c *SFTPServer) PrivateKeyPath() string {
|
request := remote.SftpAuthRequest{
|
||||||
return path.Join(c.BasePath, ".sftp/id_ed25519")
|
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{}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user