Implement password-protected tunnels

This commit is contained in:
Anders Pitman 2020-10-20 20:03:59 -06:00
parent 85a5004cc7
commit fd0451fa3b
6 changed files with 69 additions and 20 deletions

18
api.go
View File

@ -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)

View File

@ -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)

View File

@ -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"`
}

View File

@ -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 {

View File

@ -76,6 +76,17 @@
<label for="allow-external-tcp">Allow External TCP:</label>
<input type="checkbox" id="allow-external-tcp" name="allow-external-tcp">
</div>
<div class='input'>
<label for="password-protect">Password Protect:</label>
<input type="checkbox" id="password-protect" name="password-protect">
<div id='login-inputs'>
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
</div>
</div>
<button class='button' type="submit">Submit</button>
</form>
</div>

View File

@ -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;