Skip to content

Commit

Permalink
Major feature update:
Browse files Browse the repository at this point in the history
* Added socks server capability
* Fully tested Quic client/server
* E2E test cases
* Updated diagram for using the socks feature
* updated readme
  • Loading branch information
opencoff committed Jul 4, 2020
1 parent 3e92834 commit bfe2f28
Show file tree
Hide file tree
Showing 16 changed files with 976 additions and 204 deletions.
168 changes: 116 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# go-tunnel - Robust Quic/TLS Tunnel (Stunnel replacement)

## What is it?
A supercharged [Stunnel](https://www.stunnel.org) replacement written in golang. It is
A supercharged [Stunnel](https://www.stunnel.org) replacement written in golang.
is in a sense a proxy enabling addition of network-encryption to existing
clients without any source code changes.

go-tunnel uses golang's TLS stack and built-in certification verification.

## Features

- TLS 1.3 for client and server mode (TLS Connect or TLS Listen)
- Quic client and server mode (Quic listen or Quic connect)
- Optional SOCKS for connecting endpoint (SOCKS server)
- Optional TLS client certificate (for Quic/TLS Connect)
- SNI on the listening Quic/TLS server
- Ratelimits - global and per-IP
Expand All @@ -19,50 +18,66 @@ go-tunnel uses golang's TLS stack and built-in certification verification.
- YAML Configuration file
- Access Control on per IP or subnet basis (allow/deny combination)
- Strong ciphers and curves preferred on both client & server
- Comes with end-to-end tests covering variety of scenarios

Note that TLS private keys need to be *unencrypted*; we don't support password protected
private keys yet. The main reason for this is that when `gotun` is daemonized, it may not be
possible to obtain the password in an interactive manner. Additionally, for SNI support, it may be
impossible to ask for interactive password in the middle of a client connection setup.

### Motivating Example
Let us suppose that you have a SOCKS5 server on host `192.168.55.3` and this
is accessible via a "gateway" node `172.16.55.3`. Furthermore, let us say that
clients/browsers wishing to use the SOCKS5 proxy are in the `10.0.0.0/24` subnet.
And to keep things simple, let us assume that one host in the `10.0.0.0` network
can access the gateway node: `10.0.0.5`.
## Motivating Example
Lets assume you have a public server on `proxy.example.com`
listening on Quic/UDP supporting SOCKS protocol for connecting to
outbound destinations. For security reasons, you want to limit
access to only clients that are TLS authenticated (TLS client
certs).

Lets also assume that you have a laptop that wants to connect to the
SOCKS server efficiently.

Using two instances of `gotun`, you can accomplish this:

1. Local gotun instance on your laptop configured to accept TCP and
connect using Quic to the external server `proxy.example.com`

Ordinarily, we'd create a IP routing rule on `10.0.0.5` to make the hosts on its network
access the `192.168.55.0/24` via `172.16.55.3`. But, we desire the communication
between `10.0.0.0/24` and `172.16.55.0/24` to be encrypted.
2. Server gotun instance on the external host configured to accept
authenticated Quic connections and proxy via SOCKS.

Thus, with go-tunnel, one can setup a "bridge" between the two networks - and the bridge
is encrypted with TLS. The picture below explains the connectivity:
3. Configure your laptop browser to use the "local" SOCKS server.

![example diagram](/docs/example-diagram.png)
Using Quic to connect the two `gotun` instances reduces the TCP/TLS
overhead of every socks connection. And, TLS client certs enables
strong authentication on the external server.

In the setup above, hosts will treat `10.0.0.5:1080` as their "real" SOCKS server. Behind the
scenes, go-tunnel is relaying the packets from `10.0.0.5` to `172.16.55.3` via TLS. And, in turn
`172.16.55.3` relays the decrypted packets to the actual SOCKS server on `192.168.55.3`.
The picture below explains the connectivity:

The config file shown above actually demonstrates a really secure tunnel - where the server and
client both use certificates to authenticate each other.
![example diagram](/docs/socks-example.png)

Assuming the config on "Gotunnel-A" is in file `a.conf`, and the config on "Gotunnel-B" is in
`b.conf`, to run the above example, on host "Gotunnel-A":
In the setup above, the laptop browser clients will treat
`127.0.0.1:1080` as their "real" SOCKS server. Behind the scenes,
`gotun` will tunnel the packets via Quic to a remote endpoint where
a second `gotun` instance will unbundle the SOCKS protocol and
connect to the final destination.

gotun -d a.conf
The config file shown above actually demonstrates a really secure tunnel
- where the server and client both use certificates to authenticate each other.

And, on host "Gotunnel-B":
Assuming the config on "Gotunnel Laptop" is in file `client.conf`, and the
config on "Gotunnel Server" is in `server.conf`, to run the above example,
on host "Gotunnel-A":

gotun -d b.conf
gotun client.conf

And, on the public server:

The `-d` flag runs `gotun` in debug mode - where the logs are sent
to STDOUT.
gotun server.conf

### Building go-tunnel
You need a reasonably new Golang toolchain (1.8+). And the `go`
The `-d` flag for `gotun` runs it in debug mode - where the logs are sent
to STDOUT. It's not recommended to run a production server in debug
mode (too many log messages).

## Building go-tunnel
You need a reasonably new Golang toolchain (1.14+). And the `go`
executable needs to be in your path. Then run:

make
Expand Down Expand Up @@ -105,8 +120,8 @@ In debug mode, the logs are sent to STDOUT and the debug level is set to DEBUG
In the absence of the `-d` flag, the default log level is INFO or
whatever is set in the config file.

### Config File
The config file is a YAML v2 document. A self-explanatory example is below:
## Config File
The config file is a YAML v2 document. A complete, self-explanatory example is below:

```yaml

Expand All @@ -121,6 +136,10 @@ log: STDOUT
# Logging level - "DEBUG", "INFO", "WARN", "ERROR"
loglevel: DEBUG

# config dir - where all non-absolute file references below will
# apply.
config-dir: /etc/gotun

# Listeners
listen:
# Listen plain text
Expand All @@ -129,25 +148,26 @@ listen:
deny: []

timeout:
connect: 10
read: 10
write: 30
connect: 5
read: 2
write: 2

# limit to N reqs/sec globally
ratelimit:
global: 2000
perhost: 30
per-host: 30
cache-size: 10000

# Connect via TLS
connect:
address: host.name:443
bind: my.ip.address
tls:
cert: /path/to/crt
key: /path/to/key
# path to CA bundle that can verify the server certificate.
# This can be a file or a directory.
ca: /path/to/ca.crt
cert: /path/to/crt
key: /path/to/key
# path to CA bundle that can verify the server certificate.
# This can be a file or a directory.
ca: /path/to/ca.crt

# if address is a name, then servername is populated from it.
# else, if it is an IP address, it must be set below.
Expand All @@ -159,29 +179,48 @@ listen:
allow: [127.0.0.1/8, 11.0.1.0/24, 11.0.2.0/24]
deny: []
timeout:
connect: 8
read: 9
write: 27
connect: 5
read: 2
write: 2

tls:
sni: true
certdir: /path/to/cert/dir
sni: /path/to/cert/dir

# clientcert can be "required" or "optional" or "blank" or absent.
# if it is required/optional, then clientca must be set to the list of
# CAs that can verify a presented client cert.
clientcert: required
clientca: /path/to/clientca.crt
client-cert: required
client-ca: /path/to/clientca.crt

# plain connect but use proxy-protocol v1 when speaking
# downstream
connect:
address: 55.66.77.88:80
proxyprotocol: v1


# Listen on Quic + client auth and connect to SOCKS
- address: 127.0.0.1:8443
tls:
quic: true
cert: /path/to/crt
key: /path/to/key
# path to CA bundle that can verify the server certificate.
# This can be a file or a directory.
ca: /path/to/ca.crt

client-cert: required
client-ca: /path/to/clientca.crt

connect:
address: SOCKS

```

### Using SNI
The `etc/` directory has example configurations for running
Quic+SOCKS on a public server and a local laptop.

## Using SNI
SNI is exposed via domain specific certs & keys in the `tls.certdir` config block. SNI is
enabled by setting `tls.sni` config element to `true`; and each hostname that is requested via
SNI needs a cert and key file with the file prefix of hostname. e.g., if the client is looking
Expand All @@ -190,7 +229,15 @@ for hostname "blog.mydomain.com" via SNI, then `gotun` will look for `blog.mydom
an example for SNI configured on listen address `127.0.0.1:9443`.


### Performance Test
## Security
`gotun` tries to be safe by default:

- Opinionated TLS 1.3 configuration
- All config file references are checked for safety: e.g., any TLS
certs/keys are verified to have sane permissions (NOT group/world
writable)

## Performance Test
Using iperf3 on two debian-linux (amd64) hosts connected via Gigabit Ethernet and `gotun` running on either end,
the performance looks like so:

Expand Down Expand Up @@ -224,7 +271,7 @@ CPU Utilization: local/sender 1.8% (0.0%u/1.7%s), remote/receiver 9.0% (0.6%u/8.

```

### Access Control Rules
## Access Control Rules
Go-tunnel implements a flexible ACL by combination of
allow/deny rules. The rules are evaluated in the following order:

Expand All @@ -234,7 +281,7 @@ allow/deny rules. The rules are evaluated in the following order:
- Explicit denial takes precedence over explicit allow
- Default (fall through) policy is to deny

#### Example of allow/deny combinations
### Example of allow/deny combinations

1. Allow all:

Expand Down Expand Up @@ -273,7 +320,24 @@ If you are a developer, the notes here will be useful for you:
- The code uses go modules; so, you'll need a reasonably new go toolchain (1.10+)
- The go-tunnel code is in `./gotun`.
- The go-tunnel code is in `./gotun`:

* main.go: `main()` for `gotun`
* server.go: Implements TCP/TLS and Quic servers; also
implements the SOCKS server protocol
* conf.go: YAML configuration file parser
* quicdial.go: Dial outbound connections via Quic + streams
* tcpdial.go: Dial outbound connections via TCP
* safety.go: Safely open files/dirs referenced in config file

- Tests: running tests: `go test -v ./gotun`
Some of the tests/helpers:
* mocked_test.go: Mock servers and clients
* tcp_test.go: Tests for TCP/TLS to TCP/TLS
* quic_test.go: Tests for TCP/TLS to Quic and vice versa
* socks_test.go: Tests for socks (includes a test for the
example configuration above)
* utils_test.go: test helpers (e.g., `assert()`)

- We build `build` - a a master shell script to build the daemons;
it does two very important things:
Expand Down
79 changes: 79 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Example Configurations for various scenarios

## TLS to TCP proxy with client authentication
If you want to have strong authentication of clients to access a
service behind the proxy, then the following configuration is a good
starting point:

```yaml

log: SYSLOG
loglevel: INFO

listen:
- address: ip.address:lport
ratelimit:
global: 20000
per-host: 50
# LRU cache size for per-host rate limit
cache-size: 50000

tls:
cert: /path/to/server.crt
key: /path/to/server.key
# ca can be a file containing multiple certs or a
# directory containing ca certs
ca: /path/to/ca.bundle

client-cert: required
client-ca: /path/to/clientca.crt


connect:
address: host.name:port
proxy-protocol: v1

```

Now, clients that have a valid cert/key pair can connect to to
`ip.address:lport` above. And if the cert/key pair is valid and
accepted by the proxy, the client will be connected to the backend
on `host.name:port`.

## Quic to TCP with client authentication
If you have a modern quic client (e.g., most chrome browsers) but
your service is still serving legacy TCP/TLS, `gotunnel` can help
bridge this protocol gap: configure it to listen on a quic port and
relay connections from client to the backend TCP/TLS service:

```yaml

log: SYSLOG
loglevel: INFO

listen:
- address: ip.address:lport
ratelimit:
global: 20000
per-host: 50

tls:
quic: true
cert: /path/to/server.crt
key: /path/to/server.key
# ca can be a file containing multiple certs or a
# directory containing ca certs
ca: /path/to/ca.bundle

clientcert: required
clientca: /path/to/clientca.crt


connect:
address: host.name:port
proxy-protocol: v1
```
The configuration is essentially the same as the previous one - with
the addition of the **`quic: true`** setting. This setting forces
`gotunnel` to listen on a UDP port.
Binary file added docs/socks-example.dia
Binary file not shown.
Binary file added docs/socks-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit bfe2f28

Please sign in to comment.