add local HTTPS reverse proxy

This commit is contained in:
AJ ONeal 2020-08-13 02:34:39 -06:00
parent be242e98de
commit d75e5a3ff3
4 changed files with 142 additions and 7 deletions

View File

@ -47,9 +47,10 @@ var (
) )
type Forward struct { type Forward struct {
scheme string scheme string
pattern string pattern string
port string port string
localTLS bool
} }
var authorizer telebit.Authorizer var authorizer telebit.Authorizer
@ -83,6 +84,7 @@ func main() {
secret := flag.String("secret", "", "the same secret used by telebit-relay (used for JWT authentication)") secret := flag.String("secret", "", "the same secret used by telebit-relay (used for JWT authentication)")
token := flag.String("token", "", "a pre-generated token to give the server (instead of generating one with --secret)") token := flag.String("token", "", "a pre-generated token to give the server (instead of generating one with --secret)")
bindAddrsStr := flag.String("listen", "", "list of bind addresses on which to listen, such as localhost:80, or :443") bindAddrsStr := flag.String("listen", "", "list of bind addresses on which to listen, such as localhost:80, or :443")
tlsLocals := flag.String("tls-locals", "", "like --locals, but TLS will be used to connect to the local port")
locals := flag.String("locals", "", "a list of <from-domain>:<to-port>") locals := flag.String("locals", "", "a list of <from-domain>:<to-port>")
portToPorts := flag.String("port-forward", "", "a list of <from-port>:<to-port> for raw port-forwarding") portToPorts := flag.String("port-forward", "", "a list of <from-port>:<to-port> for raw port-forwarding")
verbose := flag.Bool("verbose", false, "log excessively") verbose := flag.Bool("verbose", false, "log excessively")
@ -147,6 +149,32 @@ func main() {
domains = append(domains, domain) domains = append(domains, domain)
} }
if 0 == len(*tlsLocals) {
*tlsLocals = os.Getenv("TLS_LOCALS")
}
for _, cfg := range strings.Fields(strings.ReplaceAll(*tlsLocals, ",", " ")) {
parts := strings.Split(cfg, ":")
last := len(parts) - 1
port := parts[last]
domain := parts[last-1]
scheme := ""
if len(parts) > 2 {
scheme = parts[0]
}
forwards = append(forwards, Forward{
scheme: scheme,
pattern: domain,
port: port,
localTLS: true,
})
// don't load wildcard into jwt domains
if "*" == domain {
continue
}
domains = append(domains, domain)
}
if 0 == len(*portToPorts) { if 0 == len(*portToPorts) {
*portToPorts = os.Getenv("PORT_FORWARDS") *portToPorts = os.Getenv("PORT_FORWARDS")
} }
@ -467,7 +495,12 @@ func muxAll(
for _, fwd := range forwards { for _, fwd := range forwards {
//mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second) //mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second)
if "https" == fwd.scheme { if "https" == fwd.scheme {
mux.ReverseProxyHTTP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second, "[Servername Reverse Proxy]") if fwd.localTLS {
// this doesn't make much sense, but... security theatre
mux.ReverseProxyHTTPS(fwd.pattern, "localhost:"+fwd.port, 120*time.Second, "[Servername Reverse Proxy TLS]")
} else {
mux.ReverseProxyHTTP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second, "[Servername Reverse Proxy]")
}
} }
mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second, "[Servername Forward]") mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second, "[Servername Forward]")
} }

View File

@ -42,6 +42,7 @@ VERBOSE_RAW=${VERBOSE_RAW:-}
--secret "$CLIENT_SECRET" \ --secret "$CLIENT_SECRET" \
--tunnel-relay-url $TUNNEL_RELAY_URL \ --tunnel-relay-url $TUNNEL_RELAY_URL \
--listen "$LISTEN" \ --listen "$LISTEN" \
--tls-locals "$TLS_LOCALS" \
--locals "$LOCALS" \ --locals "$LOCALS" \
--acme-agree=${ACME_AGREE} \ --acme-agree=${ACME_AGREE} \
--acme-email "$ACME_EMAIL" \ --acme-email "$ACME_EMAIL" \

View File

@ -158,6 +158,16 @@ func (m *RouteMux) ForwardTCP(servername string, target string, timeout time.Dur
return nil return nil
} }
func (m *RouteMux) ReverseProxyHTTPS(servername string, target string, timeout time.Duration, comment ...string) error {
m.routes = append(m.routes, meta{
addr: servername,
terminate: false,
handler: NewTheatricalProxier(target, timeout),
comment: append(comment, "")[0],
})
return nil
}
func (m *RouteMux) ReverseProxyHTTP(servername string, target string, timeout time.Duration, comment ...string) error { func (m *RouteMux) ReverseProxyHTTP(servername string, target string, timeout time.Duration, comment ...string) error {
m.routes = append(m.routes, meta{ m.routes = append(m.routes, meta{
addr: servername, addr: servername,

View File

@ -69,15 +69,72 @@ func NewForwarder(target string, timeout time.Duration) HandlerFunc {
} }
} }
// NewTheatricalProxier exists because... reasons... but should not be used
func NewTheatricalProxier(target string, timeout time.Duration) HandlerFunc {
return newReverseProxier(target, timeout, true)
}
func NewReverseProxier(target string, timeout time.Duration) HandlerFunc { func NewReverseProxier(target string, timeout time.Duration) HandlerFunc {
return newReverseProxier(target, timeout, false)
}
func newReverseProxier(target string, timeout time.Duration, theatre bool) HandlerFunc {
// TODO accept listener? // TODO accept listener?
proxyListener := httpshim.NewListener() proxyListener := httpshim.NewListener()
myURL, err := url.Parse("http://" + target) scheme := "http://"
if theatre {
scheme = "https://"
}
targetURL, err := url.Parse(scheme + target)
if nil != err { if nil != err {
panic(err) panic(err)
} }
// TODO headers //proxyHandler := httputil.NewSingleHostReverseProxy(targetURL)
proxyHandler := httputil.NewSingleHostReverseProxy(myURL) proxyHandler := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.Header.Del("X-Forwarded-For")
req.Header.Del("X-Forwarded-Proto")
req.Header.Del("X-Forwarded-Port")
targetQuery := targetURL.RawQuery
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.URL.Path, req.URL.RawPath = joinURLPath(targetURL, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
},
}
if theatre {
/*
// TODO we could take control of the SNI here
proxyHandler.Transport = &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// would need timeout
dialer := tls.Dialer{
Config: &tls.Config{
ServerName: "localhost",
InsecureSkipVerify: true,
TLSHandshakeTimeout: 10 * time.Second,
},
}
return dialer.DialContext(ctx, network, addr)
},
}
//*/
///*
proxyHandler.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
TLSHandshakeTimeout: 10 * time.Second,
}
//*/
}
proxyServer := &http.Server{ proxyServer := &http.Server{
Handler: proxyHandler, Handler: proxyHandler,
} }
@ -92,6 +149,40 @@ func NewReverseProxier(target string, timeout time.Duration) HandlerFunc {
} }
} }
// Taken from https://golang.org/src/net/http/httputil/reverseproxy.go
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// Forward port-forwards a relay (websocket) client to a target (local) server // Forward port-forwards a relay (websocket) client to a target (local) server
func Forward(client net.Conn, target net.Conn, timeout time.Duration) error { func Forward(client net.Conn, target net.Conn, timeout time.Duration) error {