Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Nicpon authored and michalnicp committed Feb 25, 2020
0 parents commit d225c15
Show file tree
Hide file tree
Showing 6 changed files with 703 additions and 0 deletions.
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Mole

Mole creates publicly accessible HTTP/HTTPS/TCP/UDP tunnels for local development.
Tunnels are implemented using ssh remote port forwarding. Subdomains are assigned
using an auto-incrementing id for every forwarding request.

## Installation

Install the mole server. You will need go and openssh installed before preceeding.

```bash
go get -u github.com/michalnicp/mole
```

Generate the ssh host key file.

```bash
ssh-keygen -t rsa -b 2048 -f /etc/mole/ssh_host_key
```

Start mole.

```bash
$ mole
mole version 0.0.0
```

## Configuration

Mole is configured using environment variables

| Variable | Default | Description |
|-----------------------|-------------------|---------------------------------------------|
| `SERVER_NAME` | | The fully qualified domain name (hostname). |
| `HTTP_ADDR` | `:8080` | The http server address to listen on. |
| `SSH_ADDR` | `:2022` | The ssh server address to listen on. |
| `SSH_HOST_KEY_PATH` | `ssh_host_key` | The path to the ssh server host key file. |
| `SSH_AUTHORIZED_KEYS` | `authorized_keys` | The path to the authorized keys file. |

## Quickstart

This quickstart assumes that the `mole` server is running and available at `example.com`.
You can use any ssh client that supports remote forwarding. For example, using `openssh`

```bash
$ ssh example.com -p 2022 -R 8000:localhost:8000
mole version 0.0.0
forwarding http://1.example.com->localhost:8000
```

Test using `ncat`. The following will start `ncat` listening on port `8000`

```bash
$ printf 'HTTP/1.1 200 OK\r\n' | ncat -l 8000
```

Then, in another terminal, make an http request using `curl`

```bash
$ curl 1.example.com
```

You should see the following output from `ncat`

```bash
$ printf 'HTTP/1.1 200 OK\r\n' | ncat -l 8000
GET / HTTP/1.1
Host: 1.example.com
User-Agent: curl/7.68.0
Accept: */*

```
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/michalnicp/mole

go 1.13

require (
github.com/caarlos0/env v3.5.0+incompatible
github.com/fsnotify/fsnotify v1.4.7
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
110 changes: 110 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package main

import (
"log"
"net/http"
"net/http/httputil"
"os"
"os/signal"
"strconv"
"strings"
"syscall"

"github.com/caarlos0/env"
"github.com/michalnicp/mole/ssh"
)

type config struct {
// ServerName is the server's fully qualified domain name (hostname).
ServerName string `env:"SERVER_NAME"`
HTTPAddr string `env:"HTTP_ADDR" envDefault:":8080"`
SSHAddr string `env:"SSH_ADDR" envDefault:":2022"`
SSHHostKey string `env:"SSH_HOST_KEY" envDefault:"ssh_host_key"`
SSHAuthorizedKeys string `env:"SSH_AUTHORIZED_KEYS" envDefault:"authorized_keys"`
}

func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)

// read config from env
var cfg config
if err := env.Parse(&cfg); err != nil {
log.Fatalf("parse environment: %v", err)
}

// create ssh server
sshServer, err := ssh.NewServer(cfg.SSHAddr)
if err != nil {
log.Fatalf("create ssh server: %v", err)
}

if err := sshServer.Start(); err != nil {
log.Fatalf("start ssh server: %v", err)
}

// create reverse proxy
domain := cfg.ServerName
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
subdomain := strings.TrimSuffix(req.Host, "."+domain)
id64, err := strconv.ParseUint(subdomain, 10, 64)
if err != nil {
log.Printf("parse host: %s: %v", req.Host, err)
return
}
id := uint(id64)

addr, ok := sshServer.GetForwardAddr(id)
if !ok {
log.Printf("forward address not found: %d", id)
return
}

// If there was an error above this line, the scheme will not be set.
// This results in the request failing with a 502 Bad Gateway, which
// is what we want. See proxy.ErrorHandler.

req.URL.Scheme = "http"
req.URL.Host = addr

// TODO: Should we modify req.Host as well?
// req.Host = req.URL.Host
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
if err.Error() != "unsupported protocol scheme \"\"" {
log.Printf("http: proxy error: %v", err)
}
w.WriteHeader(http.StatusBadGateway)
},
}

httpServer := http.Server{
Addr: cfg.HTTPAddr,
Handler: proxy,
}

go func() {
log.Printf("starting http server; http_addr=%s", cfg.HTTPAddr)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("start http server: %v", err)
}
}()

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

s := <-sigs
log.Printf("caught %s, shutting down", s)

if err := httpServer.Close(); err != nil {
log.Printf("close http server: %v", err)
}

if err := sshServer.Close(); err != nil {
log.Printf("close ssh server: %v", err)
}

// FIXME: get this down to 2
// time.Sleep(300 * time.Millisecond)
// pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
}
Loading

0 comments on commit d225c15

Please sign in to comment.