Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use MQ as our transport, instead of websockets. #5

Merged
merged 9 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
tunneller
public.pem
private.pem
80 changes: 30 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,59 @@ Table of Contents
* [Installation](#installation)
* [Source Installation go <= 1.11](#source-installation-go---111)
* [Source installation go >= 1.12](#source-installation-go---112)
* [Installation Self-Hosted Server](#installation-self-hosted-server)
* [Installation of your self-hosted Server](#installation-of-your-self-hosted-server)
* [Github Setup](#github-setup)
* [Implementation Issues](#implementation-issues)


# tunneller

Tunneller allows you to expose services which are running on `localhost`, or on your local network, to the entire internet.
Tunneller allows you to expose services which are running on `localhost`, or on your local network, to the public internet.

This is very useful for testing webhooks, the generation of static-site compilers, and similar things.

>**NOTE**: There is a public end-point I host, you __SHOULD NOT__ rely upon it. It might come and go. It is not secure.



## Overview

Assuming you have a service running within your local network, perhaps a HTTP server you could access via http://localhost:8080/, you can expose that to the entire internet by running:
Assuming you have a service running within your local network, perhaps a HTTP server you could access via http://localhost:8080/, you can expose that to the public-internet by running:

$ tunneller client -expose localhost:8080 -name=example
$ tunneller client -expose localhost:8080

This will output something like this:

..
Visit http://example.tunneller.steve.fi/ to see your content
Visit http://5ab654e6-7672-4e54-8368-49ec4aa4c6e4.tunnel.steve.fi/ to see your content

The location listed will now be publicly visible to all remote hosts.

As the name implies there is a central-host involved which is in charge of routing/proxying to your local network - in this case that central host is `tunnel.steve.fi` - the reason this project exists is not to host a general-purpose end-point, but instead to allow you to host your own.

In short this project is designed to be a __self-hosted__ alternative to software such as `ngrok`.

The location listed will now be publicly visible to all remote hosts. As the name implies there is a central-host involved which is in charge of routing/proxying to your local network - in this case that central host is `tunneller.steve.fi`, but the important thing is that you can run your own instance of that server.
So remember:

>**NOTE**: There is a public end-point I host, you __SHOULD NOT__ rely upon it. You should configure your own server, and use it.

This is a self-hosted alternative to a system such as `ngrok`.


## How it works

When a client is launched it creates a web-socket connection to the default remote end-point, `tunneller.steve.fi`, and keeps that connection alive. A name is also sent for that connection.
When a client is launched it creates a connection to a message-bus running on the default remote end-point, `tunnel.steve.fi`, it keeps that connection alive waiting for instructions.

When a request comes in for `foo.tunnel.steve.fi` the server will submit a command for the client to make the appropriate request by publishing a message upon the topic the client is listening to. (Each client has a name, and listens to its own topic).

Next, when a request comes in for `foo.tunneller.steve.fi` the server can look for an open web-socket connection with the name `foo`, and route the request through it:
In short:

* The server sends a "Fetch this URL" request to the client.
* The client makes the request to fetch the URL
* This will succeed, because the client is running inside your network and can access localhost, and any other "internal" resources.
* The response is sent back to the server
* The response is sent back to the server.
* And from there it is routed back to the requested web-browser.

Because the client connects directly to a message-bus there is always the risk that malicious actors will inject fake requests, attempting to scan, probe, and otherwise abuse your local network.


## Installation

Expand Down Expand Up @@ -81,24 +92,19 @@ If you don't have a golang environment setup you should be able to download a bi



## Installation Self-Hosted Server

If you wish to host your own central-server things are a little more complex:

* You'll need to create a DNS-entry `tunneller.example.com`
* You'll also need to setup a __wildcard__ DNS entry for `*.tunneller.example.com` to point to the same host.
* Finally you'll need to setup nginx/apache to proxy to the tunneller application.
* By default this will listen upon 127.0.0.1:8080.
## Installation of your self-hosted Server

You can find a sample configuration file for Apache2 beneath the [apache2](apache2) directory.
If you wish to host your own central-server this is how to do it:

## WSS options
* Create a DNS-entry `tunnel.example.com`, pointing to your host.
* Create a __wildcard__ DNS entry for `*.tunnel.example.com` to point to the same host.
* Setup and configure [mosquitto queue](https://mosquitto.org/) running on that same host.
* See [mq/](mq/) for details there.
* Don't forget to ensure that the MQ-service is publicly visible, by opening a firewall hole for port `1883` if required.

If you want to setup an encrypted remote endpoint (using apache mod_ssl, for example) you can use the `wss://` prefix to connect to a secure websocket (eg. `-tunnel wss://tunneller.example.com`). If no prefix is set on the `-tunnel` option `ws://` is assumed.
Of course security is important, so you should ensure that your message-bus is only reachable by clients you trust to expose their services. (i.e. Your VPN and office range(s).)

In case the SSL certificate you have installed on your webserver is self-signed or you are just testing, you can use the `-insecure` option to allow connection to unverified domains.

See the [apache2](apache2) folder for documentation on how to setup an encrypted websocket.

## Github Setup

Expand All @@ -109,29 +115,3 @@ pull-requests are created/updated. The testing is carried out via

Releases are automated in a similar fashion via [.github/build](.github/build),
and the [github-action-publish-binaries](https://github.com/skx/github-action-publish-binaries) action.


## Implementation Issues

This is a _really_ simple application, but it was more fiddly to implement than I expected, primarily because of issues with using websockets:

* Websocket reads/writes block.
* Keeping the connection alive is fiddly, and requires the use of keep-alives.
* But these can't overlap with the "real" communication.
* Hence the mutex-use.

It would be much simpler to implement a system like this using a RabbitMQ, mosquitto, or some other message-bus:

* Client connects to the message-bus.
* When a HTTP-request must be made it would be sent down the bus.
* The reply would get posted back via the same route.

The downside to using a message-bus is security; a client could easily sniff messages meant for _other_ clients. In a secure-environment this wouldn't matter, but hosting the central-endpoint publicly this would be a mistake.

(Similarly you could use a Redis instance as the central queue, but exposing a Redis host to the internet would be a recipe for compromise.)

If I were running this software at scale I'd probably look at using a queue though; we could create a per-client topic avoiding the issue of security. The downside to that would be we'd need to register:

$ tunneller register --login=steve --password=bar

Once that was complete we could connect to the MQ queue, and subscribe to "#steve" - assuming that the registration created an MQ-user for us, and that the ACLs on the queue would be setup such that the user `steve` couldn't subscripe to the topic belonging to another client (e.g. "`#bob`").
102 changes: 0 additions & 102 deletions apache2/README.md

This file was deleted.

Loading