Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

refactor

c5902451905acdaa478f8476844ccd4a510d9ca9 · Keenan Nemetz · 16d ago · parent ec2aba4b

internal/sshserver/sshserver.go → server/ssh/server.go +24−25

@@ -1,11 +1,11 @@
1 // Package sshserver is Vole's write transport. Identity comes from the SSH key
1 // Package ssh is Vole's write transport. Identity comes from the SSH key
2 2 // presented at handshake; the only thing a session may do is run git's own
3 3 // upload-pack or receive-pack against a repository that exists in the store.
4 4 //
5 5 // The exec surface is a remote shell until it is made not one, so it is
6 6 // whitelisted by exact command name and validated path, and everything else is
7 7 // refused. A test asserts that `sh -c` cannot get through.
8 package sshserver
8 package ssh
9 9
10 10 import (
11 11 "crypto/ed25519"
@@ -24,13 +24,12 @@ import (
24 24 "sync"
25 25 "time"
26 26
27 "golang.org/x/crypto/ssh"
27 xssh "golang.org/x/crypto/ssh"
28 28
29 "github.com/rodent-software/vole/internal/identity"
30 "github.com/rodent-software/vole/internal/lfs"
31 "github.com/rodent-software/vole/internal/locks"
32 "github.com/rodent-software/vole/internal/repo"
33 "github.com/rodent-software/vole/internal/runs"
29 "github.com/rodent-software/vole/ci/runs"
30 "github.com/rodent-software/vole/core/lfs"
31 "github.com/rodent-software/vole/forge/identity"
32 "github.com/rodent-software/vole/forge/repo"
34 33 )
35 34
36 35 const serverVersion = "SSH-2.0-vole"
@@ -55,7 +54,7 @@ type Config struct {
55 54 LFSWarnBytes int64
56 55 // Locks is the Git LFS lock table, keyed by account id. Nil means this server
57 56 // offers no locking, and `git-lfs-transfer` does not advertise the capability.
58 Locks *locks.Store
57 Locks *lfs.LockStore
59 58 // Lease is how long a runner may hold a job without a heartbeat.
60 59 Lease time.Duration
61 60 // MaxJobsPerRepo bounds how many jobs one repository may have running at once.
@@ -71,7 +70,7 @@ type Config struct {
71 70
72 71 type Server struct {
73 72 cfg Config
74 sshCfg *ssh.ServerConfig
73 sshCfg *xssh.ServerConfig
75 74 mu sync.Mutex
76 75 ln net.Listener
77 76 }
@@ -86,17 +85,17 @@ func New(cfg Config) (*Server, error) {
86 85 return nil, err
87 86 }
88 87 s := &Server{cfg: cfg}
89 s.sshCfg = &ssh.ServerConfig{
88 s.sshCfg = &xssh.ServerConfig{
90 89 ServerVersion: serverVersion,
91 90 MaxAuthTries: 6,
92 PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
93 fp := ssh.FingerprintSHA256(key)
91 PublicKeyCallback: func(c xssh.ConnMetadata, key xssh.PublicKey) (*xssh.Permissions, error) {
92 fp := xssh.FingerprintSHA256(key)
94 93 accountID, name, ok := cfg.Identity.Lookup(fp)
95 94 if !ok {
96 95 cfg.Log.Warn("ssh: unknown key", "fingerprint", fp, "remote", c.RemoteAddr().String())
97 96 return nil, fmt.Errorf("key not registered")
98 97 }
99 return &ssh.Permissions{Extensions: map[string]string{
98 return &xssh.Permissions{Extensions: map[string]string{
100 99 "user": name,
101 100 "account": accountID,
102 101 "fingerprint": fp,
@@ -142,13 +141,13 @@ func (s *Server) Close() error {
142 141 }
143 142
144 143 func (s *Server) handleConn(c net.Conn) {
145 sconn, chans, reqs, err := ssh.NewServerConn(c, s.sshCfg)
144 sconn, chans, reqs, err := xssh.NewServerConn(c, s.sshCfg)
146 145 if err != nil {
147 146 c.Close()
148 147 return
149 148 }
150 149 defer sconn.Close()
151 go ssh.DiscardRequests(reqs)
150 go xssh.DiscardRequests(reqs)
152 151
153 152 user := sconn.Permissions.Extensions["user"]
154 153 // The account id is the authorization identity. The name is only ever used
@@ -159,7 +158,7 @@ func (s *Server) handleConn(c net.Conn) {
159 158
160 159 for newCh := range chans {
161 160 if newCh.ChannelType() != "session" {
162 newCh.Reject(ssh.UnknownChannelType, "only session channels are supported")
161 newCh.Reject(xssh.UnknownChannelType, "only session channels are supported")
163 162 continue
164 163 }
165 164 ch, chReqs, err := newCh.Accept()
@@ -170,13 +169,13 @@ func (s *Server) handleConn(c net.Conn) {
170 169 }
171 170 }
172 171
173 func (s *Server) handleSession(ch ssh.Channel, reqs <-chan *ssh.Request, user, accountID, fp string) {
172 func (s *Server) handleSession(ch xssh.Channel, reqs <-chan *xssh.Request, user, accountID, fp string) {
174 173 defer ch.Close()
175 174 for req := range reqs {
176 175 switch req.Type {
177 176 case "exec":
178 177 var payload struct{ Command string }
179 ssh.Unmarshal(req.Payload, &payload)
178 xssh.Unmarshal(req.Payload, &payload)
180 179 s.exec(ch, req, payload.Command, user, accountID, fp)
181 180 return
182 181 case "shell", "pty-req", "subsystem":
@@ -193,7 +192,7 @@ func (s *Server) handleSession(ch ssh.Channel, reqs <-chan *ssh.Request, user, a
193 192 }
194 193 }
195 194
196 func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, accountID, fp string) {
195 func (s *Server) exec(ch xssh.Channel, req *xssh.Request, command, user, accountID, fp string) {
197 196 // A refusal is still answered on the channel rather than at the request
198 197 // layer: replying false makes the client print "exec request failed on
199 198 // channel 0" and the reason is lost. Nothing is ever executed on these
@@ -382,11 +381,11 @@ func splitCommand(s string) ([]string, error) {
382 381 return out, nil
383 382 }
384 383
385 func exit(ch ssh.Channel, code int) {
386 ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{uint32(code)}))
384 func exit(ch xssh.Channel, code int) {
385 ch.SendRequest("exit-status", false, xssh.Marshal(struct{ Status uint32 }{uint32(code)}))
387 386 }
388 387
389 func loadOrCreateHostKey(path string) (ssh.Signer, error) {
388 func loadOrCreateHostKey(path string) (xssh.Signer, error) {
390 389 if data, err := os.ReadFile(path); err == nil {
391 390 blk, _ := pem.Decode(data)
392 391 if blk == nil {
@@ -396,7 +395,7 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) {
396 395 if err != nil {
397 396 return nil, fmt.Errorf("%s: %w", path, err)
398 397 }
399 return ssh.NewSignerFromKey(key)
398 return xssh.NewSignerFromKey(key)
400 399 } else if !os.IsNotExist(err) {
401 400 return nil, err
402 401 }
@@ -416,5 +415,5 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) {
416 415 if err := os.WriteFile(path, blk, 0o600); err != nil {
417 416 return nil, err
418 417 }
419 return ssh.NewSignerFromKey(priv)
418 return xssh.NewSignerFromKey(priv)
420 419 }