Basic SSH server scaffolding and password auth

This commit is contained in:
kayos@tcp.direct 2023-01-08 11:44:13 -08:00
parent 968706823c
commit 570f7147c6
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
6 changed files with 120 additions and 9 deletions

View File

@ -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,

View File

@ -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")

View File

@ -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")

View File

@ -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...)
}

View File

@ -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()
}
})
}

22
internal/sshui/util.go Normal file
View File

@ -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)
}