Skip to content

Commit 1f05ff5

Browse files
committed
Update: callback for repositories
- adds a callback for ACL (to return the list of repositories for the current user) - adds all public key data to extensions - exposes the extensions via environment variables (GITKIT_) to the hook
1 parent f89a708 commit 1f05ff5

File tree

5 files changed

+147
-19
lines changed

5 files changed

+147
-19
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Paste the following:
208208
```
209209
Host localhost
210210
Port 2222
211+
ForwardAgent no
211212
```
212213

213214
Now that the server is configured, we can fire it up:

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ go 1.19
55
require (
66
github.com/gofrs/uuid v4.0.0+incompatible
77
github.com/stretchr/testify v1.7.0
8-
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
8+
golang.org/x/crypto v0.9.0
99
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9
1010
)
1111

1212
require (
1313
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/kr/text v0.2.0 // indirect
1415
github.com/pmezard/go-difflib v1.0.0 // indirect
15-
golang.org/x/sys v0.1.0 // indirect
16-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
16+
golang.org/x/sys v0.8.0 // indirect
17+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
18+
gopkg.in/yaml.v3 v3.0.1 // indirect
1719
)

go.sum

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
12
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
56
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
7+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
8+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
9+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
10+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
11+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
12+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
613
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
714
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
815
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
916
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
1017
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
11-
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
12-
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
18+
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
19+
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
1320
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 h1:frX3nT9RkKybPnjyI+yvZh6ZucTZatCCEm9D47sZ2zo=
1421
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
15-
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
16-
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
17-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
18-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
22+
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
23+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
1925
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
20-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
26+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
27+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
2128
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
30+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

ssh.go

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ type PublicKey struct {
3030
type SSH struct {
3131
listener net.Listener
3232

33-
sshconfig *ssh.ServerConfig
34-
config *Config
35-
PublicKeyLookupFunc func(string) (*PublicKey, error)
33+
sshconfig *ssh.ServerConfig
34+
config *Config
35+
PublicKeyLookupFunc func(string) (*PublicKey, error)
36+
ReposForKeyLookupFunc func(*PublicKey) ([]string, error)
3637
}
3738

3839
func NewSSH(config Config) *SSH {
@@ -75,7 +76,7 @@ func execCommand(cmdname string, args ...string) (string, string, error) {
7576
return string(bufOut), string(bufErr), err
7677
}
7778

78-
func (s *SSH) handleConnection(keyID string, chans <-chan ssh.NewChannel) {
79+
func (s *SSH) handleConnection(exts map[string]string, chans <-chan ssh.NewChannel) {
7980
for newChan := range chans {
8081
if newChan.ChannelType() != "session" {
8182
newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
@@ -142,7 +143,17 @@ func (s *SSH) handleConnection(keyID string, chans <-chan ssh.NewChannel) {
142143

143144
cmd := exec.Command(gitcmd.Command, gitcmd.Repo)
144145
cmd.Dir = s.config.Dir
145-
cmd.Env = append(os.Environ(), "GITKIT_KEY="+keyID)
146+
147+
envVariables := os.Environ()
148+
envVariables = append(envVariables, "GITKIT_CURRENT_REPOSITORY="+gitcmd.Repo)
149+
150+
// append data via ssh.Permissions.Extensions
151+
for k, v := range exts {
152+
log.Println("k=" + k + ", v=" + v)
153+
envVariables = append(envVariables, "GITKIT_"+strings.ToUpper(k)+"="+v)
154+
}
155+
cmd.Env = envVariables
156+
146157
// cmd.Env = append(os.Environ(), "SSH_ORIGINAL_COMMAND="+cmdName)
147158

148159
stdout, err := cmd.StdoutPipe()
@@ -209,6 +220,10 @@ func (s *SSH) setup() error {
209220
return fmt.Errorf("public key lookup func is not provided")
210221
}
211222

223+
if s.ReposForKeyLookupFunc == nil {
224+
log.Println("no repository callback, an authorized user may access any repositories")
225+
}
226+
212227
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
213228
pkey, err := s.PublicKeyLookupFunc(strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))))
214229
if err != nil {
@@ -219,7 +234,23 @@ func (s *SSH) setup() error {
219234
return nil, fmt.Errorf("auth handler did not return a key")
220235
}
221236

222-
return &ssh.Permissions{Extensions: map[string]string{"key-id": pkey.Id}}, nil
237+
var repos []string
238+
239+
if s.ReposForKeyLookupFunc != nil {
240+
repos, err = s.ReposForKeyLookupFunc(pkey)
241+
if err != nil {
242+
return nil, err
243+
}
244+
}
245+
246+
return &ssh.Permissions{
247+
Extensions: map[string]string{
248+
"key": pkey.Id,
249+
"fingerprint": pkey.Fingerprint,
250+
"name": pkey.Name,
251+
"repositories": strings.Join(repos, ","),
252+
},
253+
}, nil
223254
}
224255
}
225256

@@ -296,13 +327,13 @@ func (s *SSH) Serve() error {
296327
return
297328
}
298329

299-
keyId := ""
330+
var exts map[string]string
300331
if sConn.Permissions != nil {
301-
keyId = sConn.Permissions.Extensions["key-id"]
332+
exts = sConn.Permissions.Extensions
302333
}
303334

304335
go ssh.DiscardRequests(reqs)
305-
go s.handleConnection(keyId, chans)
336+
go s.handleConnection(exts, chans)
306337
}()
307338
}
308339
}

ssh_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package gitkit_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/sosedoff/gitkit"
11+
"github.com/stretchr/testify/assert"
12+
"golang.org/x/crypto/ssh"
13+
)
14+
15+
func TestKeyLookupFunctionIsNeeded(t *testing.T) {
16+
s := newSSH(t, t.TempDir())
17+
18+
err := s.Listen(":0") // random port
19+
assert.Equal(t, errors.New("public key lookup func is not provided"), err)
20+
}
21+
22+
func TestListener(t *testing.T) {
23+
testDir := t.TempDir()
24+
25+
s := newSSH(t, testDir)
26+
s.PublicKeyLookupFunc = func(content string) (*gitkit.PublicKey, error) {
27+
return &gitkit.PublicKey{Id: "1234"}, nil
28+
}
29+
30+
sshdConfig, err := setup(t, testDir)
31+
assert.NoError(t, err)
32+
33+
s.SetSSHConfig(sshdConfig)
34+
35+
listener, err := net.Listen("tcp4", ":0")
36+
assert.NoError(t, err)
37+
38+
s.SetListener(listener)
39+
t.Logf("address: %s", listener.Addr())
40+
41+
go func() {
42+
defer s.Stop()
43+
err := s.Serve()
44+
assert.NoError(t, err)
45+
}()
46+
47+
// assert the keys are created
48+
assert.FileExists(t, filepath.Join(testDir, "keys/gitkit.rsa"))
49+
assert.FileExists(t, filepath.Join(testDir, "keys/gitkit.rsa.pub"))
50+
}
51+
52+
func newSSH(t *testing.T, baseDir string) *gitkit.SSH {
53+
t.Helper()
54+
55+
return gitkit.NewSSH(gitkit.Config{
56+
Auth: true,
57+
AutoCreate: true,
58+
KeyDir: filepath.Join(baseDir, "keys"),
59+
Dir: filepath.Join(baseDir, "repos"),
60+
})
61+
}
62+
63+
// custom setup function to replicate what gitkit does to setup the ssh server,
64+
// but doesn't do when you supply a custom listener
65+
func setup(t *testing.T, dir string) (*ssh.ServerConfig, error) {
66+
t.Helper()
67+
68+
config := &ssh.ServerConfig{
69+
ServerVersion: fmt.Sprintf("SSH-2.0-gitkit %s", "testing"),
70+
}
71+
72+
config.NoClientAuth = true
73+
74+
// create server key
75+
k := gitkit.NewKey(filepath.Join(dir, "keys"))
76+
77+
err := k.CreateRSA()
78+
assert.NoError(t, err)
79+
80+
private, err := k.GetRSA()
81+
assert.NoError(t, err)
82+
83+
config.AddHostKey(private)
84+
return config, nil
85+
}

0 commit comments

Comments
 (0)