From fd0451fa3bf7448e4901ec26f3a2a3e665dae18e Mon Sep 17 00:00:00 2001 From: Anders Pitman Date: Tue, 20 Oct 2020 20:03:59 -0600 Subject: [PATCH] Implement password-protected tunnels --- api.go | 18 ++++++++++++++++++ boringproxy.go | 26 ++++++++++++++++++++++---- database.go | 2 ++ tunnel_manager.go | 24 ++++++++---------------- webui/index.tmpl | 11 +++++++++++ webui/styles.css | 8 ++++++++ 6 files changed, 69 insertions(+), 20 deletions(-) diff --git a/api.go b/api.go index dec7648..361355a 100644 --- a/api.go +++ b/api.go @@ -79,6 +79,22 @@ func (a *Api) CreateTunnel(tokenData TokenData, params url.Values) (*Tunnel, err allowExternalTcp := params.Get("allow-external-tcp") == "on" + passwordProtect := params.Get("password-protect") == "on" + + var username string + var password string + if passwordProtect { + username = params.Get("username") + if username == "" { + return nil, errors.New("Username required") + } + + password = params.Get("password") + if password == "" { + return nil, errors.New("Password required") + } + } + request := Tunnel{ Domain: domain, Owner: tokenData.Owner, @@ -86,6 +102,8 @@ func (a *Api) CreateTunnel(tokenData TokenData, params url.Values) (*Tunnel, err ClientPort: clientPort, ClientAddress: clientAddr, AllowExternalTcp: allowExternalTcp, + AuthUsername: username, + AuthPassword: password, } tunnel, err := a.tunMan.RequestCreateTunnel(request) diff --git a/boringproxy.go b/boringproxy.go index f8afc29..63af39d 100644 --- a/boringproxy.go +++ b/boringproxy.go @@ -26,6 +26,7 @@ type SmtpConfig struct { } type BoringProxy struct { + db *Database tunMan *TunnelManager httpClient *http.Client } @@ -83,7 +84,7 @@ func Listen() { httpClient := &http.Client{} - p := &BoringProxy{tunMan, httpClient} + p := &BoringProxy{db, tunMan, httpClient} tlsConfig := &tls.Config{ GetCertificate: certConfig.GetCertificate, @@ -117,17 +118,34 @@ func Listen() { func (p *BoringProxy) proxyRequest(w http.ResponseWriter, r *http.Request) { - port, err := p.tunMan.GetPort(r.Host) - if err != nil { + tunnel, exists := p.db.GetTunnel(r.Host) + if !exists { errMessage := fmt.Sprintf("No tunnel attached to %s", r.Host) w.WriteHeader(500) io.WriteString(w, errMessage) return } + if tunnel.AuthUsername != "" || tunnel.AuthPassword != "" { + username, password, ok := r.BasicAuth() + if !ok { + w.Header()["WWW-Authenticate"] = []string{"Basic"} + w.WriteHeader(401) + return + } + + if username != tunnel.AuthUsername || password != tunnel.AuthPassword { + w.Header()["WWW-Authenticate"] = []string{"Basic"} + w.WriteHeader(401) + // TODO: should probably use a better form of rate limiting + time.Sleep(2 * time.Second) + return + } + } + downstreamReqHeaders := r.Header.Clone() - upstreamAddr := fmt.Sprintf("localhost:%d", port) + upstreamAddr := fmt.Sprintf("localhost:%d", tunnel.TunnelPort) upstreamUrl := fmt.Sprintf("http://%s%s", upstreamAddr, r.URL.RequestURI()) upstreamReq, err := http.NewRequest(r.Method, upstreamUrl, r.Body) diff --git a/database.go b/database.go index 9b3ec19..f7d4fab 100644 --- a/database.go +++ b/database.go @@ -36,6 +36,8 @@ type Tunnel struct { ClientAddress string `json:"client_address"` ClientPort int `json:"client_port"` AllowExternalTcp bool `json:"allow_external_tcp"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` CssId string `json:"css_id"` } diff --git a/tunnel_manager.go b/tunnel_manager.go index 429e1e9..ea5adc8 100644 --- a/tunnel_manager.go +++ b/tunnel_manager.go @@ -82,24 +82,16 @@ func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) { return Tunnel{}, err } - tunnel := Tunnel{ - Owner: tunReq.Owner, - Domain: tunReq.Domain, - ServerAddress: m.config.WebUiDomain, - ServerPort: 22, - ServerPublicKey: "", - Username: m.user.Username, - TunnelPort: port, - TunnelPrivateKey: privKey, - ClientName: tunReq.ClientName, - ClientPort: tunReq.ClientPort, - ClientAddress: tunReq.ClientAddress, - AllowExternalTcp: tunReq.AllowExternalTcp, - } + tunReq.ServerAddress = m.config.WebUiDomain + tunReq.ServerPort = 22 + tunReq.ServerPublicKey = "" + tunReq.Username = m.user.Username + tunReq.TunnelPort = port + tunReq.TunnelPrivateKey = privKey - m.db.SetTunnel(tunReq.Domain, tunnel) + m.db.SetTunnel(tunReq.Domain, tunReq) - return tunnel, nil + return tunReq, nil } func (m *TunnelManager) DeleteTunnel(domain string) error { diff --git a/webui/index.tmpl b/webui/index.tmpl index 67f46d1..0434ccc 100644 --- a/webui/index.tmpl +++ b/webui/index.tmpl @@ -76,6 +76,17 @@ +
+ + + +
+ + + + +
+
diff --git a/webui/styles.css b/webui/styles.css index a1dbaeb..ac55402 100644 --- a/webui/styles.css +++ b/webui/styles.css @@ -164,6 +164,14 @@ main { font-weight: bold; } +#login-inputs { + display: none; +} + +#password-protect:checked ~ #login-inputs { + display: block; +} + .token-adder { padding: 5px; display: flex;