Skip to content

Commit 870190f

Browse files
authored
Merge pull request gliderlabs#15 from stokito/master
Use go modules
2 parents cdbabc9 + 0512720 commit 870190f

File tree

12 files changed

+143
-80
lines changed

12 files changed

+143
-80
lines changed

Diff for: Makefile

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ ARCH=$(shell uname -m)
44
RMFLAG=--rm
55
VERSION=0.2.1
66

7-
build:
7+
build/Linux/sshfront:
88
mkdir -p build/Linux && GOOS=linux CGO_ENABLED=0 go build -a \
99
-ldflags "-X main.Version=$(VERSION)" \
1010
-installsuffix cgo \
11-
-o build/Linux/$(NAME)
11+
-o build/Linux/$(NAME) ./cmd/sshfront
12+
13+
build/Darwin/sshfront:
1214
mkdir -p build/Darwin && GOOS=darwin CGO_ENABLED=0 go build -a \
1315
-ldflags "-X main.Version=$(VERSION)" \
1416
-installsuffix cgo \
15-
-o build/Darwin/$(NAME)
17+
-o build/Darwin/$(NAME) ./cmd/sshfront
18+
19+
build: build/Linux/sshfront build/Darwin/sshfront
1620

1721
deps:
1822
go get -u github.com/progrium/gh-release/...

Diff for: README.md

+45-32
Original file line numberDiff line numberDiff line change
@@ -7,67 +7,80 @@ A lightweight SSH server frontend where authentication and connections
77
are controlled with command handlers / shell scripts.
88

99
## Using sshfront
10-
```
11-
Usage: ./sshfront [options] <handler>
1210

13-
-a="": authentication hook. empty=allow all
14-
-d=false: debug mode
15-
-e=false: pass environment to handler
16-
-h="0.0.0.0": ip to listen on
17-
-k="~/.ssh/id_rsa": private host key path
18-
-p="22": port to listen on
19-
```
11+
12+
Usage: ./sshfront [options] <handler>
13+
14+
-a="": authentication hook. empty=allow all
15+
-d=false: debug mode
16+
-e=false: pass environment to handler
17+
-h="0.0.0.0": ip to listen on
18+
-k="~/.ssh/id_rsa": private host key path
19+
-p="22": port to listen on
2020

2121

2222
#### handler $command...
2323

24-
* `$command...` command line arguments specified to run by the SSH client
24+
`$command...` command line arguments specified to run by the SSH client
2525

26-
The handler is a command that's used to handle all SSH connections. Output, stderr, and the exit code is returned to the client. If the client provides stdin, that's passed to the handler.
26+
The handler is a command that's used to handle all SSH connections.
27+
Output, stderr, and the exit code is returned to the client.
28+
If the client provides stdin, that's passed to the handler.
2729

28-
If the authentication hook was specified, any output is parsed as environment variables and added to the handler environment. `$USER` is always the SSH user used to connect and `$SSH_ORIGINAL_COMMAND` is the command specified from the client if not interactive.
30+
If the authentication hook was specified, any output is parsed as environment variables and added to the handler environment.
31+
`$USER` is always the SSH user used to connect and `$SSH_ORIGINAL_COMMAND` is the command specified from the client if not interactive.
2932

3033
#### auth-hook $user $key
3134

3235
* `$user` argument is the name of the user being used to attempt the connection
3336
* `$key` argument is the public key data being provided for authentication
3437

35-
The auth hook is a command used for authenticating incoming SSH connections. If it returns with exit status 0, the connection will be allowed, otherwise it will be denied. The output of auth hook must be empty, or key-value pairs in the form `KEY=value` separated by newlines, which will be added to the environment of connection handler.
38+
The auth hook is a command used for authenticating incoming SSH connections.
39+
If it returns with exit status 0, the connection will be allowed, otherwise it will be denied.
40+
The output of auth hook must be empty, or key-value pairs in the form `KEY=value` separated by newlines, which will be added to the environment of connection handler.
3641

3742
The auth hook is optional, but if not specified then all connections are allowed.
3843
It is a good idea to always specify an auth hook.
3944

45+
46+
See example/authcheck auth hook that checks that the pub key is authorized. Usage:
47+
48+
sshfront -a example/authcheck
49+
50+
4051
## Examples
4152

4253
**Many of these bypass authentication and may allow remote execution, *do not* run this in production.**
4354

4455
Echo server:
4556

46-
```
47-
server$ sshfront $(which echo)
48-
client$ ssh $SERVER "hello world"
49-
hello world
50-
```
57+
server$ sshfront $(which echo)
58+
client$ ssh $SERVER "hello world"
59+
hello world
5160

5261
Echo host's environment to clients:
5362

54-
```
55-
server$ sshfront -e $(env)
56-
client$ ssh $SERVER
57-
USER=root
58-
HOME=/root
59-
LANG=en_US.UTF-8
60-
...
61-
```
63+
server$ sshfront -e $(env)
64+
client$ ssh $SERVER
65+
USER=root
66+
HOME=/root
67+
LANG=en_US.UTF-8
68+
...
6269

6370
Bash server:
6471

65-
```
66-
server$ sshfront $(which bash)
67-
client$ ssh $SERVER
68-
bash-4.3$ echo "this is a bash instance running on the server"
69-
this is a bash instance running on the server
70-
```
72+
server$ sshfront $(which bash)
73+
client$ ssh $SERVER
74+
bash-4.3$ echo "this is a bash instance running on the server"
75+
this is a bash instance running on the server
76+
77+
78+
SSH forwarding:
79+
80+
server$ sshfront example/sshforward
81+
client$ ssh alice@$SERVER
82+
Forward ssh [email protected]...
83+
Welcome to another.server
7184

7285

7386
## Sponsors

Diff for: sshfront.go renamed to cmd/sshfront/sshfront.go

+10-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"flag"
55
"fmt"
6+
. "github.com/gliderlabs/sshfront/internal"
67
"log"
78
"net"
89
"os"
@@ -12,36 +13,21 @@ import (
1213

1314
var Version string
1415

15-
var (
16-
listenHost = flag.String("h", "0.0.0.0", "ip to listen on")
17-
listenPort = flag.String("p", "22", "port to listen on")
18-
hostKey = flag.String("k", "~/.ssh/id_rsa", "private host key path")
19-
authHook = flag.String("a", "", "authentication hook. empty=allow all")
20-
debugMode = flag.Bool("d", false, "debug mode")
21-
useEnv = flag.Bool("e", false, "pass environment to handler")
22-
)
23-
24-
func debug(v ...interface{}) {
25-
if *debugMode {
26-
log.Println(v...)
27-
}
28-
}
29-
3016
func handleConn(conn net.Conn, conf *ssh.ServerConfig) {
3117
defer conn.Close()
3218
sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf)
3319
if err != nil {
34-
debug("handshake failed:", err)
20+
Debug("handshake failed:", err)
3521
return
3622
}
3723
go ssh.DiscardRequests(reqs)
3824
for ch := range chans {
3925
if ch.ChannelType() != "session" {
4026
ch.Reject(ssh.UnknownChannelType, "unsupported channel type")
41-
debug("channel rejected, unsupported type:", ch.ChannelType())
27+
Debug("channel rejected, unsupported type:", ch.ChannelType())
4228
continue
4329
}
44-
go handleChannel(sshConn, ch)
30+
go HandleChannel(sshConn, ch)
4531
}
4632
}
4733

@@ -61,14 +47,14 @@ func main() {
6147

6248
config := &ssh.ServerConfig{
6349
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
64-
return handleAuth(conn, key)
50+
return HandleAuth(conn, key)
6551
},
6652
}
67-
setupHostKey(config)
53+
SetupHostKey(config)
6854

69-
var listenAddr string
70-
if listenAddr = os.Getenv("SSHFRONT_LISTEN"); listenAddr == "" {
71-
listenAddr = fmt.Sprintf("%s:%s", *listenHost, *listenPort)
55+
listenAddr := os.Getenv("SSHFRONT_LISTEN")
56+
if listenAddr == "" {
57+
listenAddr = net.JoinHostPort(*ListenHost, *ListenPort)
7258
}
7359

7460
log.Printf("sshfront v%s listening on %s ...\n", Version, listenAddr)
@@ -80,7 +66,7 @@ func main() {
8066
for {
8167
conn, err := listener.Accept()
8268
if err != nil {
83-
debug("accept failed:", err)
69+
Debug("accept failed:", err)
8470
continue
8571
}
8672
go handleConn(conn, config)

Diff for: example/authcheck

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#!/bin/bash
2+
# check that the user's pubkey is authorized
23
grep "$2" "/home/$1/.ssh/authorized_keys" > /dev/null 2>&1

Diff for: example/helloworld

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/bash
2-
echo "Hello world: $@"
2+
echo "Execute command: $@"
3+
echo "Env vars:"
34
env
5+
# if FD = 1 then this is an interactive shell
46
if [[ -t 1 ]]; then
57
exec bash
68
fi

Diff for: example/sshforward

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
echo "Forward ssh ${USER}@localhost..."
3+
ssh "${USER}@localhost"

Diff for: go.mod

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/gliderlabs/sshfront
2+
3+
go 1.21.3
4+
5+
require (
6+
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
7+
github.com/kr/pty v1.1.8
8+
golang.org/x/crypto v0.16.0
9+
)
10+
11+
require (
12+
github.com/creack/pty v1.1.21 // indirect
13+
golang.org/x/sys v0.15.0 // indirect
14+
)

Diff for: go.sum

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
2+
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
3+
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
4+
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
5+
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
6+
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
7+
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
8+
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
9+
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
10+
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
11+
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
12+
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
13+
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
14+
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=

Diff for: internal/config.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package internal
2+
3+
import (
4+
"flag"
5+
"log"
6+
)
7+
8+
var (
9+
ListenHost = flag.String("h", "0.0.0.0", "ip to listen on")
10+
ListenPort = flag.String("p", "22", "port to listen on")
11+
HostKey = flag.String("k", "~/.ssh/id_rsa", "private host key path")
12+
AuthHook = flag.String("a", "", "authentication hook. empty=allow all")
13+
DebugMode = flag.Bool("d", false, "debug mode")
14+
UseEnv = flag.Bool("e", false, "pass environment to handler")
15+
)
16+
17+
func Debug(v ...interface{}) {
18+
if *DebugMode {
19+
log.Println(v...)
20+
}
21+
}

Diff for: handlers.go renamed to internal/handlers.go

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package internal
22

33
import (
44
"bytes"
@@ -57,8 +57,8 @@ func handlerCmd(handler string, appendArgs ...string) (*exec.Cmd, error) {
5757
return exec.Command(execPath, append(args, appendArgs...)...), nil
5858
}
5959

60-
func handleAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
61-
if *authHook == "" {
60+
func HandleAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
61+
if *AuthHook == "" {
6262
// allow all
6363
return &ssh.Permissions{
6464
Extensions: map[string]string{
@@ -67,8 +67,8 @@ func handleAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, err
6767
}, nil
6868
}
6969

70-
keydata := string(bytes.TrimSpace(ssh.MarshalAuthorizedKey(key)))
71-
cmd, err := handlerCmd(*authHook, conn.User(), keydata)
70+
pubKey := string(bytes.TrimSpace(ssh.MarshalAuthorizedKey(key)))
71+
cmd, err := handlerCmd(*AuthHook, conn.User(), pubKey)
7272
if err != nil {
7373
return nil, err
7474
}
@@ -87,11 +87,11 @@ func handleAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, err
8787
},
8888
}, nil
8989
}
90-
debug("authentication hook status:", status.Status)
90+
Debug("authentication hook status:", status.Status)
9191
return nil, fmt.Errorf("authentication failed")
9292
}
9393

94-
func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel) {
94+
func HandleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel) {
9595
ch, reqs, err := newChan.Accept()
9696
if err != nil {
9797
log.Println("newChan.Accept failed:", err)
@@ -100,7 +100,7 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel) {
100100

101101
// Setup stdout/stderr
102102
var stdout, stderr io.Writer
103-
if *debugMode {
103+
if *DebugMode {
104104
stdout = io.MultiWriter(ch, os.Stdout)
105105
stderr = io.MultiWriter(ch.Stderr(), os.Stdout)
106106
} else {
@@ -115,7 +115,7 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel) {
115115
}
116116

117117
// Load default environment
118-
if *useEnv {
118+
if *UseEnv {
119119
handler.Env = os.Environ()
120120
}
121121
if conn.Permissions != nil {
@@ -192,6 +192,8 @@ func (h *sshHandler) handleEnv(req *ssh.Request) {
192192
req.Reply(true, nil)
193193
}
194194

195+
// handleExec when executed a command e.g.
196+
// ssh user@localhost -p 2222 'echo Hello'
195197
func (h *sshHandler) handleExec(req *ssh.Request) {
196198
h.Lock()
197199
defer h.Unlock()
@@ -200,14 +202,14 @@ func (h *sshHandler) handleExec(req *ssh.Request) {
200202
ssh.Unmarshal(req.Payload, &payload)
201203
cmdargs, err := shlex.Split(payload.Value)
202204
if err != nil {
203-
debug("failed exec split:", err)
205+
Debug("failed exec split:", err)
204206
h.channel.Close()
205207
return
206208
}
207209

208210
cmd, err := handlerCmd(flag.Arg(0), cmdargs...)
209211
if err != nil {
210-
debug("failed handler init:", err)
212+
Debug("failed handler init:", err)
211213
h.channel.Close()
212214
return
213215
}
@@ -234,6 +236,8 @@ func (h *sshHandler) handleExec(req *ssh.Request) {
234236
h.Exit(cmd.Run())
235237
}
236238

239+
// handlePty when executed a command e.g.
240+
// ssh user@localhost -p 2222
237241
func (h *sshHandler) handlePty(req *ssh.Request) {
238242
h.Lock()
239243
defer h.Unlock()
@@ -246,9 +250,10 @@ func (h *sshHandler) handlePty(req *ssh.Request) {
246250

247251
width, height, okSize := parsePtyRequest(req.Payload)
248252

249-
cmd, err := handlerCmd(flag.Arg(0))
253+
scriptName := flag.Arg(0)
254+
cmd, err := handlerCmd(scriptName)
250255
if err != nil {
251-
debug("failed handler init:", err)
256+
Debug("failed handler init:", err)
252257
h.channel.Close()
253258
return
254259
}

0 commit comments

Comments
 (0)