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