dgrp/main.go

146 lines
2.6 KiB
Go

package main
import (
"context"
"crypto/sha256"
"crypto/subtle"
"flag"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"github.com/armon/go-proxyproto"
)
func main() {
var configFile string
flag.StringVar(&configFile, "config", "config.yaml", "Config file")
flag.Parse()
parseConfig(configFile)
handler := loghttpreq(proxy())
wg := sync.WaitGroup{}
for _, ld := range listenerDefs {
listener, err := net.Listen(ld.protocol, ld.address)
if err != nil {
panic(err)
}
if ld.acceptProxy {
listener = &proxyproto.Listener{
Listener: listener,
}
}
wg.Add(1)
go func() {
http.Serve(listener, handler)
wg.Done()
}()
}
wg.Wait()
}
func rewrite(proxyRequest *httputil.ProxyRequest) {
target, ok := proxyRequest.In.Context().Value("target").(*url.URL)
if !ok {
panic("unreachable")
}
proxyRequest.SetURL(target)
proxyRequest.SetXForwarded()
}
func proxy() http.Handler {
revproxy := &httputil.ReverseProxy{
Rewrite: rewrite,
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tmp := strings.Split(r.Host, ":")
if len(tmp) == 0 {
w.WriteHeader(400)
return
}
hst := tmp[0]
hd, ok := hostDefs[hst]
if !ok {
hd, ok = hostDefs[""]
if !ok {
w.WriteHeader(400)
return
}
}
escapedPath := r.URL.EscapedPath()
var pathDef pathDef
matched := false
for _, hostPathDef := range hd {
if !strings.HasPrefix(escapedPath, hostPathDef.path) {
continue
}
if !matched || len(hostPathDef.path) > len(pathDef.path) {
matched = true
pathDef = hostPathDef
}
}
if !matched {
w.WriteHeader(400)
return
}
r.URL.RawPath = escapedPath[len(pathDef.path):]
unescapedPath, err := url.PathUnescape(r.URL.RawPath)
if err != nil {
panic("unreachable")
}
r.URL.Path = unescapedPath
if pathDef.credentials != nil {
match := false
username, password, ok := r.BasicAuth()
if ok {
hash := sha256.New()
hash.Write([]byte(username))
usernameSum := hash.Sum(nil)
hash = sha256.New()
hash.Write([]byte(password))
passwordSum := hash.Sum(nil)
for _, cred := range pathDef.credentials {
res1 := subtle.ConstantTimeCompare(cred.username, usernameSum) == 1
res2 := subtle.ConstantTimeCompare(cred.password, passwordSum) == 1
if res1 && res2 {
match = true
break
}
}
}
if !match {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(401)
return
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, "target", pathDef.targetURL)
r = r.WithContext(ctx)
w.Header().Add("Server", "dgrp")
revproxy.ServeHTTP(w, r)
})
}