Merge branch 'master' into oauth2

This commit is contained in:
Anders Pitman 2022-02-25 11:26:57 -07:00
commit a602c6cfa7
7 changed files with 238 additions and 99 deletions

20
api.go
View File

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

View File

@ -112,7 +112,7 @@ func Listen() {
}
if *acceptCATerms {
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.Agreed = true
log.Print(fmt.Sprintf("Automatic agreement to CA terms with email (%s)", *acmeEmail))
}
@ -345,11 +345,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 {
@ -363,6 +363,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)
}

107
client.go
View File

@ -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()
@ -345,17 +331,27 @@ func (c *Client) BoreTunnel(ctx context.Context, tunnel Tunnel) error {
break
//continue
}
go c.handleConnection(conn, tunnel.ClientAddress, tunnel.ClientPort)
var useTls bool
if tunnel.TlsTermination == "client-tls" {
useTls = true
} else {
useTls = false
}
go ProxyTcp(conn, tunnel.ClientAddress, tunnel.ClientPort, useTls, c.certConfig)
}
}()
}
// TODO: There's still quite a bit of duplication with what the server does. Could we
// encapsulate it into a type?
err = c.certConfig.ManageSync(ctx, []string{tunnel.Domain})
if err != nil {
log.Println("CertMagic error at startup")
log.Println(err)
if tunnel.TlsTermination != "passthrough" {
// TODO: There's still quite a bit of duplication with what the server does. Could we
// encapsulate it into a type?
err = c.certConfig.ManageSync(ctx, []string{tunnel.Domain})
if err != nil {
log.Println("CertMagic error at startup")
log.Println(err)
}
}
<-ctx.Done()
@ -363,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))

View File

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

View File

@ -39,6 +39,7 @@
<option value="client">Client HTTPS</option>
<option value="server">Server HTTPS</option>
<option value="client-tls">Client raw TLS</option>
<option value="server-tls">Server raw TLS</option>
<option value="passthrough">Passthrough</option>
</select>
</div>
@ -58,6 +59,15 @@
</div>
</div>
<div class='input'>
<label for="ssh-server-addr">Override SSH Server Address:</label>
<input type="text" id="ssh-server-addr" name="ssh-server-addr">
</div>
<div class='input'>
<label for="ssh-server-port">Override SSH Server Port:</label>
<input type="text" id="ssh-server-port" name="ssh-server-port">
</div>
<button class='button' type="submit">Submit</button>
</form>

108
tls_proxy.go Normal file
View File

@ -0,0 +1,108 @@
package boringproxy
import (
//"errors"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"strings"
"sync"
"github.com/caddyserver/certmagic"
)
func ProxyTcp(conn net.Conn, addr string, port int, useTls bool, certConfig *certmagic.Config) error {
if useTls {
tlsConfig := &tls.Config{
GetCertificate: certConfig.GetCertificate,
}
tlsConfig.NextProtos = append([]string{"http/1.1", "h2", "acme-tls/1"}, tlsConfig.NextProtos...)
tlsConn := tls.Server(conn, tlsConfig)
tlsConn.Handshake()
if tlsConn.ConnectionState().NegotiatedProtocol == "acme-tls/1" {
tlsConn.Close()
return nil
}
go handleConnection(tlsConn, addr, port)
} else {
go handleConnection(conn, addr, port)
}
return nil
}
func 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()
}

View File

@ -35,7 +35,7 @@ func NewTunnelManager(config *Config, db *Database, certConfig *certmagic.Config
if config.autoCerts {
for domainName, tun := range db.GetTunnels() {
if tun.TlsTermination == "server" {
if tun.TlsTermination == "server" || tun.TlsTermination == "server-tls" {
err = certConfig.ManageSync(context.Background(), []string{domainName})
if err != nil {
log.Println("CertMagic error at startup")
@ -63,7 +63,7 @@ func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
return Tunnel{}, errors.New("Owner required")
}
if tunReq.TlsTermination == "server" {
if tunReq.TlsTermination == "server" || tunReq.TlsTermination == "server-tls" {
if m.config.autoCerts {
err := m.certConfig.ManageSync(context.Background(), []string{tunReq.Domain})
if err != nil {
@ -98,8 +98,6 @@ func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
return Tunnel{}, err
}
tunReq.ServerAddress = m.db.GetAdminDomain()
tunReq.ServerPort = m.config.SshServerPort
tunReq.ServerPublicKey = ""
tunReq.Username = m.user.Username
tunReq.TunnelPrivateKey = privKey