Skip to content

Commit 1871137

Browse files
committed
ssh working, user auth working, addr whitelisting (mostly) working
1 parent 68eb79a commit 1871137

File tree

8 files changed

+154
-258
lines changed

8 files changed

+154
-258
lines changed

README.md

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Chisel is an HTTP client and server which acts as a TCP proxy, written in Go (Go
88

99
**Binaries**
1010

11-
See [Releases](https://github.com/jpillora/chisel/releases)
11+
See [Releases](https://github.com/jpillora/chisel/releases/latest)
1212

1313
**Source**
1414

@@ -20,7 +20,8 @@ $ go get -v github.com/jpillora/chisel
2020

2121
* Easy to use
2222
* [Performant](#performance)*
23-
* [Encrypted connections](https://github.com/jpillora/conncrypt) with `key` derived (PBKDF2) symmetric key[*](#security)
23+
* [Encrypted connections](#security) using `crypto/ssh`
24+
* [Authenticated connections](#authentication) using a users config file
2425
* Client auto-reconnects with [exponential backoff](https://github.com/jpillora/backoff)
2526
* Client can create multiple tunnel endpoints over one TCP connection
2627
* Server optionally doubles as a [reverse proxy](http://golang.org/pkg/net/http/httputil/#NewSingleHostReverseProxy)
@@ -30,22 +31,19 @@ $ go get -v github.com/jpillora/chisel
3031
A [demo app](https://chisel-demo.herokuapp.com) on Heroku is running this `chisel server`:
3132

3233
``` sh
33-
$ chisel server --key foobar --port $PORT --proxy http://example.com
34-
# listens on $PORT, requires password 'foobar', proxy web requests to 'http://example.com'
34+
$ chisel server --port $PORT --proxy http://example.com
35+
# listens on $PORT, proxy web requests to 'http://example.com'
3536
```
3637

3738
This demo app is also running a [simple file server](https://www.npmjs.com/package/serve) on `:3000`, which is normally inaccessible due to Heroku's firewall. However, if we tunnel in with:
3839

3940
``` sh
40-
$ chisel client --key foobar https://chisel-demo.herokuapp.com 3000
41-
# connects to 'https://chisel-demo.herokuapp.com', using password 'foobar',
41+
$ chisel client https://chisel-demo.herokuapp.com 3000
42+
# connects to 'https://chisel-demo.herokuapp.com',
4243
# tunnels your localhost:3000 to the server's localhost:3000
4344
```
4445

45-
and then visit [localhost:3000](http://localhost:3000/), we should
46-
see a directory listing of the demo app's root. Also, if we visit
47-
the [demo app](https://chisel-demo.herokuapp.com) in the browser we should hit the server's
48-
default proxy and see a copy of [example.com](http://example.com).
46+
and then visit [localhost:3000](http://localhost:3000/), we should see a directory listing of the demo app's root. Also, if we visit the [demo app](https://chisel-demo.herokuapp.com) in the browser we should hit the server's default proxy and see a copy of [example.com](http://example.com).
4947

5048
### Usage
5149

@@ -54,7 +52,7 @@ default proxy and see a copy of [example.com](http://example.com).
5452
5553
Usage: chisel [command] [--help]
5654
57-
Version: X.X.X
55+
Version: 0.0.0-src
5856
5957
Commands:
6058
server - runs chisel in server mode
@@ -66,6 +64,8 @@ default proxy and see a copy of [example.com](http://example.com).
6664
```
6765
</tmpl>
6866

67+
`chisel server --help`
68+
6969
<tmpl,code: chisel server --help>
7070
```
7171
@@ -78,13 +78,22 @@ default proxy and see a copy of [example.com](http://example.com).
7878
7979
--port, Defines the HTTP listening port (defaults to 8080).
8080
81+
--key, An optional string to seed the generation of a ECC public
82+
and private key pair. All commications will be secured using this
83+
key pair. Share the resulting fingerprint with clients to prevent
84+
man-in-the-middle attacks.
85+
86+
--authfile, An optional path to a users.json file. This file should
87+
be an object with users defined like:
88+
"<user:pass>": ["<addr-regex>","<addr-regex>"]
89+
when <user> connects, their <pass> will be verified and then
90+
each of the remote addresses will be compared against the list
91+
of address regular expressions for a match. Addresses will
92+
always come in the form "<host/ip>:<port>".
93+
8194
--proxy, Specifies the default proxy target to use when chisel
8295
receives a normal HTTP request.
8396
84-
--key, Enables AES256 encryption and specify the string to
85-
use to derive the key (derivation is performed using PBKDF2
86-
with 2048 iterations of SHA256).
87-
8897
-v, Enable verbose logging
8998
9099
--help, This help text
@@ -95,6 +104,8 @@ default proxy and see a copy of [example.com](http://example.com).
95104
```
96105
</tmpl>
97106

107+
`chisel client --help`
108+
98109
<tmpl,code: chisel client --help>
99110
```
100111
@@ -121,9 +132,14 @@ default proxy and see a copy of [example.com](http://example.com).
121132
122133
Options:
123134
124-
--key, Enables AES256 encryption and specify the string to
125-
use to derive the key (derivation is performed using PBKDF2
126-
with 2048 iterations of SHA256).
135+
--fingerprint, An optional fingerprint (server authentication)
136+
string to compare against the server's public key. You may provide
137+
just a prefix of the key or the entire string. Fingerprint
138+
mismatches will close the connection.
139+
140+
--auth, An optional username and password (client authentication)
141+
in the form: "<user>:<pass>". These credentials are compared to
142+
the credentials inside the server's --authfile.
127143
128144
-v, Enable verbose logging
129145
@@ -139,13 +155,17 @@ See also [programmatic usage](https://github.com/jpillora/chisel/wiki/Programmat
139155

140156
### Security
141157

142-
**Beware** The `key` option derives the keys and initialization vectors and is currently susceptible to a sustained targeted attack, this risk will be lessened when the switch SSH is complete.
158+
Encryption is enabled by default, when you start up a chisel server, it will generate an in-memory ECC public/private key pair. The public key fingerprint will be displayed as the server starts. Instead of always generating a random key, the server may optionally specify a key seed, using the `--key`, which will be used to seed the key generation. When clients connect, they will also display the server's public key fingerprint. The client can force a particular fingerprint using the `--fingerprint` option. See the `--help` above for more information.
159+
160+
### Authentication
161+
162+
Using the `--authfile` option, the server may optionally provide a `user.json` configuration file to create a list of accepted users. The client then authenticates using the `--auth` option. See [users.json](example/users.json) for an example authentication configuration file. See the `--help` above for more information.
143163

144-
It's recommended to use TLS to secure your traffic, which can only be done by hosting your chisel server behind a TLS terminating proxy (like Heroku's router). In the future, the server will allow your to pass in TLS credentials and make use of Go's TLS (HTTPS) server.
164+
Internally, this is done using the *Password* authentication method provided by SSH. Learn more about `crypto/ssh` here http://blog.gopheracademy.com/go-and-ssh/.
145165

146166
### Performance
147167

148-
With [crowbar](https://github.com/q3k/crowbar), a connection is tunnelled by repeatedly querying the server with updates. This results in a large amount of HTTP and TCP connection overhead. Chisel overcomes this using WebSockets combined with [Yamux](https://github.com/hashicorp/yamux) to create hundreds of SDPY/HTTP2 like logical connections, resulting in **one** TCP connection per client.
168+
With [crowbar](https://github.com/q3k/crowbar), a connection is tunnelled by repeatedly querying the server with updates. This results in a large amount of HTTP and TCP connection overhead. Chisel overcomes this using WebSockets combined with [crypto/ssh](https://golang.org/x/crypto/ssh) to create hundreds of logical connections, resulting in **one** TCP connection per client.
149169

150170
In this simple benchmark, we have:
151171

@@ -178,18 +198,18 @@ Note, we're using an in-memory "file" server on localhost for these tests
178198
`chisel`
179199

180200
```
181-
:2001 => 1 bytes in 1.334661ms
182-
:2001 => 10 bytes in 807.797µs
183-
:2001 => 100 bytes in 763.728µs
184-
:2001 => 1000 bytes in 1.029811ms
185-
:2001 => 10000 bytes in 840.247µs
186-
:2001 => 100000 bytes in 1.647748ms
187-
:2001 => 1000000 bytes in 3.495904ms
188-
:2001 => 10000000 bytes in 22.298904ms
189-
:2001 => 100000000 bytes in 255.410448ms
201+
:2001 => 1 bytes in 1.190288ms
202+
:2001 => 10 bytes in 1.17237ms
203+
:2001 => 100 bytes in 821.369µs
204+
:2001 => 1000 bytes in 1.029366ms
205+
:2001 => 10000 bytes in 1.281065ms
206+
:2001 => 100000 bytes in 2.14094ms
207+
:2001 => 1000000 bytes in 9.538984ms
208+
:2001 => 10000000 bytes in 86.500426ms
209+
:2001 => 100000000 bytes in 814.630443ms
190210
```
191211

192-
~100MB in **a quarter of a second**
212+
~100MB in **0.8 seconds**
193213

194214
`crowbar`
195215

@@ -227,13 +247,16 @@ See more [test/](test/)
227247
* `github.com/jpillora/chisel/server` contains the server package
228248
* `github.com/jpillora/chisel/client` contains the client package
229249

250+
### Changelog
251+
252+
* `1.0.0` - Init
253+
* `1.1.0` - Swapped out simple symmetric encryption for ECC SSH
254+
230255
### Todo
231256

232-
* Users file with white-listed remotes
233-
* Pass in TLS server configuration
257+
* Better, faster tests
234258
* Expose a stats page for proxy throughput
235-
* Configurable connection retry times
236-
* Treat forwarder stdin/stdout as a socket
259+
* Treat client stdin/stdout as a socket
237260

238261
#### MIT License
239262

client/client.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@ type Client struct {
2323
sshConn ssh.Conn
2424
fingerprint string
2525
server string
26-
keyPrefix string
2726
running bool
2827
runningc chan error
2928
}
3029

31-
func NewClient(keyPrefix, auth, server string, remotes ...string) (*Client, error) {
30+
func NewClient(fingerprint, auth, server string, remotes ...string) (*Client, error) {
3231

3332
//apply default scheme
3433
if !strings.HasPrefix(server, "http") {
@@ -62,25 +61,23 @@ func NewClient(keyPrefix, auth, server string, remotes ...string) (*Client, erro
6261
}
6362

6463
c := &Client{
65-
Logger: chshare.NewLogger("client"),
66-
config: config,
67-
server: u.String(),
68-
keyPrefix: keyPrefix,
69-
running: true,
70-
runningc: make(chan error, 1),
64+
Logger: chshare.NewLogger("client"),
65+
config: config,
66+
server: u.String(),
67+
fingerprint: fingerprint,
68+
running: true,
69+
runningc: make(chan error, 1),
7170
}
7271

72+
user, pass := chshare.ParseAuth(auth)
73+
7374
c.sshConfig = &ssh.ClientConfig{
75+
User: user,
76+
Auth: []ssh.AuthMethod{ssh.Password(pass)},
7477
ClientVersion: chshare.ProtocolVersion + "-client",
7578
HostKeyCallback: c.verifyServer,
7679
}
7780

78-
user, pass := chshare.ParseAuth(auth)
79-
if user != "" {
80-
c.sshConfig.User = user
81-
c.sshConfig.Auth = []ssh.AuthMethod{ssh.Password(pass)}
82-
}
83-
8481
return c, nil
8582
}
8683

@@ -92,10 +89,11 @@ func (c *Client) Run() error {
9289

9390
func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKey) error {
9491
f := chshare.FingerprintKey(key)
95-
if c.keyPrefix != "" && !strings.HasPrefix(f, c.keyPrefix) {
92+
if c.fingerprint != "" && !strings.HasPrefix(f, c.fingerprint) {
9693
return fmt.Errorf("Invalid fingerprint (Got %s)", f)
9794
}
98-
c.fingerprint = f
95+
//overwrite with complete fingerprint
96+
c.Infof("Fingerprint %s", f)
9997
return nil
10098
}
10199

@@ -140,29 +138,31 @@ func (c *Client) start() {
140138
if err != nil {
141139
if strings.Contains(err.Error(), "unable to authenticate") {
142140
c.Infof("Authentication failed")
141+
c.Debugf(err.Error())
143142
} else {
144143
c.Infof(err.Error())
145144
}
146145
break
147146
}
148147
conf, _ := chshare.EncodeConfig(c.config)
148+
c.Debugf("Sending configurating")
149149
_, conerr, err := sshConn.SendRequest("config", true, conf)
150150
if err != nil {
151-
c.Infof("Config verification failed", c.fingerprint)
151+
c.Infof("Config verification failed")
152152
break
153153
}
154154
if len(conerr) > 0 {
155155
c.Infof(string(conerr))
156156
break
157157
}
158158

159-
c.Infof("Connected (%s)", c.fingerprint)
159+
c.Infof("Connected")
160160
//connected
161161
b.Reset()
162162

163163
c.sshConn = sshConn
164164
go ssh.DiscardRequests(reqs)
165-
go chshare.RejectStreams(chans)
165+
go chshare.RejectStreams(chans) //TODO allow client to ConnectStreams
166166
err = sshConn.Wait()
167167
//disconnected
168168
c.sshConn = nil

example/users.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
2+
"root:toor": [
3+
""
4+
],
25
"foo:bar": [
3-
"0.0.0.0:3000"
6+
"^0.0.0.0:3000$"
47
],
58
"ping:pong": [
6-
"0.0.0.0:[45]000",
7-
"example.com:80"
9+
"^0.0.0.0:[45]000$",
10+
"^example.com:80$"
811
]
912
}

main.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,21 @@ var clientHelp = `
163163
164164
Options:
165165
166-
--key, An optional fingerprint (server authentication) string to
167-
compare against the server's public key. You may provide just a
168-
prefix of the key or the entire string. Fingerprint mismatches
169-
will disallow the connection.
166+
--fingerprint, An optional fingerprint (server authentication)
167+
string to compare against the server's public key. You may provide
168+
just a prefix of the key or the entire string. Fingerprint
169+
mismatches will close the connection.
170170
171171
--auth, An optional username and password (client authentication)
172-
in the form: "<user>:<pass>".
172+
in the form: "<user>:<pass>". These credentials are compared to
173+
the credentials inside the server's --authfile.
173174
` + commonHelp
174175

175176
func client(args []string) {
176177

177178
flags := flag.NewFlagSet("client", flag.ContinueOnError)
178179

179-
key := flags.String("key", "", "")
180+
fingerprint := flags.String("fingerprint", "", "")
180181
auth := flags.String("auth", "", "")
181182
verbose := flags.Bool("v", false, "")
182183
flags.Usage = func() {
@@ -193,7 +194,7 @@ func client(args []string) {
193194
server := args[0]
194195
remotes := args[1:]
195196

196-
c, err := chclient.NewClient(*key, *auth, server, remotes...)
197+
c, err := chclient.NewClient(*fingerprint, *auth, server, remotes...)
197198
if err != nil {
198199
log.Fatal(err)
199200
}

0 commit comments

Comments
 (0)