631 lines
17 KiB
Markdown
631 lines
17 KiB
Markdown
Goldilocks
|
|
==========
|
|
|
|
The node.js netserver that's just right.
|
|
|
|
* **HTTPS Web Server** with Automatic TLS (SSL) via ACME ([Let's Encrypt](https://letsencrypt.org))
|
|
* Static Web Server
|
|
* URL Redirects
|
|
* SSL on localhost (with bundled localhost.daplie.me certificates)
|
|
* Uses node cluster to take advantage of multiple CPUs (in progress)
|
|
* **TLS** name-based (SNI) proxy
|
|
* **TCP** port-based proxy
|
|
* WS **Tunnel Server** (i.e. run on Digital Ocean and expose a home-firewalled Raspberry Pi to the Internet)
|
|
* WS **Tunnel Client** (i.e. run on a Raspberry Pi and connect to a Daplie Tunnel)
|
|
* UPnP / NAT-PMP forwarding and loopback testing (in progress)
|
|
* Configurable via API
|
|
* mDNS Discoverable (configure in home or office with mobile and desktop apps)
|
|
* OAuth3 Authentication
|
|
|
|
Install Standalone
|
|
-------
|
|
|
|
### curl | bash
|
|
|
|
```bash
|
|
curl -fsSL https://git.daplie.com/Daplie/goldilocks.js/raw/v1.1/installer/get.sh
|
|
```
|
|
|
|
### git
|
|
|
|
```bash
|
|
git clone https://git.daplie.com/Daplie/goldilocks.js
|
|
git checkout v1.1
|
|
pushd goldilocks.js
|
|
bash installer/install.sh
|
|
```
|
|
|
|
### npm
|
|
|
|
```bash
|
|
# v1 in git (unauthenticated)
|
|
npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js#v1
|
|
|
|
# v1 in git (via ssh)
|
|
npm install -g git+ssh://git@git.daplie.com:Daplie/goldilocks.js#v1
|
|
|
|
# v1 in npm
|
|
npm install -g goldilocks@v1
|
|
```
|
|
|
|
```bash
|
|
goldilocks
|
|
```
|
|
|
|
```bash
|
|
Serving /Users/foo/ at https://localhost.daplie.me:8443
|
|
```
|
|
|
|
Install as a System Service (daemon-mode)
|
|
|
|
We have service support for
|
|
|
|
* systemd (Linux, Ubuntu)
|
|
* launchd (macOS)
|
|
|
|
```bash
|
|
curl https://git.daplie.com/Daplie/goldilocks.js/raw/master/install.sh | bash
|
|
```
|
|
|
|
Modules & Configuration
|
|
-----
|
|
|
|
Goldilocks has several core systems, which all have their own configuration and
|
|
some of which have modules:
|
|
|
|
* [http](#http)
|
|
- [proxy (reverse proxy)](#httpproxy-how-to-reverse-proxy-ruby-python-etc)
|
|
- [static](#httpstatic-how-to-serve-a-web-page)
|
|
- [redirect](#httpredirect-how-to-redirect-urls)
|
|
* [tls](#tls)
|
|
- [proxy (reverse proxy)](#tlsproxy)
|
|
- [acme](#tlsacme)
|
|
* [tcp](#tcp)
|
|
- [proxy](#tcpproxy)
|
|
- [forward](#tcpforward)
|
|
* [udp](#udp)
|
|
- [forward](#udpforward)
|
|
* [domains](#domains)
|
|
* [tunnel_server](#tunnel_server)
|
|
* [DDNS](#ddns)
|
|
* [tunnel_client](#tunnel)
|
|
* [mDNS](#mdns)
|
|
* [socks5](#socks5)
|
|
* api
|
|
|
|
All modules require a `type` and an `id`, and any modules not defined inside the
|
|
`domains` system also require a `domains` field (with the exception of the `forward`
|
|
modules that require the `ports` field).
|
|
|
|
### http
|
|
|
|
The HTTP system handles plain http (TLS / SSL is handled by the tls system)
|
|
|
|
Example config:
|
|
```yml
|
|
http:
|
|
trust_proxy: true # allow localhost, 192.x, 10.x, 172.x, etc to set headers
|
|
allow_insecure: false # allow non-https even without proxy https headers
|
|
primary_domain: example.com # attempts to access via IP address will redirect here
|
|
|
|
# An array of modules that define how to handle incoming HTTP requests
|
|
modules:
|
|
- type: static
|
|
domains:
|
|
- example.com
|
|
root: /srv/www/:hostname
|
|
```
|
|
|
|
### http.proxy - how to reverse proxy (ruby, python, etc)
|
|
|
|
The proxy module is for reverse proxying, typically to an application on the same machine.
|
|
(Though it can also reverse proxy to other devices on the local network.)
|
|
|
|
It has the following options:
|
|
```
|
|
address The DNS-resolvable hostname (or IP address) and port connected by `:` to proxy the request to.
|
|
Takes priority over host and port if they are also specified.
|
|
ex: locahost:3000
|
|
ex: 192.168.1.100:80
|
|
|
|
host The DNS-resolvable hostname (or IP address) of the system to which the request will be proxied.
|
|
Defaults to localhost if only the port is specified.
|
|
ex: localhost
|
|
ex: 192.168.1.100
|
|
|
|
port The port on said system to which the request will be proxied
|
|
ex: 3000
|
|
ex: 80
|
|
```
|
|
|
|
Example config:
|
|
```yml
|
|
http:
|
|
modules:
|
|
- type: proxy
|
|
domains:
|
|
- api.example.com
|
|
host: 192.168.1.100
|
|
port: 80
|
|
- type: proxy
|
|
domains:
|
|
- www.example.com
|
|
address: 192.168.1.16:80
|
|
- type: proxy
|
|
domains:
|
|
- '*'
|
|
port: 3000
|
|
```
|
|
|
|
### http.static - how to serve a web page
|
|
|
|
The static module is for serving static web pages and assets and has the following options:
|
|
|
|
```
|
|
root The path to serve as a string.
|
|
The template variable `:hostname` represents the HTTP Host header without port information
|
|
ex: `root: /srv/www/example.com` would load the example.com folder for any domain listed
|
|
ex: `root: /srv/www/:hostname` would load `/srv/www/example.com` if so indicated by the Host header
|
|
|
|
index Set to `false` to disable the default behavior of loading `index.html` in directories
|
|
ex: `false`
|
|
|
|
dotfiles Set to `allow` to load dotfiles rather than ignoring them
|
|
ex: `"allow"`
|
|
|
|
redirect Set to `false` to disable the default behavior of ensuring that directory paths end in '/'
|
|
ex: `false`
|
|
|
|
indexes An array of directories which should be have indexes served rather than blocked
|
|
ex: `[ '/' ]` will allow all directories indexes to be served
|
|
```
|
|
|
|
Example config:
|
|
```yml
|
|
http:
|
|
modules:
|
|
- type: static
|
|
domains:
|
|
- example.com
|
|
root: /srv/www/:hostname
|
|
```
|
|
|
|
### http.redirect - how to redirect URLs
|
|
|
|
The redirect module is for, you guessed it, redirecting URLs.
|
|
|
|
It has the following options:
|
|
```
|
|
status The HTTP status code to issue (301 is usual permanent redirect, 302 is temporary)
|
|
ex: 301
|
|
|
|
from The URL path that was used in the request.
|
|
The `*` wildcard character can be used for matching a full segment of the path
|
|
ex: /photos/
|
|
ex: /photos/*/*/
|
|
|
|
to The new URL path which should be used.
|
|
If wildcards matches were used they will be available as `:1`, `:2`, etc.
|
|
ex: /pics/
|
|
ex: /pics/:1/:2/
|
|
ex: https://mydomain.com/photos/:1/:2/
|
|
```
|
|
|
|
Example config:
|
|
```yml
|
|
http:
|
|
modules:
|
|
- type: proxy
|
|
domains:
|
|
- example.com
|
|
status: 301
|
|
from: /archives/*/*/*/
|
|
to: https://example.net/year/:1/month/:2/day/:3/
|
|
```
|
|
|
|
### tls
|
|
|
|
The tls system handles encrypted connections, including fetching certificates,
|
|
and uses ServerName Indication (SNI) to determine if the connection should be
|
|
handled by the http system, a tls system module, or rejected.
|
|
|
|
Example config:
|
|
```yml
|
|
tls:
|
|
modules:
|
|
- type: proxy
|
|
domains:
|
|
- example.com
|
|
- example.net
|
|
address: '127.0.0.1:6443'
|
|
```
|
|
|
|
Certificates are saved to `~/acme`, which may be `/var/www/acme` if Goldilocks is run as the www-data user.
|
|
|
|
### tls.proxy
|
|
|
|
The proxy module routes the traffic based on the ServerName Indication (SNI) **without decrypting** it.
|
|
|
|
It has the same options as the [HTTP proxy module](#httpproxy-how-to-reverse-proxy-ruby-python-etc).
|
|
|
|
Example config:
|
|
```yml
|
|
tls:
|
|
modules:
|
|
- type: proxy
|
|
domains:
|
|
- example.com
|
|
address: '127.0.0.1:5443'
|
|
```
|
|
|
|
### tls.acme
|
|
|
|
The acme module defines the setting used when getting new certificates.
|
|
|
|
It has the following options:
|
|
```
|
|
email The email address for ACME certificate issuance
|
|
ex: john.doe@example.com
|
|
|
|
server The ACME server to use
|
|
ex: https://acme-v01.api.letsencrypt.org/directory
|
|
ex: https://acme-staging.api.letsencrypt.org/directory
|
|
|
|
challenge_type The ACME challenge to request
|
|
ex: http-01, dns-01, tls-01
|
|
```
|
|
|
|
Example config:
|
|
```yml
|
|
tls:
|
|
modules:
|
|
- type: acme
|
|
domains:
|
|
- example.com
|
|
- example.net
|
|
email: 'joe.shmoe@example.com'
|
|
server: 'https://acme-staging.api.letsencrypt.org/directory'
|
|
challenge_type: 'http-01'
|
|
```
|
|
|
|
### tcp
|
|
|
|
The tcp system handles both *raw* and *tls-terminated* tcp network traffic
|
|
(see the _Note_ section below the example). It may use port numbers
|
|
or traffic sniffing to determine how the connection should be handled.
|
|
|
|
It has the following options:
|
|
```
|
|
bind An array of numeric ports on which to bind
|
|
ex: 80
|
|
```
|
|
|
|
Example Config:
|
|
```yml
|
|
tcp:
|
|
bind:
|
|
- 22
|
|
- 80
|
|
- 443
|
|
modules:
|
|
- type: forward
|
|
ports:
|
|
- 22
|
|
address: '127.0.0.1:2222'
|
|
```
|
|
|
|
_Note_: When tcp traffic comes into goldilocks it will be tested against the tcp modules.
|
|
The connection may be handed to the TLS module if it appears to be a TLS/SSL/HTTPS connection
|
|
and if the tls module terminates the traffic, the connection will be sent back to the TLS module.
|
|
Due to the complexity of node.js' networking stack it is not currently possible to tell which
|
|
port tls-terminated traffic came from, so only the SNI header (serername / domain name) may be used for
|
|
modules matching terminated TLS.
|
|
|
|
### tcp.proxy
|
|
|
|
The proxy module routes traffic **after tls-termination** based on the servername (domain name)
|
|
contained in a SNI header. As such this only works to route TCP connections wrapped in a TLS stream.
|
|
|
|
It has the same options as the [HTTP proxy module](#httpproxy-how-to-reverse-proxy-ruby-python-etc).
|
|
|
|
This is particularly useful for routing ssh and vpn traffic over tcp port 443 as wrapped TLS
|
|
connections in order to access one of your servers even when connecting from a harsh or potentially
|
|
misconfigured network environment (i.e. hotspots in public libraries and shopping malls).
|
|
|
|
Example config:
|
|
```yml
|
|
tcp:
|
|
modules:
|
|
- type: proxy
|
|
domains:
|
|
- ssh.example.com # Note: this domain would also listed in tls.acme.domains
|
|
host: localhost
|
|
port: 22
|
|
- type: proxy
|
|
domains:
|
|
- vpn.example.com # Note: this domain would also listed in tls.acme.domains
|
|
host: localhost
|
|
port: 1194
|
|
```
|
|
|
|
_Note_: In same cases network administrators purposefully block ssh and vpn connections using
|
|
Application Firewalls with DPI (deep packet inspection) enabled. You should read the ToS of the
|
|
network you are connected to to ensure that you aren't subverting policies that are purposefully
|
|
in place on such networks.
|
|
|
|
#### Using with ssh
|
|
|
|
In order to use this to route SSH connections you will need to use `ssh`'s
|
|
`ProxyCommand` option. For example to use the TLS certificate for `ssh.example.com`
|
|
to wrap an ssh connection you could use the following command:
|
|
|
|
```bash
|
|
ssh user@example.com -o ProxyCommand='openssl s_client -quiet -connect example.com:443 -servername ssh.example.com'
|
|
```
|
|
|
|
Alternatively you could add the following lines to your ssh config file.
|
|
```
|
|
Host example.com
|
|
ProxyCommand openssl s_client -quiet -connect example.com:443 -servername ssh.example.com
|
|
```
|
|
|
|
#### Using with OpenVPN
|
|
|
|
There are two strategies that will work well for you:
|
|
|
|
1) [Use ssh](https://redfern.me/tunneling-openvpn-through-ssh/) with the config above to reverse proxy tcp port 1194 to you.
|
|
|
|
```bash
|
|
ssh -L 1194:localhost:1194 example.com
|
|
```
|
|
|
|
2) [Use stunnel]https://serverfault.com/questions/675553/stunnel-vpn-traffic-and-ensure-it-looks-like-ssl-traffic-on-port-443/681497)
|
|
|
|
```
|
|
[openvpn-over-goldilocks]
|
|
client = yes
|
|
accept = 127.0.0.1:1194
|
|
sni = vpn.example.com
|
|
connect = example.com:443
|
|
```
|
|
|
|
3) [Use stunnel.js](https://git.daplie.com/Daplie/node-tunnel-client) as described in the "tunnel_server" section below.
|
|
|
|
### tcp.forward
|
|
|
|
The forward module routes traffic based on port number **without decrypting** it.
|
|
|
|
In addition to the same options as the [HTTP proxy module](#httpproxy-how-to-reverse-proxy-ruby-python-etc),
|
|
the TCP forward modules also has the following options:
|
|
|
|
```
|
|
ports A numeric array of source ports
|
|
ex: 22
|
|
```
|
|
|
|
Example Config:
|
|
```yml
|
|
tcp:
|
|
bind:
|
|
- 22
|
|
- 80
|
|
- 443
|
|
modules:
|
|
- type: forward
|
|
ports:
|
|
- 22
|
|
port: 2222
|
|
```
|
|
|
|
### udp
|
|
|
|
The udp system handles all udp network traffic. It currently only supports
|
|
forwarding the messages without any examination.
|
|
|
|
It has the following options:
|
|
```
|
|
bind An array of numeric ports on which to bind
|
|
ex: 53
|
|
```
|
|
|
|
Example Config:
|
|
```yml
|
|
udp:
|
|
bind:
|
|
- 53
|
|
modules:
|
|
- type: forward
|
|
ports:
|
|
- 53
|
|
address: '127.0.0.1:8053'
|
|
```
|
|
|
|
### udp.forward
|
|
|
|
The forward module routes traffic based on port number **without decrypting** it.
|
|
|
|
It has the same options as the [TCP forward module](#tcpforward).
|
|
|
|
Example Config:
|
|
```yml
|
|
udp:
|
|
bind:
|
|
- 53
|
|
modules:
|
|
- type: forward
|
|
ports:
|
|
- 53
|
|
address: '127.0.0.1:8053'
|
|
```
|
|
|
|
### domains
|
|
|
|
To reduce repetition defining multiple modules that operate on the same domain
|
|
name the `domains` field can define multiple modules of multiple types for a
|
|
single list of names. The modules defined this way do not need to have their
|
|
own `domains` field. Note that the [tcp.forward](#tcpforward) module is not
|
|
allowed in a domains group since its routing is not based on domains.
|
|
|
|
Example Config
|
|
|
|
```yml
|
|
domains:
|
|
- names:
|
|
- example.com
|
|
- www.example.com
|
|
- api.example.com
|
|
modules:
|
|
tls:
|
|
- type: acme
|
|
email: joe.schmoe@example.com
|
|
challenge_type: 'http-01'
|
|
http:
|
|
- type: redirect
|
|
from: /deprecated/path
|
|
to: /new/path
|
|
- type: proxy
|
|
port: 3000
|
|
dns:
|
|
- type: 'dns@oauth3.org'
|
|
token_id: user_token_id
|
|
|
|
- names:
|
|
- ssh.example.com
|
|
modules:
|
|
tls:
|
|
- type: acme
|
|
email: john.smith@example.com
|
|
challenge_type: 'http-01'
|
|
tcp:
|
|
- type: proxy
|
|
port: 22
|
|
dns:
|
|
- type: 'dns@oauth3.org'
|
|
token_id: user_token_id
|
|
```
|
|
|
|
|
|
|
|
### tunnel\_server
|
|
|
|
The tunnel server system is meant to be run on a publicly accessible IP address to server tunnel clients
|
|
which are behind firewalls, carrier-grade NAT, or otherwise Internet-connect but inaccessible devices.
|
|
|
|
It has the following options:
|
|
|
|
```
|
|
secret A 128-bit or greater string to use for signing tokens (HMAC JWT)
|
|
ex: abc123
|
|
|
|
servernames An array of string servernames that should be captured as the
|
|
tunnel server, ignoring the TLS forward module
|
|
ex: api.tunnel.example.com
|
|
```
|
|
|
|
Example config:
|
|
|
|
```yml
|
|
tunnel_server:
|
|
secret: abc123def456ghi789
|
|
servernames:
|
|
- 'api.tunnel.example.com'
|
|
```
|
|
|
|
### DDNS
|
|
|
|
The DDNS module watches the network environment of the unit and makes sure the
|
|
device is always accessible on the internet using the domains listed in the
|
|
config. If the device has a public address or if it can automatically set up
|
|
port forwarding the device will periodically check its public address to ensure
|
|
the DNS records always point to it. Otherwise it will to connect to a tunnel
|
|
server and set the DNS records to point to that server.
|
|
|
|
The `loopback` setting specifies how the unit will check its public IP address
|
|
and whether connections can reach it. Currently only `tunnel@oauth3.org` is
|
|
supported. If the loopback setting is not defined it will default to using
|
|
`oauth3.org`.
|
|
|
|
The `tunnel` setting can be used to specify how to connect to the tunnel.
|
|
Currently only `tunnel@oauth3.org` is supported. The token specified in the
|
|
`tunnel` setting will be used to acquire the tokens that are used directly with
|
|
the tunnel server. If the tunnel setting is not defined it will default to try
|
|
using the tokens in the modules for the relevant domains.
|
|
|
|
If a particular DDNS module has been disabled the device will still try to set
|
|
up port forwarding (and connect to a tunnel if that doesn't work), but the DNS
|
|
records will not be updated to point to the device. This is to allow a setup to
|
|
be tested before transitioning services between devices.
|
|
|
|
```yaml
|
|
ddns:
|
|
disabled: false
|
|
loopback:
|
|
type: 'tunnel@oauth3.org'
|
|
domain: oauth3.org
|
|
tunnel:
|
|
type: 'tunnel@oauth3.org'
|
|
token_id: user_token_id
|
|
modules:
|
|
- type: 'dns@oauth3.org'
|
|
token_id: user_token_id
|
|
domains:
|
|
- www.example.com
|
|
- api.example.com
|
|
- test.example.com
|
|
```
|
|
|
|
### mDNS
|
|
|
|
enabled by default
|
|
|
|
Although it does not announce itself, Goldilocks is discoverable via mDNS with the special query `_cloud._tcp.local`.
|
|
This is so that it can be easily configured via Desktop and Mobile apps when run on devices such as a Raspberry Pi or
|
|
SOHO servers.
|
|
|
|
```yaml
|
|
mdns:
|
|
disabled: false
|
|
port: 5353
|
|
broadcast: '224.0.0.251'
|
|
ttl: 300
|
|
```
|
|
|
|
You can discover goldilocks with `mdig`.
|
|
|
|
```
|
|
npm install -g git+https://git.daplie.com/Daplie/mdig.git
|
|
|
|
mdig _cloud._tcp.local
|
|
```
|
|
|
|
### socks5
|
|
|
|
Run a Socks5 proxy server.
|
|
|
|
```yaml
|
|
socks5:
|
|
enable: true
|
|
port: 1080
|
|
```
|
|
|
|
### api
|
|
|
|
See [API.md](/API.md)
|
|
|
|
@tigerbot: How are the APIs used (in terms of URL, Method, Headers, etc)?
|
|
|
|
TODO
|
|
----
|
|
|
|
* [ ] http - nowww module
|
|
* [ ] http - Allow match styles of `www.*`, `*`, and `*.example.com` equally
|
|
* [ ] http - redirect based on domain name (not just path)
|
|
* [ ] tcp - bind should be able to specify localhost, uniquelocal, private, or ip
|
|
* [ ] tcp - if destination host is omitted default to localhost, if dst port is missing, default to src
|
|
* [ ] sys - `curl https://daplie.me/goldilocks | bash -s example.com`
|
|
* [ ] oauth3 - `example.com/.well-known/domains@oauth3.org/directives.json`
|
|
* [ ] oauth3 - commandline questionnaire
|
|
* [x] modules - use consistent conventions (i.e. address vs host + port)
|
|
* [x] tls - tls.acme vs tls.modules.acme
|
|
* [ ] tls - forward should be able to match on source port to reach different destination ports
|