diff --git a/api.go b/api.go index d733fc0..c622527 100644 --- a/api.go +++ b/api.go @@ -374,10 +374,26 @@ func (a *Api) CreateTunnel(tokenData TokenData, params url.Values) (*Tunnel, err } tlsTerm := params.Get("tls-termination") - if tlsTerm != "server" && tlsTerm != "client" && tlsTerm != "passthrough" && tlsTerm != "client-tls" { + if tlsTerm != "server" && tlsTerm != "client" && tlsTerm != "passthrough" && tlsTerm != "client-tls" && tlsTerm != "server-tls" { return nil, errors.New("Invalid tls-termination parameter") } + sshServerAddr := a.db.GetAdminDomain() + sshServerAddrParam := params.Get("ssh-server-addr") + if sshServerAddrParam != "" { + sshServerAddr = sshServerAddrParam + } + + sshServerPort := a.config.SshServerPort + sshServerPortParam := params.Get("ssh-server-port") + if sshServerPortParam != "" { + var err error + sshServerPort, err = strconv.Atoi(sshServerPortParam) + if err != nil { + return nil, errors.New("Invalid ssh-server-port parameter") + } + } + request := Tunnel{ Domain: domain, Owner: owner, @@ -389,6 +405,8 @@ func (a *Api) CreateTunnel(tokenData TokenData, params url.Values) (*Tunnel, err AuthUsername: username, AuthPassword: password, TlsTermination: tlsTerm, + ServerAddress: sshServerAddr, + ServerPort: sshServerPort, } tunnel, err := a.tunMan.RequestCreateTunnel(request) diff --git a/boringproxy.go b/boringproxy.go index 50cc5d1..28ae553 100644 --- a/boringproxy.go +++ b/boringproxy.go @@ -338,11 +338,11 @@ func Listen() { continue } - go p.handleConnection(conn) + go p.handleConnection(conn, certConfig) } } -func (p *Server) handleConnection(clientConn net.Conn) { +func (p *Server) handleConnection(clientConn net.Conn, certConfig *certmagic.Config) { clientHello, clientReader, err := peekClientHello(clientConn) if err != nil { @@ -356,6 +356,13 @@ func (p *Server) handleConnection(clientConn net.Conn) { if exists && (tunnel.TlsTermination == "client" || tunnel.TlsTermination == "passthrough") || tunnel.TlsTermination == "client-tls" { p.passthroughRequest(passConn, tunnel) + } else if exists && tunnel.TlsTermination == "server-tls" { + useTls := true + err := ProxyTcp(passConn, "127.0.0.1", tunnel.TunnelPort, useTls, certConfig) + if err != nil { + log.Println(err.Error()) + return + } } else { p.httpListener.PassConn(passConn) } diff --git a/client.go b/client.go index 2976cf2..9cf2d45 100644 --- a/client.go +++ b/client.go @@ -6,12 +6,10 @@ import ( "encoding/json" "errors" "fmt" - "io" "io/ioutil" "log" "net" "net/http" - "strings" "sync" "time" @@ -321,18 +319,6 @@ func (c *Client) BoreTunnel(ctx context.Context, tunnel Tunnel) error { } else { - if tunnel.TlsTermination == "client-tls" { - tlsConfig := &tls.Config{ - GetCertificate: c.certConfig.GetCertificate, - } - - tlsConfig.NextProtos = append([]string{"http/1.1", "h2", "acme-tls/1"}, tlsConfig.NextProtos...) - - tlsListener := tls.NewListener(listener, tlsConfig) - - listener = tlsListener - } - go func() { for { conn, err := listener.Accept() @@ -346,17 +332,14 @@ func (c *Client) BoreTunnel(ctx context.Context, tunnel Tunnel) error { //continue } - // If ALPN type is acme-tls/1, certmagic will do its thing under the hood, and the - // connection should not be used. - if tlsConn, ok := conn.(*tls.Conn); ok { - tlsConn.Handshake() - if tlsConn.ConnectionState().NegotiatedProtocol == "acme-tls/1" { - tlsConn.Close() - continue - } + var useTls bool + if tunnel.TlsTermination == "client-tls" { + useTls = true + } else { + useTls = false } - go c.handleConnection(conn, tunnel.ClientAddress, tunnel.ClientPort) + go ProxyTcp(conn, tunnel.ClientAddress, tunnel.ClientPort, useTls, c.certConfig) } }() } @@ -376,75 +359,6 @@ func (c *Client) BoreTunnel(ctx context.Context, tunnel Tunnel) error { return nil } -func (c *Client) handleConnection(conn net.Conn, upstreamAddr string, port int) { - - defer conn.Close() - - useTls := false - addr := upstreamAddr - - if strings.HasPrefix(upstreamAddr, "https://") { - addr = upstreamAddr[len("https://"):] - useTls = true - } - - var upstreamConn net.Conn - var err error - - if useTls { - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - upstreamConn, err = tls.Dial("tcp", fmt.Sprintf("%s:%d", addr, port), tlsConfig) - } else { - upstreamConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port)) - } - - if err != nil { - log.Print(err) - return - } - - defer upstreamConn.Close() - - var wg sync.WaitGroup - wg.Add(2) - - // Copy request to upstream - go func() { - _, err := io.Copy(upstreamConn, conn) - if err != nil { - log.Println(err.Error()) - } - - if c, ok := upstreamConn.(*net.TCPConn); ok { - c.CloseWrite() - } else if c, ok := upstreamConn.(*tls.Conn); ok { - c.CloseWrite() - } - - wg.Done() - }() - - // Copy response to downstream - go func() { - _, err := io.Copy(conn, upstreamConn) - //conn.(*net.TCPConn).CloseWrite() - if err != nil { - log.Println(err.Error()) - } - // TODO: I added this to fix a bug where the copy to - // upstreamConn was never closing, even though the copy to - // conn was. It seems related to persistent connections going - // idle and upstream closing the connection. I'm a bit worried - // this might not be thread safe. - conn.Close() - wg.Done() - }() - - wg.Wait() -} - func printJson(data interface{}) { d, _ := json.MarshalIndent(data, "", " ") fmt.Println(string(d)) diff --git a/cmd/boringproxy/main.go b/cmd/boringproxy/main.go index 4e28b61..5331dc1 100644 --- a/cmd/boringproxy/main.go +++ b/cmd/boringproxy/main.go @@ -2,9 +2,13 @@ package main import ( "context" + "crypto/tls" "flag" "fmt" + "io" + "net" "os" + "sync" "github.com/boringproxy/boringproxy" ) @@ -15,6 +19,7 @@ Commands: version Prints version information. server Start a new server. client Connect to a server. + tuntls Tunnel a raw TLS connection. Use "%[1]s command -h" for a list of flags for the command. ` @@ -25,7 +30,6 @@ func fail(msg string) { fmt.Fprintln(os.Stderr, msg) os.Exit(1) } - func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, os.Args[0]+": Need a command") @@ -40,6 +44,44 @@ func main() { fmt.Println(Version) case "help", "-h", "--help", "-help": fmt.Printf(usage, os.Args[0]) + case "tuntls": + // This command is a direct port of https://github.com/anderspitman/tuntls + flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + server := flagSet.String("server", "", "boringproxy server") + port := flagSet.Int("port", 0, "Local port to bind to") + err := flagSet.Parse(os.Args[2:]) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: parsing flags: %s\n", os.Args[0], err) + os.Exit(1) + } + + if *server == "" { + fmt.Fprintf(os.Stderr, "server argument is required\n") + os.Exit(1) + } + + if *port == 0 { + // one-time tunnel over stdin/stdout + doTlsTunnel(*server, os.Stdin, os.Stdout) + } else { + // listen on a port and create tunnels for each connection + fmt.Fprintf(os.Stderr, "Listening on port %d\n", *port) + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + go doTlsTunnel(*server, conn, conn) + } + } case "server": boringproxy.Listen() case "client": @@ -97,3 +139,32 @@ func main() { fail(os.Args[0] + ": Invalid command " + command) } } + +func doTlsTunnel(server string, in io.Reader, out io.Writer) { + fmt.Fprintf(os.Stderr, "tuntls connecting to server: %s\n", server) + + conn, err := tls.Dial("tcp", fmt.Sprintf("%s:443", server), &tls.Config{ + //RootCAs: roots, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to connect: "+err.Error()) + os.Exit(1) + } + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + io.Copy(conn, in) + wg.Done() + }() + + go func() { + io.Copy(out, conn) + wg.Done() + }() + + wg.Wait() + + conn.Close() +} diff --git a/templates/edit_tunnel.tmpl b/templates/edit_tunnel.tmpl index 30c3684..94366f2 100644 --- a/templates/edit_tunnel.tmpl +++ b/templates/edit_tunnel.tmpl @@ -39,6 +39,7 @@ + @@ -58,6 +59,15 @@ +