diff --git a/cmd/proxy_client.go b/cmd/proxy_client.go index 3e0b0a7..604cd63 100644 --- a/cmd/proxy_client.go +++ b/cmd/proxy_client.go @@ -3,6 +3,14 @@ package main import ( "crypto/tls" "crypto/x509" + "encoding/base64" + "io/ioutil" + "net" + "net/http" + "strings" + "time" + + "github.com/elazarl/goproxy" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/congestion" "github.com/sirupsen/logrus" @@ -12,10 +20,6 @@ import ( hyHTTP "github.com/tobyxdd/hysteria/pkg/http" "github.com/tobyxdd/hysteria/pkg/obfs" "github.com/tobyxdd/hysteria/pkg/socks5" - "io/ioutil" - "net" - "net/http" - "time" ) func proxyClient(args []string) { @@ -152,6 +156,36 @@ func proxyClient(args []string) { "action": actionToString(action, arg), "dst": reqAddr, }).Debug("New HTTP request") + }, + func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + if config.HTTPUser == "" || config.HTTPPassword == "" { + return req, nil + } + + resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusUnauthorized, "401 - Forbidden: Unauthorized") + // RFC7617 section 2.1 + pa := req.Header.Get("Proxy-Authorization") + if pa == "" { + return req, resp + } + authStr := strings.Fields(pa) + if len(authStr) != 2 || authStr[0] != "Basic" { + return req, resp + } + decodeBytes, err := base64.StdEncoding.DecodeString(authStr[1]) + if err != nil { + logrus.WithField("error", err).Fatal("Failed to decode base64 auth string") + return req, resp + } + userAndPassword := strings.Split(string(decodeBytes), ":") + if len(userAndPassword) != 2 { + return req, resp + } + if userAndPassword[0] != config.HTTPUser || userAndPassword[1] != config.HTTPPassword { + return req, resp + } + + return req, nil }) if err != nil { logrus.WithField("error", err).Fatal("HTTP server initialization failed") diff --git a/cmd/proxy_config.go b/cmd/proxy_config.go index c11f726..7793ae3 100644 --- a/cmd/proxy_config.go +++ b/cmd/proxy_config.go @@ -13,6 +13,8 @@ type proxyClientConfig struct { SOCKS5DisableUDP bool `json:"socks5_disable_udp" desc:"Disable SOCKS5 UDP support"` HTTPAddr string `json:"http_addr" desc:"HTTP listen address"` HTTPTimeout int `json:"http_timeout" desc:"HTTP connection timeout in seconds"` + HTTPUser string `json:"http_user" desc:"HTTP basic auth username"` + HTTPPassword string `json:"http_password" desc:"HTTP basic auth password"` ACLFile string `json:"acl" desc:"Access control list"` ServerAddr string `json:"server" desc:"Server address"` Username string `json:"username" desc:"Authentication username"` diff --git a/pkg/http/server.go b/pkg/http/server.go index eae6ea9..fbc3666 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -3,19 +3,22 @@ package http import ( "errors" "fmt" - "github.com/elazarl/goproxy" - "github.com/tobyxdd/hysteria/pkg/acl" - "github.com/tobyxdd/hysteria/pkg/core" "net" "net/http" "time" + + "github.com/elazarl/goproxy" + "github.com/tobyxdd/hysteria/pkg/acl" + "github.com/tobyxdd/hysteria/pkg/core" ) func NewProxyHTTPServer(hyClient core.Client, idleTimeout time.Duration, aclEngine *acl.Engine, - newDialFunc func(reqAddr string, action acl.Action, arg string)) (*goproxy.ProxyHttpServer, error) { + newDialFunc func(reqAddr string, action acl.Action, arg string), + basicAuthFunc func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response)) (*goproxy.ProxyHttpServer, error) { proxy := goproxy.NewProxyHttpServer() proxy.Logger = &nopLogger{} proxy.NonproxyHandler = http.NotFoundHandler() + proxy.OnRequest().DoFunc(basicAuthFunc) proxy.Tr = &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { // Parse addr string