From 776aabf87904d40e156494537cb5e2827cca59bd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 6 May 2020 11:11:13 -0600 Subject: [PATCH] use DuckDNS for demo --- client/client.go | 11 ++--- client/ws_handler.go | 37 ++++++++++------ cmd/telebit/telebit.go | 93 ++++++++++++++++++++++++++++++++++++++-- relay/mplexy/listener.go | 1 + 4 files changed, 121 insertions(+), 21 deletions(-) diff --git a/client/client.go b/client/client.go index 501cb98..f7f0be1 100644 --- a/client/client.go +++ b/client/client.go @@ -13,10 +13,11 @@ import ( // The Config struct holds all of the information needed to establish and handle a connection // with the RVPN server. type Config struct { - Server string - Token string - Insecure bool - Services RouteMap + Server string + Token string + Insecure bool + Services RouteMap + TLSConfig *tls.Config } // Run establishes a connection with the RVPN server specified in the config. If the first attempt @@ -47,7 +48,7 @@ func Run(ctx context.Context, config *Config) error { return fmt.Errorf(`service %s missing port for "*"`, name) } } - handler := NewWsHandler(config.Services) + handler := NewWsHandler(config.Services, config.TLSConfig) authenticated := false for { diff --git a/client/ws_handler.go b/client/ws_handler.go index 5cdec9a..e613407 100644 --- a/client/ws_handler.go +++ b/client/ws_handler.go @@ -2,6 +2,7 @@ package client import ( "context" + "crypto/tls" "fmt" "io" "net" @@ -26,17 +27,19 @@ type WsHandler struct { servicePorts RouteMap - ctx context.Context - dataChan chan *packer.Packer + ctx context.Context + dataChan chan *packer.Packer + tlsConfig *tls.Config } // NewWsHandler creates a new handler ready to be given a websocket connection. The services // argument specifies what port each service type should be directed to on the local interface. -func NewWsHandler(services RouteMap) *WsHandler { - h := new(WsHandler) - h.servicePorts = services - h.localConns = make(map[string]net.Conn) - return h +func NewWsHandler(services RouteMap, tlsConfig *tls.Config) *WsHandler { + return &WsHandler{ + servicePorts: services, + localConns: make(map[string]net.Conn), + tlsConfig: tlsConfig, + } } // HandleConn handles all of the traffic on the provided websocket connection. The function @@ -81,7 +84,7 @@ func (h *WsHandler) HandleConn(ctx context.Context, conn *websocket.Conn) { } } -func (h *WsHandler) writeRemote(conn *websocket.Conn) { +func (h *WsHandler) writeRemote(wsconn *websocket.Conn) { defer h.closeConnections() defer func() { h.dataChan = nil }() @@ -94,13 +97,13 @@ func (h *WsHandler) writeRemote(conn *websocket.Conn) { // all errors if it doesn't work. message := websocket.FormatCloseMessage(websocket.CloseGoingAway, "closing connection") deadline := time.Now().Add(10 * time.Second) - conn.WriteControl(websocket.CloseMessage, message, deadline) - conn.Close() + wsconn.WriteControl(websocket.CloseMessage, message, deadline) + wsconn.Close() return case p := <-h.dataChan: packed := p.PackV1() - conn.WriteMessage(websocket.BinaryMessage, packed.Bytes()) + wsconn.WriteMessage(websocket.BinaryMessage, packed.Bytes()) } } } @@ -139,6 +142,7 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { } var hostname string + //var terminate bool if service == "http" { if match := hostRegexp.FindSubmatch(p.Data.Data()); match != nil { hostname = strings.Split(string(match[1]), ":")[0] @@ -146,6 +150,7 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { } } else if service == "https" { hostname, _ = sni.GetHostname(p.Data.Data()) + //terminate = true } else { hostname = "*" } @@ -176,7 +181,14 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { return nil } - h.localConns[key] = conn + rconn := conn + /* + if terminate { + rconn = tls.Server(conn, h.tlsConfig) + //rconn = tls.Client(conn, h.tlsConfig) + } + */ + h.localConns[key] = rconn loginfo.Printf("new client %q for %s:%d (%d clients)\n", key, hostname, term.Port, len(h.localConns)) go h.readLocal(key, &p.Header) return conn @@ -190,6 +202,7 @@ func (h *WsHandler) writeLocal(p *packer.Packer) { } if p.Service() == "error" || p.Service() == "end" { + // TODO XXX where's the opposite of this? conn.Close() return } diff --git a/cmd/telebit/telebit.go b/cmd/telebit/telebit.go index dd406b5..2a6e761 100644 --- a/cmd/telebit/telebit.go +++ b/cmd/telebit/telebit.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" "flag" "fmt" "log" @@ -12,7 +13,9 @@ import ( "git.coolaj86.com/coolaj86/go-telebitd/client" + "github.com/caddyserver/certmagic" jwt "github.com/dgrijalva/jwt-go" + "github.com/go-acme/lego/v3/providers/dns/duckdns" _ "github.com/joho/godotenv/autoload" ) @@ -264,17 +267,99 @@ func main() { ctx, quit := context.WithCancel(context.Background()) defer quit() + acmeStorage := "./acme.d/" + acmeEmail := "" + acmeStaging := false + // + // CertMagic is Greenlock for Go + // + directory := certmagic.LetsEncryptProductionCA + if acmeStaging { + directory = certmagic.LetsEncryptStagingCA + } + magic, err := newCertMagic(directory, acmeEmail, &certmagic.FileStorage{Path: acmeStorage}) + if nil != err { + fmt.Fprintf(os.Stderr, "failed to initialize certificate management (discovery url? local folder perms?): %s\n", err) + os.Exit(1) + } + + tlsConfig := &tls.Config{ + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return magic.GetCertificate(hello) + /* + if false { + _, _ = magic.GetCertificate(hello) + } + + // TODO + // 1. call out to greenlock for validation + // 2. push challenges through http channel + // 3. receive certificates (or don't) + certbundleT, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem") + certbundle := &certbundleT + if err != nil { + return nil, err + } + return certbundle, nil + */ + }, + } + config := client.Config{ - Insecure: insecure, - Server: relay, - Services: servicePorts, - Token: token, + Insecure: insecure, + Server: relay, + Services: servicePorts, + Token: token, + TLSConfig: tlsConfig, } fmt.Printf("config:\n%#v\n", config) log.Fatal(client.Run(ctx, &config)) } +func newCertMagic(directory string, email string, storage certmagic.Storage) (*certmagic.Config, error) { + cache := certmagic.NewCache(certmagic.CacheOptions{ + GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { + // do whatever you need to do to get the right + // configuration for this certificate; keep in + // mind that this config value is used as a + // template, and will be completed with any + // defaults that are set in the Default config + return &certmagic.Config{}, nil + }, + }) + provider, err := newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN")) + if err != nil { + return nil, err + } + magic := certmagic.New(cache, certmagic.Config{ + Storage: storage, + OnDemand: &certmagic.OnDemandConfig{ + DecisionFunc: func(name string) error { + return nil + }, + }, + }) + // Ummm... just a little confusing + magic.Issuer = certmagic.NewACMEManager(magic, certmagic.ACMEManager{ + DNSProvider: provider, + CA: directory, + Email: email, + Agreed: true, + DisableHTTPChallenge: true, + DisableTLSALPNChallenge: true, + // plus any other customizations you need + }) + return magic, nil +} + +// newDuckDNSProvider is for the sake of demoing the tunnel +func newDuckDNSProvider(token string) (*duckdns.DNSProvider, error) { + config := duckdns.NewDefaultConfig() + config.Token = token + return duckdns.NewDNSProviderConfig(config) +} + func stringSlice(csv string) []string { list := []string{} for _, item := range strings.Split(csv, ", ") { diff --git a/relay/mplexy/listener.go b/relay/mplexy/listener.go index d9072dd..1c3c756 100644 --- a/relay/mplexy/listener.go +++ b/relay/mplexy/listener.go @@ -332,6 +332,7 @@ func (mx *MPlexy) routeToTarget(ctx context.Context, extConn *tunnel.WedgeConn, } for { + fmt.Println("xxyyzz buffer") buffer, err := extConn.PeekAll() if err != nil { loginfo.Println("unable to peekAll", err)