From ada1f26b680331d9c6fb1e3ff5ea0b37a9df35ee Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 15:41:55 +0100 Subject: [PATCH] Add keystore class, separate keystore tool executable --- cmd/ssh/main.go | 37 ++++++++++++++++++----- cmd/ssh/sshkey/main.go | 44 +++++++++++++++++++++++++++ internal/ssh/keystore.go | 64 ++++++++++++++++++++++++++++++++++++++++ internal/ssh/ssh.go | 29 +++++++++--------- 4 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 cmd/ssh/sshkey/main.go create mode 100644 internal/ssh/keystore.go diff --git a/cmd/ssh/main.go b/cmd/ssh/main.go index 972bdaf..0227616 100644 --- a/cmd/ssh/main.go +++ b/cmd/ssh/main.go @@ -6,13 +6,14 @@ import ( "fmt" "path" "os" + "os/signal" "sync" + "syscall" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/logging" - "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/ssh" ) @@ -47,9 +48,15 @@ func main() { os.Exit(1) } + ctx := context.Background() + logg.WarnCtxf(ctx, "!!!!! WARNING WARNING WARNING") + logg.WarnCtxf(ctx, "!!!!! =======================") + logg.WarnCtxf(ctx, "!!!!! This is not a production ready server!") + logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!") + logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)") + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port) - ctx := context.Background() pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ @@ -64,20 +71,24 @@ func main() { cfg.EngineDebug = true } - keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm") - authKeyStore := storage.NewThreadGdbmDb() - err = authKeyStore.Connect(ctx, keyStoreFile) + authKeyStore, err := ssh.NewSshKeyStore(ctx, dbDir) if err != nil { fmt.Fprintf(os.Stderr, "keystore file open error: %v", err) os.Exit(1) } - defer func() { - err := authKeyStore.Close() + defer func () { + logg.TraceCtxf(ctx, "shutdown auth key store reached") + err = authKeyStore.Close() if err != nil { logg.ErrorCtxf(ctx, "keystore close error", "err", err) } }() + cint := make(chan os.Signal) + cterm := make(chan os.Signal) + signal.Notify(cint, os.Interrupt, syscall.SIGINT) + signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) + runner := &ssh.SshRunner{ Cfg: cfg, Debug: engineDebug, @@ -88,5 +99,17 @@ func main() { Host: host, Port: port, } + go func() { + select { + case _ = <-cint: + case _ = <-cterm: + } + logg.TraceCtxf(ctx, "shutdown runner reached") + err := runner.Stop() + if err != nil { + logg.ErrorCtxf(ctx, "runner stop error", "err", err) + } + + }() runner.Run(ctx, authKeyStore) } diff --git a/cmd/ssh/sshkey/main.go b/cmd/ssh/sshkey/main.go new file mode 100644 index 0000000..87b89a3 --- /dev/null +++ b/cmd/ssh/sshkey/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "git.grassecon.net/urdt/ussd/internal/ssh" +) + +func main() { + var dbDir string + var sessionId string + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&sessionId, "i", "", "session id") + flag.Parse() + + if sessionId == "" { + fmt.Fprintf(os.Stderr, "empty session id\n") + os.Exit(1) + } + + ctx := context.Background() + + sshKeyFile := flag.Arg(0) + if sshKeyFile == "" { + fmt.Fprintf(os.Stderr, "missing key file argument\n") + os.Exit(1) + } + + store, err := ssh.NewSshKeyStore(ctx, dbDir) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + defer store.Close() + + err = store.AddFromFile(ctx, sshKeyFile, sessionId) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} diff --git a/internal/ssh/keystore.go b/internal/ssh/keystore.go new file mode 100644 index 0000000..fed7233 --- /dev/null +++ b/internal/ssh/keystore.go @@ -0,0 +1,64 @@ +package ssh + +import ( + "context" + "fmt" + "os" + "path" + + "golang.org/x/crypto/ssh" + + "git.defalsify.org/vise.git/db" + + "git.grassecon.net/urdt/ussd/internal/storage" +) + +type SshKeyStore struct { + store db.Db +} + +func NewSshKeyStore(ctx context.Context, dbDir string) (*SshKeyStore, error) { + keyStore := &SshKeyStore{} + keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm") + keyStore.store = storage.NewThreadGdbmDb() + err := keyStore.store.Connect(ctx, keyStoreFile) + if err != nil { + return nil, err + } + return keyStore, nil +} + +func(s *SshKeyStore) AddFromFile(ctx context.Context, fp string, sessionId string) error { + _, err := os.Stat(fp) + if err != nil { + return fmt.Errorf("cannot open ssh server public key file: %v\n", err) + } + + publicBytes, err := os.ReadFile(fp) + if err != nil { + return fmt.Errorf("Failed to load public key: %v", err) + } + pubKey, _, _, _, err := ssh.ParseAuthorizedKey(publicBytes) + if err != nil { + return fmt.Errorf("Failed to parse public key: %v", err) + } + k := append([]byte{0x01}, pubKey.Marshal()...) + s.store.SetPrefix(storage.DATATYPE_CUSTOM) + logg.Infof("Added key", "sessionId", sessionId, "public key", string(publicBytes)) + return s.store.Put(ctx, k, []byte(sessionId)) +} + +func(s *SshKeyStore) Get(ctx context.Context, pubKey ssh.PublicKey) (string, error) { + s.store.SetLanguage(nil) + s.store.SetPrefix(storage.DATATYPE_CUSTOM) + k := append([]byte{0x01}, pubKey.Marshal()...) + v, err := s.store.Get(ctx, k) + if err != nil { + return "", err + } + return string(v), nil +} + +func(s *SshKeyStore) Close() error { + return s.store.Close() +} diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go index 9cb00b3..31cbc3e 100644 --- a/internal/ssh/ssh.go +++ b/internal/ssh/ssh.go @@ -11,7 +11,6 @@ import ( "golang.org/x/crypto/ssh" - "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/resource" @@ -27,11 +26,11 @@ var ( type auther struct { Ctx context.Context - keyStore db.Db + keyStore *SshKeyStore auth map[string]string } -func NewAuther(ctx context.Context, keyStore db.Db) *auther { +func NewAuther(ctx context.Context, keyStore *SshKeyStore) *auther { return &auther{ Ctx: ctx, keyStore: keyStore, @@ -40,17 +39,13 @@ func NewAuther(ctx context.Context, keyStore db.Db) *auther { } func(a *auther) Check(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { - a.keyStore.SetLanguage(nil) - a.keyStore.SetPrefix(storage.DATATYPE_CUSTOM) - k := append([]byte{0x01}, pubKey.Marshal()...) - v, err := a.keyStore.Get(a.Ctx, k) + va, err := a.keyStore.Get(a.Ctx, pubKey) if err != nil { return nil, err } ka := hex.EncodeToString(conn.SessionID()) - va := string(v) a.auth[ka] = va - fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, v) + fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, va) return nil, nil } @@ -142,6 +137,11 @@ type SshRunner struct { Host string Port uint wg sync.WaitGroup + lst net.Listener +} + +func(s *SshRunner) Stop() error { + return s.lst.Close() } func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) { @@ -203,7 +203,7 @@ func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) { } // adapted example from crypto/ssh package, NewServerConn doc -func(s *SshRunner) Run(ctx context.Context, keyStore db.Db) { +func(s *SshRunner) Run(ctx context.Context, keyStore *SshKeyStore) { running := true // TODO: waitgroup should probably not be global @@ -224,18 +224,19 @@ func(s *SshRunner) Run(ctx context.Context, keyStore db.Db) { } cfg.AddHostKey(private) - lst, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) + s.lst, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) if err != nil { panic(err) } for running { - conn, err := lst.Accept() + conn, err := s.lst.Accept() if err != nil { - panic(err) + logg.ErrorCtxf(ctx, "ssh accept error", "err", err) + running = false + continue } - go func(conn net.Conn) { defer conn.Close() for true {