werty1st / go-tunnel

TLS/SSL Tunnel - A modern STunnel replacement written in golang

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

go-tunnel - Robust TLS Tunnel (Stunnel replacement)

What is it?

An Stunnel replacement written in golang. It is 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.2 for client and server mode (TLS Connect or TLS Listen)
  • Optional TLS client certificate (for TLS Connect)
  • SNI on the listening TLS server
  • Ratelimits - global and per-IP
  • Proxy-Protocol v1 support when connecting to downstream servers
  • YAML Configuration file
  • Access Control on per IP or subnet basis (allow/deny combination)
  • Strong ciphers and curves preferred on both client & server

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.

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.

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:

example diagram

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 config file shown above actually demonstrates a really secure tunnel - where the server and client both use certificates to authenticate each other.

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":

gotun -d a.conf

And, on host "Gotunnel-B":

gotun -d b.conf

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

Building go-tunnel

You need a reasonably new Golang toolchain (1.8+). And the go executable needs to be in your path. Then run:

make

Make essentially runs:

./build

build will build the binary gotun and places it in TARGET specific directory. e.g., for linux-amd64, the binaries will be in ./bin/linux-amd64; and OS X, it will be in ./bin/darwin-amd64 and so on.

You can cross-compile 'go-tun' by passing appropriate architecture names to the script. e.g., to build on host OS X for openbsd-amd64:

./build --arch=openbsd-amd64

You can build a statically linked executable (with no other runtime dependency):

./build -s

The script also has other options. To see them::

./build --help

Running go-tunnel

gotun takes a YAML config file as its sole command line argument. The server does not fork itself into the background. If you need that capability, explore your platform's init toolchain (e.g., start-stop-daemon).

The server can run in debug mode; e.g., on Linux x86_64:

./bin/linux-amd64/gotun -d etc/gotun.conf

In debug mode, the logs are sent to STDOUT and the debug level is set to DEBUG (i.e., verbose).

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:

# Log file; can be one of:
#  - Absolute path
#  - SYSLOG
#  - STDOUT
#  - STDERR
log: STDOUT
#log: STDOUT

# Logging level - "DEBUG", "INFO", "WARN", "ERROR"
loglevel: DEBUG

# Listeners
listen:
    # Listen plain text
    -   address: 127.0.0.1:9090
        allow: [127.0.0.1/8, 11.0.1.0/24, 11.0.2.0/24]
        deny: []

        timeout:
            connect: 10
            read: 10
            write: 30

        # limit to N reqs/sec globally
        ratelimit:
            global: 2000
            perhost: 30

        # 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

            # if address is a name, then servername is populated from it.
            # else, if it is an IP address, it must be set below.
            # Not setting it => no verification (InsecureSkipVerify = true)
            # servername: a.example.com

    # Listen using TLS with SNI
    -   address: 127.0.0.1:9443
        allow: [127.0.0.1/8, 11.0.1.0/24, 11.0.2.0/24]
        deny: []
        timeout:
            connect: 8
            read: 9
            write: 27

        tls:
            sni: true
            certdir: /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

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

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 for hostname "blog.mydomain.com" via SNI, then gotun will look for blog.mydomain.com.crt and blog.mydomain.com.key in the directory identified by tls.certdir. The config file above has an example for SNI configured on listen address 127.0.0.1:9443.

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:

$ iperf3 -V  -c 127.0.0.1 -p 9000
iperf 3.1.3
Linux ungoliant 4.15.0-2-amd64 #1 SMP Debian 4.15.11-1 (2018-03-20) x86_64
Time: Sat, 28 Apr 2018 21:18:46 GMT
Connecting to host 127.0.0.1, port 9000
      Cookie: ungoliant.1524950326.966562.77625193
      TCP MSS: 21888 (default)
[  4] local 127.0.0.1 port 35444 connected to 127.0.0.1 port 9000
Starting Test: protocol: TCP, 1 streams, 131072 byte blocks, omitting 0 seconds, 10 second test
[ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
[  4]   0.00-1.00   sec  54.5 MBytes   457 Mbits/sec    0   2.50 MBytes
[  4]   1.00-2.00   sec  45.7 MBytes   383 Mbits/sec    0   2.50 MBytes
[  4]   2.00-3.00   sec  46.2 MBytes   388 Mbits/sec    0   2.50 MBytes
[  4]   3.00-4.00   sec  46.5 MBytes   390 Mbits/sec    0   2.50 MBytes
[  4]   4.00-5.00   sec  46.6 MBytes   391 Mbits/sec    0   2.50 MBytes
[  4]   5.00-6.00   sec  46.2 MBytes   388 Mbits/sec    0   2.50 MBytes
[  4]   6.00-7.00   sec  47.0 MBytes   394 Mbits/sec    0   2.50 MBytes
[  4]   7.00-8.00   sec  47.7 MBytes   400 Mbits/sec    0   2.50 MBytes
[  4]   8.00-9.00   sec  47.5 MBytes   398 Mbits/sec    0   2.50 MBytes
[  4]   9.00-10.00  sec  46.7 MBytes   392 Mbits/sec    0   2.50 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
Test Complete. Summary Results:
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.00  sec   475 MBytes   398 Mbits/sec    0             sender
[  4]   0.00-10.00  sec   464 MBytes   389 Mbits/sec                  receiver
CPU Utilization: local/sender 1.8% (0.0%u/1.7%s), remote/receiver 9.0% (0.6%u/8.4%s)

Access Control Rules

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

  • If explicitly denied, then host is blocked
  • If allow list is empty, then host is allowed
  • If allow list is non-empty & host is in allow-list, then host is allowed
  • Explicit denial takes precedence over explicit allow
  • Default (fall through) policy is to deny

Example of allow/deny combinations

  1. Allow all:
   allow: []
   deny:  []
  1. Only allow specific subnets and deny everyone else:
    allow: [ 192.168.55.0/24, 172.16.10.0/24, 127.0.0.1/8 ]
    deny: []
  1. Allow all except selected subnets:
    allow: []
    deny: [ 192.168.80.0/24, 172.16.5.0/24 ]
  1. Expliclty block certain hosts and explicitly allow certain subnets and block everyone else:
    allow: [ 192.168.55.0/24, 172.16.10.0/24, 127.0.0.1/8 ]
    deny:  [ 192.168.1.1/32, 192.168.80.0/24, 172.16.5.0/24 ]

Development Notes

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.

  • We build build - a a master shell script to build the daemons; it does two very important things:

    • Puts the binary in an OS/Arch specific directory
    • Injects a git version-tag into the final binary ("linker resolved symbol")

    This script can be reused for other go projects.

  • Example config files is in the etc/gotun.conf directory.

About

TLS/SSL Tunnel - A modern STunnel replacement written in golang

License:GNU General Public License v2.0


Languages

Language:Go 80.1%Language:Shell 19.7%Language:Makefile 0.2%