From 570f7147c6f066abd4e4f6a8979ed7059d54b540 Mon Sep 17 00:00:00 2001 From: "kayos@tcp.direct" Date: Sun, 8 Jan 2023 11:44:13 -0800 Subject: [PATCH] Basic SSH server scaffolding and password auth --- internal/config/config.go | 6 ++-- internal/data/db.go | 5 +++ internal/data/users.go | 3 ++ internal/sshui/server.go | 32 ++++++++++++++---- internal/sshui/server_test.go | 61 +++++++++++++++++++++++++++++++++++ internal/sshui/util.go | 22 +++++++++++++ 6 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 internal/sshui/server_test.go create mode 100644 internal/sshui/util.go diff --git a/internal/config/config.go b/internal/config/config.go index 04fcd06..49ab318 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -104,11 +104,11 @@ func setDefaults() { } Opt["http"] = map[string]interface{}{ - "bind_addr": "127.0.0.1:9090", + "listen": "127.0.0.1:9090", } Opt["ssh"] = map[string]interface{}{ - "bind_addr": "127.0.0.1:2222", + "listen": "127.0.0.1:2222", "host_key_dir": "~/.config/" + common.Title + "/.ssh", } @@ -188,7 +188,7 @@ func argParse() { func processOpts() { // string options and their exported variables stringOpt := map[string]*string{ - "http.bind_addr": &HTTPBind, + "http.listen": &HTTPBind, "logger.directory": &LogDir, "http.api_key": &APIKey, "ssh.listen": &SSHListen, diff --git a/internal/data/db.go b/internal/data/db.go index 49b0f06..8835e00 100644 --- a/internal/data/db.go +++ b/internal/data/db.go @@ -63,6 +63,11 @@ func Start() { startDB() } +func StartTest() { + testMode() + Start() +} + func Close() { if err := db.SyncAndCloseAll(); err != nil { log.Warn().Err(err).Msg("error syncing and closing db") diff --git a/internal/data/users.go b/internal/data/users.go index 043e544..3bbc7d2 100644 --- a/internal/data/users.go +++ b/internal/data/users.go @@ -84,6 +84,9 @@ func NewUserPass(hashIt bool, username, password string) *UserPass { } func (up *UserPass) Authenticate() error { + if up.Username == "" { + return errors.New("username cannot be empty") + } user, err := GetUser(up.Username) if err != nil { log.Warn().Err(err).Str("username", up.Username).Msg("error getting user") diff --git a/internal/sshui/server.go b/internal/sshui/server.go index c41edbf..65d95e2 100644 --- a/internal/sshui/server.go +++ b/internal/sshui/server.go @@ -1,24 +1,44 @@ package sshui import ( - "log" + "crypto/rand" + "crypto/rsa" + "os" + "path/filepath" "github.com/gliderlabs/ssh" "git.tcp.direct/kayos/ziggs/internal/config" + "git.tcp.direct/kayos/ziggs/internal/data" ) -func ServeSSH() { +func ServeSSH() error { var opts []ssh.Option - if config.SSHHostKey != "" { + switch config.SSHHostKey { + case "": + privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return err + } + if err = privateKey.Validate(); err != nil { + return err + } + dir, _ := filepath.Split(config.Filename) + newFile := filepath.Join(dir, "host_rsa") + if err = os.WriteFile(newFile, encodePrivateKeyToPEM(privateKey), 0600); err != nil { + return err + } + config.Snek.Set("ssh.host_key", newFile) + default: opts = append(opts, ssh.HostKeyFile(config.SSHHostKey)) } opts = append(opts, ssh.PasswordAuth(func(ctx ssh.Context, password string) bool { - - return false + attempt := data.NewUserPass(false, ctx.User(), password) + err := attempt.Authenticate() + return err == nil })) - log.Fatal(ssh.ListenAndServe(":2222", nil, opts...)) + return ssh.ListenAndServe(config.SSHListen, nil, opts...) } diff --git a/internal/sshui/server_test.go b/internal/sshui/server_test.go new file mode 100644 index 0000000..3fc4046 --- /dev/null +++ b/internal/sshui/server_test.go @@ -0,0 +1,61 @@ +package sshui + +import ( + "testing" + "time" + + "golang.org/x/crypto/ssh" + + "git.tcp.direct/kayos/ziggs/internal/config" + "git.tcp.direct/kayos/ziggs/internal/data" +) + +func TestServeSSH(t *testing.T) { + config.Init() + data.StartTest() + go func() { + t.Log("Starting SSH server") + err := ServeSSH() + if err != nil { + t.Error(err) + } + }() + time.Sleep(2 * time.Second) + _, err := data.NewUser("test", data.NewUserPass(true, "test", "test")) + if err != nil { + t.Fatal(err) + } + t.Run("GoodLoginPassword", func(t *testing.T) { + client, err := ssh.Dial("tcp", config.SSHListen, &ssh.ClientConfig{ + User: "test", + Auth: []ssh.AuthMethod{ + ssh.Password("test"), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err != nil { + t.Fatal(err) + } + session, err := client.NewSession() + if err != nil { + t.Error(err) + } + session.Close() + client.Close() + }) + t.Run("BadLoginPassword", func(t *testing.T) { + client, err := ssh.Dial("tcp", config.SSHListen, &ssh.ClientConfig{ + User: "test", + Auth: []ssh.AuthMethod{ + ssh.Password("yeet"), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if client != nil { + client.Close() + } + }) +} diff --git a/internal/sshui/util.go b/internal/sshui/util.go new file mode 100644 index 0000000..87cc3d4 --- /dev/null +++ b/internal/sshui/util.go @@ -0,0 +1,22 @@ +package sshui + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" +) + +// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format +func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte { + // Get ASN.1 DER format + privDER := x509.MarshalPKCS1PrivateKey(privateKey) + + // pem.Block + privBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: map[string]string{"gr33tz": "tcp.direct"}, + Bytes: privDER, + } + + return pem.EncodeToMemory(&privBlock) +}