- 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
+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
+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,
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 {
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 {