add local HTTPS reverse proxy
This commit is contained in:
parent
be242e98de
commit
d75e5a3ff3
|
@ -47,9 +47,10 @@ var (
|
|||
)
|
||||
|
||||
type Forward struct {
|
||||
scheme string
|
||||
pattern string
|
||||
port string
|
||||
scheme string
|
||||
pattern string
|
||||
port string
|
||||
localTLS bool
|
||||
}
|
||||
|
||||
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)")
|
||||
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")
|
||||
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>")
|
||||
portToPorts := flag.String("port-forward", "", "a list of <from-port>:<to-port> for raw port-forwarding")
|
||||
verbose := flag.Bool("verbose", false, "log excessively")
|
||||
|
@ -147,6 +149,32 @@ func main() {
|
|||
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) {
|
||||
*portToPorts = os.Getenv("PORT_FORWARDS")
|
||||
}
|
||||
|
@ -467,7 +495,12 @@ func muxAll(
|
|||
for _, fwd := range forwards {
|
||||
//mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second)
|
||||
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]")
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ VERBOSE_RAW=${VERBOSE_RAW:-}
|
|||
--secret "$CLIENT_SECRET" \
|
||||
--tunnel-relay-url $TUNNEL_RELAY_URL \
|
||||
--listen "$LISTEN" \
|
||||
--tls-locals "$TLS_LOCALS" \
|
||||
--locals "$LOCALS" \
|
||||
--acme-agree=${ACME_AGREE} \
|
||||
--acme-email "$ACME_EMAIL" \
|
||||
|
|
10
routemux.go
10
routemux.go
|
@ -158,6 +158,16 @@ func (m *RouteMux) ForwardTCP(servername string, target string, timeout time.Dur
|
|||
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 {
|
||||
m.routes = append(m.routes, meta{
|
||||
addr: servername,
|
||||
|
|
97
telebit.go
97
telebit.go
|
@ -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 {
|
||||
return newReverseProxier(target, timeout, false)
|
||||
}
|
||||
|
||||
func newReverseProxier(target string, timeout time.Duration, theatre bool) HandlerFunc {
|
||||
// TODO accept listener?
|
||||
proxyListener := httpshim.NewListener()
|
||||
myURL, err := url.Parse("http://" + target)
|
||||
scheme := "http://"
|
||||
if theatre {
|
||||
scheme = "https://"
|
||||
}
|
||||
targetURL, err := url.Parse(scheme + target)
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
// TODO headers
|
||||
proxyHandler := httputil.NewSingleHostReverseProxy(myURL)
|
||||
//proxyHandler := httputil.NewSingleHostReverseProxy(targetURL)
|
||||
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{
|
||||
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
|
||||
func Forward(client net.Conn, target net.Conn, timeout time.Duration) error {
|
||||
|
||||
|
|
Loading…
Reference in New Issue