2020-12-07 21:41:45 -07:00
package boringproxy
2020-09-30 23:49:03 -06:00
import (
2020-10-10 09:55:07 -06:00
"context"
2020-11-22 13:49:48 -07:00
"crypto/tls"
2020-10-02 20:36:28 -06:00
"encoding/json"
2020-10-11 13:45:46 -06:00
"errors"
2020-10-02 17:09:14 -06:00
"fmt"
2020-10-02 20:36:28 -06:00
"io"
2020-10-02 17:09:14 -06:00
"io/ioutil"
"log"
2020-10-02 20:36:28 -06:00
"net"
"net/http"
2020-11-22 13:49:48 -07:00
"strings"
2020-10-02 20:36:28 -06:00
"sync"
2020-10-10 09:55:07 -06:00
"time"
2021-01-05 22:04:08 +08:00
"github.com/caddyserver/certmagic"
"golang.org/x/crypto/ssh"
2020-09-30 23:49:03 -06:00
)
2020-12-07 21:50:33 -07:00
type Client struct {
2020-10-10 09:55:07 -06:00
httpClient * http . Client
tunnels map [ string ] Tunnel
previousEtag string
server string
token string
clientName string
2020-10-24 16:48:53 -06:00
user string
2020-10-10 09:55:07 -06:00
cancelFuncs map [ string ] context . CancelFunc
cancelFuncsMutex * sync . Mutex
2020-11-27 15:36:07 -07:00
certConfig * certmagic . Config
2020-09-30 23:49:03 -06:00
}
2020-12-10 23:14:34 -07:00
type ClientConfig struct {
2021-01-22 22:04:14 -07:00
ServerAddr string ` json:"serverAddr,omitempty" `
Token string ` json:"token,omitempty" `
ClientName string ` json:"clientName,omitempty" `
User string ` json:"user,omitempty" `
CertDir string ` json:"certDir,omitempty" `
AcmeEmail string ` json:"acmeEmail,omitempty" `
DnsServer string ` json:"dnsServer,omitempty" `
2020-12-10 23:14:34 -07:00
}
2021-01-23 11:16:50 -07:00
func NewClient ( config * ClientConfig ) ( * Client , error ) {
2020-10-09 10:05:31 -06:00
2020-12-10 23:14:34 -07:00
if config . DnsServer != "" {
2020-11-27 21:31:32 -07:00
net . DefaultResolver = & net . Resolver {
PreferGo : true ,
Dial : func ( ctx context . Context , network , address string ) ( net . Conn , error ) {
d := net . Dialer {
Timeout : time . Millisecond * time . Duration ( 10000 ) ,
}
2020-12-10 23:14:34 -07:00
return d . DialContext ( ctx , "udp" , fmt . Sprintf ( "%s:53" , config . DnsServer ) )
2020-11-27 21:31:32 -07:00
} ,
}
}
2020-11-28 11:48:23 -07:00
// Use random unprivileged port for ACME challenges. This is necessary
// because of the way certmagic works, in that if it fails to bind
// HTTPSPort (443 by default) and doesn't detect anything else binding
// it, it fails. Obviously the boringproxy client is likely to be
// running on a machine where 443 isn't bound, so we need a different
// port to hack around this. See here for more details:
// https://github.com/caddyserver/certmagic/issues/111
var err error
certmagic . HTTPSPort , err = randomOpenPort ( )
if err != nil {
2021-01-23 11:16:50 -07:00
return nil , errors . New ( "Failed get random port for TLS challenges" )
2020-11-28 11:48:23 -07:00
}
2020-11-27 15:36:07 -07:00
certmagic . DefaultACME . DisableHTTPChallenge = true
2020-11-27 21:20:38 -07:00
2020-12-10 23:14:34 -07:00
if config . CertDir != "" {
certmagic . Default . Storage = & certmagic . FileStorage { config . CertDir }
2020-11-27 21:20:38 -07:00
}
2020-11-27 22:17:07 -07:00
2020-12-10 23:14:34 -07:00
if config . AcmeEmail != "" {
certmagic . DefaultACME . Email = config . AcmeEmail
2020-11-27 22:17:07 -07:00
}
2020-11-27 15:36:07 -07:00
certConfig := certmagic . NewDefault ( )
2021-10-02 11:04:47 -06:00
httpClient := & http . Client {
// Don't follow redirects
CheckRedirect : func ( req * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
2020-10-10 09:55:07 -06:00
tunnels := make ( map [ string ] Tunnel )
cancelFuncs := make ( map [ string ] context . CancelFunc )
cancelFuncsMutex := & sync . Mutex { }
2020-12-07 21:50:33 -07:00
return & Client {
2020-10-10 09:55:07 -06:00
httpClient : httpClient ,
tunnels : tunnels ,
previousEtag : "" ,
2020-12-10 23:14:34 -07:00
server : config . ServerAddr ,
token : config . Token ,
clientName : config . ClientName ,
user : config . User ,
2020-10-10 09:55:07 -06:00
cancelFuncs : cancelFuncs ,
cancelFuncsMutex : cancelFuncsMutex ,
2020-11-27 15:36:07 -07:00
certConfig : certConfig ,
2021-01-23 11:16:50 -07:00
} , nil
2020-10-10 09:55:07 -06:00
}
2021-01-23 10:07:26 -07:00
func ( c * Client ) Run ( ctx context . Context ) error {
2020-10-09 10:05:31 -06:00
2020-10-24 16:48:53 -06:00
url := fmt . Sprintf ( "https://%s/api/users/%s/clients/%s" , c . server , c . user , c . clientName )
clientReq , err := http . NewRequest ( "PUT" , url , nil )
if err != nil {
2021-01-23 10:07:26 -07:00
return errors . New ( fmt . Sprintf ( "Failed to create request for URL %s" , url ) )
2020-10-24 16:48:53 -06:00
}
if len ( c . token ) > 0 {
clientReq . Header . Add ( "Authorization" , "bearer " + c . token )
}
resp , err := c . httpClient . Do ( clientReq )
if err != nil {
2021-01-23 10:07:26 -07:00
return errors . New ( fmt . Sprintf ( "Failed to create client. Ensure the server is running. URL: %s" , url ) )
2020-10-24 16:48:53 -06:00
}
defer resp . Body . Close ( )
if resp . StatusCode != 200 {
2021-01-08 14:18:51 -07:00
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2021-01-23 10:07:26 -07:00
return errors . New ( fmt . Sprintf ( "Failed to create client. HTTP Status code: %d. Failed to read body" , resp . StatusCode ) )
2021-01-08 14:18:51 -07:00
}
msg := string ( body )
2021-01-23 10:07:26 -07:00
return errors . New ( fmt . Sprintf ( "Failed to create client. Are the user (%s) and token correct? HTTP Status code: %d. Message: %s" , c . user , resp . StatusCode , msg ) )
2020-10-24 16:48:53 -06:00
}
2020-10-10 09:55:07 -06:00
for {
2021-01-23 11:25:29 -07:00
err := c . PollTunnels ( ctx )
2020-10-10 10:04:37 -06:00
if err != nil {
log . Print ( err )
}
2021-02-17 10:56:29 -07:00
select {
case <- ctx . Done ( ) :
return nil
case <- time . After ( 2 * time . Second ) :
// continue
}
2020-10-10 09:55:07 -06:00
}
}
2021-01-23 11:25:29 -07:00
func ( c * Client ) PollTunnels ( ctx context . Context ) error {
2020-10-27 15:21:56 -06:00
2020-10-28 12:59:40 -06:00
//log.Println("PollTunnels")
2020-10-27 15:21:56 -06:00
2020-10-10 09:55:07 -06:00
url := fmt . Sprintf ( "https://%s/api/tunnels?client-name=%s" , c . server , c . clientName )
2020-10-09 10:05:31 -06:00
listenReq , err := http . NewRequest ( "GET" , url , nil )
if err != nil {
2020-10-10 10:04:37 -06:00
return err
2020-10-09 10:05:31 -06:00
}
2020-10-10 09:55:07 -06:00
if len ( c . token ) > 0 {
listenReq . Header . Add ( "Authorization" , "bearer " + c . token )
2020-10-09 10:05:31 -06:00
}
2020-10-10 09:55:07 -06:00
resp , err := c . httpClient . Do ( listenReq )
2020-10-09 10:05:31 -06:00
if err != nil {
2020-10-10 10:04:37 -06:00
return err
2020-10-09 10:05:31 -06:00
}
defer resp . Body . Close ( )
if resp . StatusCode != 200 {
2020-10-10 10:04:37 -06:00
return errors . New ( "Failed to listen (not 200 status)" )
2020-10-09 10:05:31 -06:00
}
2020-10-10 09:55:07 -06:00
etag := resp . Header [ "Etag" ] [ 0 ]
2020-10-09 10:05:31 -06:00
2020-10-10 09:55:07 -06:00
if etag != c . previousEtag {
body , err := ioutil . ReadAll ( resp . Body )
2020-10-09 10:05:31 -06:00
2020-10-10 09:55:07 -06:00
tunnels := make ( map [ string ] Tunnel )
err = json . Unmarshal ( body , & tunnels )
if err != nil {
2020-10-10 10:04:37 -06:00
return err
2020-10-10 09:55:07 -06:00
}
2021-01-23 11:25:29 -07:00
c . SyncTunnels ( ctx , tunnels )
2020-10-10 09:55:07 -06:00
c . previousEtag = etag
2020-10-09 10:05:31 -06:00
}
2020-10-11 13:45:46 -06:00
return nil
2020-10-10 09:55:07 -06:00
}
2021-01-23 11:25:29 -07:00
func ( c * Client ) SyncTunnels ( ctx context . Context , serverTunnels map [ string ] Tunnel ) {
2020-10-27 15:21:56 -06:00
log . Println ( "SyncTunnels" )
2020-10-10 09:55:07 -06:00
// update tunnels to match server
for k , newTun := range serverTunnels {
2021-01-23 11:25:29 -07:00
// assume tunnels exists and hasn't changed
2021-01-23 11:16:50 -07:00
bore := false
2020-10-10 09:55:07 -06:00
tun , exists := c . tunnels [ k ]
if ! exists {
log . Println ( "New tunnel" , k )
c . tunnels [ k ] = newTun
2021-01-23 11:16:50 -07:00
bore = true
2020-10-10 09:55:07 -06:00
} else if newTun != tun {
log . Println ( "Restart tunnel" , k )
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Lock ( )
2020-10-10 09:55:07 -06:00
c . cancelFuncs [ k ] ( )
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Unlock ( )
2021-01-23 11:16:50 -07:00
bore = true
}
2020-10-24 17:00:42 -06:00
2021-01-23 11:16:50 -07:00
if bore {
2021-01-23 11:25:29 -07:00
cancelCtx , cancel := context . WithCancel ( ctx )
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Lock ( )
2020-10-10 09:55:07 -06:00
c . cancelFuncs [ k ] = cancel
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Unlock ( )
2021-01-23 11:16:50 -07:00
2021-01-23 11:25:29 -07:00
go func ( closureCtx context . Context , tun Tunnel ) {
err := c . BoreTunnel ( closureCtx , tun )
2021-01-23 11:16:50 -07:00
if err != nil {
log . Println ( "BoreTunnel error: " , err )
}
2021-01-23 11:25:29 -07:00
} ( cancelCtx , newTun )
2020-10-10 09:55:07 -06:00
}
}
2020-10-09 10:05:31 -06:00
2020-10-10 09:55:07 -06:00
// delete any tunnels that no longer exist on server
for k , _ := range c . tunnels {
_ , exists := serverTunnels [ k ]
if ! exists {
log . Println ( "Kill tunnel" , k )
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Lock ( )
2020-10-10 09:55:07 -06:00
c . cancelFuncs [ k ] ( )
2020-10-24 17:00:42 -06:00
c . cancelFuncsMutex . Unlock ( )
2021-01-23 11:16:50 -07:00
delete ( c . cancelFuncs , k )
2020-10-24 17:00:42 -06:00
delete ( c . tunnels , k )
2020-10-10 09:55:07 -06:00
}
2020-10-09 10:05:31 -06:00
}
}
2021-01-23 11:16:50 -07:00
func ( c * Client ) BoreTunnel ( ctx context . Context , tunnel Tunnel ) error {
2020-10-02 20:36:28 -06:00
2020-10-27 15:21:56 -06:00
log . Println ( "BoreTunnel" , tunnel . Domain )
2021-01-23 10:07:26 -07:00
signer , err := ssh . ParsePrivateKey ( [ ] byte ( tunnel . TunnelPrivateKey ) )
if err != nil {
2021-01-23 11:16:50 -07:00
return errors . New ( fmt . Sprintf ( "Unable to parse private key: %v" , err ) )
2021-01-23 10:07:26 -07:00
}
2020-09-30 23:49:03 -06:00
2021-01-23 10:07:26 -07:00
//var hostKey ssh.PublicKey
2020-09-30 23:49:03 -06:00
2021-01-23 10:07:26 -07:00
config := & ssh . ClientConfig {
User : tunnel . Username ,
Auth : [ ] ssh . AuthMethod {
ssh . PublicKeys ( signer ) ,
} ,
//HostKeyCallback: ssh.FixedHostKey(hostKey),
HostKeyCallback : ssh . InsecureIgnoreHostKey ( ) ,
}
2020-09-30 23:49:03 -06:00
2021-01-23 10:07:26 -07:00
sshHost := fmt . Sprintf ( "%s:%d" , tunnel . ServerAddress , tunnel . ServerPort )
client , err := ssh . Dial ( "tcp" , sshHost , config )
if err != nil {
2021-01-23 11:16:50 -07:00
return errors . New ( fmt . Sprintf ( "Failed to dial: " , err ) )
2021-01-23 10:07:26 -07:00
}
2021-01-23 11:16:50 -07:00
defer client . Close ( )
2020-10-02 20:36:28 -06:00
2021-01-23 10:07:26 -07:00
bindAddr := "127.0.0.1"
if tunnel . AllowExternalTcp {
bindAddr = "0.0.0.0"
}
tunnelAddr := fmt . Sprintf ( "%s:%d" , bindAddr , tunnel . TunnelPort )
listener , err := client . Listen ( "tcp" , tunnelAddr )
if err != nil {
2021-01-23 11:16:50 -07:00
return errors . New ( fmt . Sprintf ( "Unable to register tcp forward for %s:%d %v" , bindAddr , tunnel . TunnelPort , err ) )
2021-01-23 10:07:26 -07:00
}
2021-01-23 11:16:50 -07:00
defer listener . Close ( )
2020-10-24 17:00:42 -06:00
2021-01-23 10:07:26 -07:00
if tunnel . TlsTermination == "client" {
2020-11-27 15:36:07 -07:00
2021-01-23 10:07:26 -07:00
tlsConfig := & tls . Config {
GetCertificate : c . certConfig . GetCertificate ,
NextProtos : [ ] string { "h2" , "acme-tls/1" } ,
}
tlsListener := tls . NewListener ( listener , tlsConfig )
2020-11-27 15:36:07 -07:00
2021-01-23 10:07:26 -07:00
httpMux := http . NewServeMux ( )
2020-11-27 16:01:40 -07:00
2021-01-23 10:07:26 -07:00
httpMux . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
proxyRequest ( w , r , tunnel , c . httpClient , tunnel . ClientPort )
} )
2020-11-27 15:36:07 -07:00
2021-01-23 10:07:26 -07:00
httpServer := & http . Server {
Handler : httpMux ,
}
2020-11-27 16:01:40 -07:00
2021-01-23 10:07:26 -07:00
// TODO: It seems inefficient to make a separate HTTP server for each TLS-passthrough tunnel,
// but the code is much simpler. The only alternative I've thought of so far involves storing
// all the tunnels in a mutexed map and retrieving them from a single HTTP server, same as the
// boringproxy server does.
go httpServer . Serve ( tlsListener )
2020-11-27 21:20:38 -07:00
2021-01-23 10:07:26 -07:00
// 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 ( [ ] string { tunnel . Domain } )
if err != nil {
log . Println ( "CertMagic error at startup" )
log . Println ( err )
2020-11-27 15:36:07 -07:00
}
2020-10-17 16:07:56 -06:00
2021-01-23 10:07:26 -07:00
} else {
go func ( ) {
for {
conn , err := listener . Accept ( )
if err != nil {
// TODO: Currently assuming an error means the
// tunnel was manually deleted, but there
// could be other errors that we should be
// attempting to recover from rather than
// breaking.
break
//continue
}
go c . handleConnection ( conn , tunnel . ClientAddress , tunnel . ClientPort )
}
} ( )
}
2020-10-17 16:07:56 -06:00
2021-01-23 10:07:26 -07:00
<- ctx . Done ( )
2021-01-23 11:16:50 -07:00
return nil
2020-10-01 17:22:54 -06:00
}
2020-12-07 21:50:33 -07:00
func ( c * Client ) handleConnection ( conn net . Conn , upstreamAddr string , port int ) {
2020-10-01 17:22:54 -06:00
2020-10-02 20:36:28 -06:00
defer conn . Close ( )
2020-11-22 13:49:48 -07:00
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 ) )
}
2020-10-02 17:09:14 -06:00
if err != nil {
2020-10-02 20:36:28 -06:00
log . Print ( err )
return
2020-10-02 17:09:14 -06:00
}
2020-11-22 13:49:48 -07:00
2020-10-02 20:36:28 -06:00
defer upstreamConn . Close ( )
2020-10-01 17:22:54 -06:00
2020-10-02 20:36:28 -06:00
var wg sync . WaitGroup
wg . Add ( 2 )
2020-10-01 17:22:54 -06:00
2020-10-17 16:07:56 -06:00
// Copy request to upstream
2020-10-02 20:36:28 -06:00
go func ( ) {
2020-10-17 16:07:56 -06:00
_ , err := io . Copy ( upstreamConn , conn )
if err != nil {
log . Println ( err . Error ( ) )
}
2020-11-22 13:49:48 -07:00
if c , ok := upstreamConn . ( * net . TCPConn ) ; ok {
c . CloseWrite ( )
} else if c , ok := upstreamConn . ( * tls . Conn ) ; ok {
c . CloseWrite ( )
}
2020-10-02 20:36:28 -06:00
wg . Done ( )
} ( )
2020-10-17 16:07:56 -06:00
// Copy response to downstream
2020-10-02 20:36:28 -06:00
go func ( ) {
2020-10-17 16:07:56 -06:00
_ , 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 ( )
2020-10-02 20:36:28 -06:00
wg . Done ( )
} ( )
2020-09-30 23:49:03 -06:00
2020-10-02 20:36:28 -06:00
wg . Wait ( )
2020-09-30 23:49:03 -06:00
}
2021-01-23 11:16:50 -07:00
func printJson ( data interface { } ) {
d , _ := json . MarshalIndent ( data , "" , " " )
fmt . Println ( string ( d ) )
}