repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
aa965ca
parent
b20dbf9
author
Antonio Mika
date
2025-03-09 22:42:33 -0400 EDT
More work on standalone ssh
5 files changed,  +185, -25
M go.mod
M cmd/pgs/test/main.go
+66, -3
 1@@ -2,24 +2,87 @@ package main
 2 
 3 import (
 4 	"context"
 5-	"log/slog"
 6+	"os"
 7 
 8+	"github.com/picosh/pico/pgs"
 9+	pgsdb "github.com/picosh/pico/pgs/db"
10 	"github.com/picosh/pico/shared"
11+	"github.com/picosh/pico/shared/storage"
12+	"github.com/picosh/send/auth"
13+	"github.com/picosh/send/list"
14+	"github.com/picosh/send/pipe"
15+	"github.com/picosh/send/protocols/scp"
16+	"github.com/picosh/utils"
17+	"golang.org/x/crypto/ssh"
18 )
19 
20 func main() {
21 	// Initialize the logger
22-	logger := slog.Default()
23+	logger := shared.CreateLogger("pgs-ssh")
24 
25 	ctx, cancel := context.WithCancel(context.Background())
26 	defer cancel()
27 
28+	minioURL := utils.GetEnv("MINIO_URL", "")
29+	minioUser := utils.GetEnv("MINIO_ROOT_USER", "")
30+	minioPass := utils.GetEnv("MINIO_ROOT_PASSWORD", "")
31+	dbURL := utils.GetEnv("DATABASE_URL", "")
32+
33+	dbpool, err := pgsdb.NewDB(dbURL, logger)
34+	if err != nil {
35+		panic(err)
36+	}
37+
38+	st, err := storage.NewStorageMinio(logger, minioURL, minioUser, minioPass)
39+	if err != nil {
40+		panic(err)
41+	}
42+
43+	cfg := pgs.NewPgsConfig(logger, dbpool, st)
44+
45+	sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
46+
47+	cacheClearingQueue := make(chan string, 100)
48+
49+	handler := pgs.NewUploadAssetHandler(
50+		cfg,
51+		cacheClearingQueue,
52+		ctx,
53+	)
54+
55 	// Create a new SSH server
56 	server := shared.NewSSHServer(ctx, logger, &shared.SSHServerConfig{
57 		ListenAddr: "localhost:2222",
58+		ServerConfig: &ssh.ServerConfig{
59+			PublicKeyCallback: sshAuth.PubkeyAuthHandler,
60+		},
61+		Middleware: []shared.SSHServerMiddleware{
62+			pipe.Middleware(handler, ""),
63+			list.Middleware(handler),
64+			scp.Middleware(handler),
65+			wishrsync.Middleware(handler),
66+			auth.Middleware(handler),
67+			wsh.PtyMdw(wsh.DeprecatedNotice()),
68+			WishMiddleware(handler),
69+			wsh.LogMiddleware(handler.GetLogger(s), handler.Cfg.DB),
70+		},
71 	})
72 
73-	err := server.ListenAndServe()
74+	pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
75+	if err != nil {
76+		logger.Error("failed to read private key file", "error", err)
77+		return
78+	}
79+
80+	signer, err := ssh.ParsePrivateKey(pemBytes)
81+	if err != nil {
82+		logger.Error("failed to parse private key", "error", err)
83+		return
84+	}
85+
86+	server.Config.AddHostKey(signer)
87+
88+	err = server.ListenAndServe()
89 	if err != nil {
90 		logger.Error("failed to start SSH server", "error", err)
91 		return
M go.mod
+17, -2
 1@@ -4,8 +4,23 @@ go 1.24
 2 
 3 toolchain go1.24.0
 4 
 5-// replace github.com/picosh/send => ../send
 6-// replace git.sr.ht/~rockorager/vaxis => ../../../src/vaxis
 7+replace github.com/picosh/tunkit => ../tunkit
 8+
 9+replace github.com/picosh/send => ../send
10+
11+// replace github.com/picosh/go-rsync-receiver => ../go-rsync-receiver
12+
13+replace github.com/picosh/pobj => ../pobj
14+
15+// replace github.com/picosh/pubsub => ../pubsub
16+
17+// replace github.com/picosh/utils => ../utils
18+
19+// replace git.sr.ht/~delthas/senpai => ../../senpai
20+
21+// replace git.sr.ht/~rockorager/vaxis => ../../vaxis
22+
23+// replace github.com/charmbracelet/wish => ../../wish
24 
25 require (
26 	git.sr.ht/~delthas/senpai v0.3.1-0.20250311003540-18f699aaf9b0
M pgs/ssh.go
+4, -5
 1@@ -11,7 +11,6 @@ import (
 2 	"github.com/charmbracelet/promwish"
 3 	"github.com/charmbracelet/ssh"
 4 	"github.com/charmbracelet/wish"
 5-	"github.com/picosh/pico/shared"
 6 	wsh "github.com/picosh/pico/wish"
 7 	"github.com/picosh/send/auth"
 8 	"github.com/picosh/send/list"
 9@@ -81,11 +80,11 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
10 		HttpHandler: createHttpHandler(cfg),
11 	}
12 
13-	sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
14+	// sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
15 	s, err := wish.NewServer(
16-		wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
17-		wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
18-		wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
19+		// wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
20+		// wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
21+		// wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
22 		tunkit.WithWebTunnel(webTunnel),
23 		withProxy(
24 			handler,
M shared/ssh.go
+11, -10
 1@@ -1,11 +1,12 @@
 2 package shared
 3 
 4 import (
 5+	"fmt"
 6 	"log/slog"
 7 
 8-	"github.com/charmbracelet/ssh"
 9 	"github.com/picosh/pico/db"
10 	"github.com/picosh/utils"
11+	"golang.org/x/crypto/ssh"
12 )
13 
14 type SshAuthHandler struct {
15@@ -24,7 +25,7 @@ func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger) *SshAuthHandler {
16 	}
17 }
18 
19-func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) bool {
20+func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
21 	pubkey := utils.KeyForKeyText(key)
22 	user, err := r.DB.FindUserByPubkey(pubkey)
23 	if err != nil {
24@@ -34,20 +35,20 @@ func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) b
25 			"key", string(key.Marshal()),
26 			"err", err,
27 		)
28-		return false
29+		return nil, err
30 	}
31 
32 	if user.Name == "" {
33 		r.Logger.Error("username is not set")
34-		return false
35+		return nil, fmt.Errorf("username is not set")
36 	}
37 
38-	if ctx.Permissions().Extensions == nil {
39-		ctx.Permissions().Extensions = map[string]string{}
40-	}
41-	ctx.Permissions().Extensions["user_id"] = user.ID
42-	ctx.Permissions().Extensions["pubkey"] = pubkey
43-	return true
44+	return &ssh.Permissions{
45+		Extensions: map[string]string{
46+			"user_id": user.ID,
47+			"pubkey":  pubkey,
48+		},
49+	}, nil
50 }
51 
52 func FindPlusFF(dbpool db.DB, cfg *ConfigSite, userID string) *db.FeatureFlag {
M shared/sshServer.go
+87, -5
  1@@ -5,6 +5,8 @@ import (
  2 	"errors"
  3 	"log/slog"
  4 	"net"
  5+	"sync"
  6+	"time"
  7 
  8 	"github.com/antoniomika/syncmap"
  9 	"golang.org/x/crypto/ssh"
 10@@ -16,6 +18,8 @@ type SSHServerConn struct {
 11 	Logger     *slog.Logger
 12 	Conn       *ssh.ServerConn
 13 	SSHServer  *SSHServer
 14+
 15+	mu sync.Mutex
 16 }
 17 
 18 func (sc *SSHServerConn) Close() error {
 19@@ -23,15 +27,94 @@ func (sc *SSHServerConn) Close() error {
 20 	return nil
 21 }
 22 
 23+type SSHServerConnSession struct {
 24+	ssh.Channel
 25+	*SSHServerConn
 26+}
 27+
 28+// Deadline implements context.Context.
 29+func (s *SSHServerConn) Deadline() (deadline time.Time, ok bool) {
 30+	s.mu.Lock()
 31+	defer s.mu.Unlock()
 32+
 33+	return s.Ctx.Deadline()
 34+}
 35+
 36+// Done implements context.Context.
 37+func (s *SSHServerConn) Done() <-chan struct{} {
 38+	s.mu.Lock()
 39+	defer s.mu.Unlock()
 40+
 41+	return s.Ctx.Done()
 42+}
 43+
 44+// Err implements context.Context.
 45+func (s *SSHServerConn) Err() error {
 46+	s.mu.Lock()
 47+	defer s.mu.Unlock()
 48+
 49+	return s.Ctx.Err()
 50+}
 51+
 52+// Value implements context.Context.
 53+func (s *SSHServerConn) Value(key any) any {
 54+	s.mu.Lock()
 55+	defer s.mu.Unlock()
 56+
 57+	return s.Ctx.Value(key)
 58+}
 59+
 60+// SetValue implements context.Context.
 61+func (s *SSHServerConn) SetValue(key any, data any) {
 62+	s.mu.Lock()
 63+	defer s.mu.Unlock()
 64+
 65+	s.Ctx = context.WithValue(s.Ctx, key, data)
 66+}
 67+
 68+var _ context.Context = &SSHServerConn{}
 69+
 70 func (sc *SSHServerConn) Handle(chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) error {
 71 	defer sc.Close()
 72 
 73 	for {
 74 		select {
 75-		case <-sc.Ctx.Done():
 76+		case <-sc.Done():
 77 			return nil
 78 		case newChan := <-chans:
 79 			sc.Logger.Info("new channel", "type", newChan.ChannelType(), "extraData", newChan.ExtraData())
 80+			switch newChan.ChannelType() {
 81+			case "session":
 82+				channel, requests, err := newChan.Accept()
 83+				if err != nil {
 84+					sc.Logger.Error("accept session channel", "err", err)
 85+					return err
 86+				}
 87+
 88+				go func() {
 89+					for {
 90+						select {
 91+						case <-sc.Done():
 92+							return
 93+						case req := <-requests:
 94+							if req == nil {
 95+								continue
 96+							}
 97+							sc.Logger.Info("new session request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
 98+						}
 99+					}
100+				}()
101+
102+				h := func(*SSHServerConnSession) error { return nil }
103+				for _, m := range sc.SSHServer.Config.Middleware {
104+					h = m(h)
105+				}
106+
107+				return h(&SSHServerConnSession{
108+					Channel:       channel,
109+					SSHServerConn: sc,
110+				})
111+			}
112 		case req := <-reqs:
113 			sc.Logger.Info("new request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
114 		}
115@@ -63,13 +146,12 @@ func NewSSHServerConn(
116 	}
117 }
118 
119-type SSHServerMiddleware func(func(ssh.Session) error) func(ssh.Session) error
120+type SSHServerMiddleware func(func(*SSHServerConnSession) error) func(*SSHServerConnSession) error
121 
122 type SSHServerConfig struct {
123 	*ssh.ServerConfig
124-	ListenAddr          string
125-	SessionMiddleware   []SSHServerMiddleware
126-	SubsystemMiddleware []SSHServerMiddleware
127+	ListenAddr string
128+	Middleware []SSHServerMiddleware
129 }
130 
131 type SSHServer struct {