diff --git a/client/client.go b/client/client.go index 1d8453a..501cb98 100644 --- a/client/client.go +++ b/client/client.go @@ -16,7 +16,7 @@ type Config struct { Server string Token string Insecure bool - Services map[string]map[string]int + Services RouteMap } // Run establishes a connection with the RVPN server specified in the config. If the first attempt diff --git a/client/router.go b/client/router.go new file mode 100644 index 0000000..5ddd0b5 --- /dev/null +++ b/client/router.go @@ -0,0 +1,21 @@ +package client + +// SchemeName is an alias for string (for readability) +type SchemeName = string + +// DomainName is an alias for string (for readability) +type DomainName = string + +// TerminalConfig indicates destination +type TerminalConfig struct { + // The localhost port to which to forward + Port int + // Whether or not to unwap the TLS + TerminateTLS bool + //Hostname string + XForward bool + // ... create react app... +} + +// RouteMap is a map of scheme to domain to port +type RouteMap = map[SchemeName]map[DomainName]*TerminalConfig diff --git a/client/ws_handler.go b/client/ws_handler.go index 09f47cf..5cdec9a 100644 --- a/client/ws_handler.go +++ b/client/ws_handler.go @@ -24,7 +24,7 @@ type WsHandler struct { lock sync.Mutex localConns map[string]net.Conn - servicePorts map[string]map[string]int + servicePorts RouteMap ctx context.Context dataChan chan *packer.Packer @@ -32,7 +32,7 @@ type WsHandler struct { // 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 map[string]map[string]int) *WsHandler { +func NewWsHandler(services RouteMap) *WsHandler { h := new(WsHandler) h.servicePorts = services h.localConns = make(map[string]net.Conn) @@ -142,6 +142,7 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { if service == "http" { if match := hostRegexp.FindSubmatch(p.Data.Data()); match != nil { hostname = strings.Split(string(match[1]), ":")[0] + // TODO remove Hostname } } else if service == "https" { hostname, _ = sni.GetHostname(p.Data.Data()) @@ -154,23 +155,29 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { } hostname = strings.ToLower(hostname) - port := portList[hostname] - if port == 0 { - port = portList["*"] + term := portList[hostname] + fmt.Println("route to", hostname, term) + if term == nil { + portList[hostname] = portList["*"] + term = portList[hostname] } - if port == 0 { + if term.Port == 0 { + portList[hostname] = portList["*"] + } + if term.Port == 0 { loginfo.Println("unable to determine local port for", service, hostname) return nil } - conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + // TODO allow jumping + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", term.Port)) if err != nil { - loginfo.Println("unable to open local connection on port", port, err) + loginfo.Println("unable to open local connection on port", term.Port, err) return nil } h.localConns[key] = conn - loginfo.Printf("new client %q for %s:%d (%d clients)\n", key, hostname, port, len(h.localConns)) + 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 } diff --git a/cmd/telebit-relay/telebit-relay.go b/cmd/telebit-relay/telebit-relay.go index ebfd373..c9574b3 100644 --- a/cmd/telebit-relay/telebit-relay.go +++ b/cmd/telebit-relay/telebit-relay.go @@ -220,17 +220,21 @@ func main() { tokenString = r.URL.Query().Get("access_token") } - _, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + tok, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(secretKey), nil }) if nil != err { + fmt.Println("return an error, do not go on") return nil, err } + fmt.Printf("client claims:\n%+v\n", tok.Claims) + domains := []string{} + for _, name := range tok.Claims.(jwt.MapClaims)["domains"].([]interface{}) { + domains = append(domains, name.(string)) + } authz := &mplexy.Authz{ - Domains: []string{ - "target.rootprojects.org", - }, + Domains: domains, } return authz, err diff --git a/cmd/telebit/telebit.go b/cmd/telebit/telebit.go index 53ebd73..dd406b5 100644 --- a/cmd/telebit/telebit.go +++ b/cmd/telebit/telebit.go @@ -2,36 +2,39 @@ package main import ( "context" + "flag" "fmt" + "log" + "os" "regexp" "strconv" "strings" - jwt "github.com/dgrijalva/jwt-go" - flag "github.com/spf13/pflag" - "github.com/spf13/viper" - "git.coolaj86.com/coolaj86/go-telebitd/client" + + jwt "github.com/dgrijalva/jwt-go" + + _ "github.com/joho/godotenv/autoload" ) var httpRegexp = regexp.MustCompile(`(?i)^http`) +var locals string +var domains string +var insecure bool +var relay string +var secret string +var token string func init() { - flag.StringSlice("locals", []string{}, "comma separated list of : or "+ + flag.StringVar(&locals, "locals", "", "comma separated list of : or "+ ":: to which matching incoming connections should forward. "+ "Ex: smtps:8465,https:example.com:8443") - flag.StringSlice("domains", []string{}, "comma separated list of domain names to set to the tunnel") - viper.BindPFlag("locals", flag.Lookup("locals")) - viper.BindPFlag("domains", flag.Lookup("domains")) - - flag.BoolP("insecure", "k", false, "Allow TLS connections to telebit-relay without valid certs") - flag.String("relay", "", "the domain (or ip address) at which the RVPN server is running") - flag.String("secret", "", "the same secret used by telebit-relay (used for JWT authentication)") - flag.String("token", "", "a pre-generated token to give the server (instead of generating one with --secret)") - viper.BindPFlag("raw.insecure", flag.Lookup("insecure")) - viper.BindPFlag("raw.relay", flag.Lookup("relay")) - viper.BindPFlag("raw.secret", flag.Lookup("secret")) - viper.BindPFlag("raw.token", flag.Lookup("token")) + flag.StringVar(&domains, "domains", "", "comma separated list of domain names to set to the tunnel") + flag.BoolVar(&insecure, "insecure", false, "Allow TLS connections to telebit-relay without valid certs") + flag.BoolVar(&insecure, "k", false, "alias of --insecure") + flag.StringVar(&relay, "relay", "", "the domain (or ip address) at which the relay server is running") + flag.StringVar(&secret, "secret", "", "the same secret used by telebit-relay (used for JWT authentication)") + flag.StringVar(&token, "token", "", "a pre-generated token to give the server (instead of generating one with --secret)") } type proxy struct { @@ -139,14 +142,14 @@ func addDomains(proxies []proxy, location string) ([]proxy, error) { return proxies, nil } -func extractServicePorts(proxies []proxy) map[string]map[string]int { - result := make(map[string]map[string]int, 2) +func extractServicePorts(proxies []proxy) client.RouteMap { + result := make(client.RouteMap, 2) for _, p := range proxies { if p.protocol != "" && p.port != 0 { hostPorts := result[p.protocol] if hostPorts == nil { - result[p.protocol] = make(map[string]int) + result[p.protocol] = make(map[client.DomainName]*client.TerminalConfig) hostPorts = result[p.protocol] } @@ -155,25 +158,33 @@ func extractServicePorts(proxies []proxy) map[string]map[string]int { if !httpRegexp.MatchString(p.protocol) || p.hostname == "" { p.hostname = "*" } - if port, ok := hostPorts[p.hostname]; ok && port != p.port { + if port, ok := hostPorts[p.hostname]; ok && port.Port != p.port { panic(fmt.Sprintf("duplicate ports for %s://%s", p.protocol, p.hostname)) } - hostPorts[p.hostname] = p.port + hostPorts[p.hostname] = &client.TerminalConfig{ + Port: p.port, + } } } // Make sure we have defaults for HTTPS and HTTP. if result["https"] == nil { - result["https"] = make(map[string]int, 1) + result["https"] = make(map[client.DomainName]*client.TerminalConfig, 1) } - if result["https"]["*"] == 0 { - result["https"]["*"] = 8443 + if result["https"]["*"] == nil { + result["https"]["*"] = &client.TerminalConfig{} + } + if result["https"]["*"].Port == 0 { + result["https"]["*"].Port = 8443 } if result["http"] == nil { - result["http"] = make(map[string]int, 1) + result["http"] = make(map[client.DomainName]*client.TerminalConfig, 1) } - if result["http"]["*"] == 0 { + if result["http"]["*"] == nil { + result["http"]["*"] = &client.TerminalConfig{} + } + if result["http"]["*"].Port == 0 { result["http"]["*"] = result["https"]["*"] } @@ -184,8 +195,13 @@ func main() { flag.Parse() var err error + + if "" == locals { + locals = os.Getenv("LOCALS") + } + proxies := make([]proxy, 0) - for _, option := range viper.GetStringSlice("locals") { + for _, option := range stringSlice(locals) { for _, location := range strings.Split(option, ",") { //fmt.Println("locals", location) proxies, err = addLocals(proxies, location) @@ -194,9 +210,10 @@ func main() { } } } + //fmt.Println("proxies:") //fmt.Printf("%+v\n\n", proxies) - for _, option := range viper.GetStringSlice("domains") { + for _, option := range stringSlice(domains) { for _, location := range strings.Split(option, ",") { proxies, err = addDomains(proxies, location) if nil != err { @@ -213,39 +230,58 @@ func main() { } } - if viper.GetString("raw.relay") == "" { - panic("must provide remote RVPN server to connect to") + if relay == "" { + relay = os.Getenv("RELAY") + } + if relay == "" { + fmt.Fprintf(os.Stderr, "must provide remote relay server to connect to\n") + os.Exit(1) } - var token string - if viper.GetString("raw.token") != "" { - token = viper.GetString("raw.token") - } else if viper.GetString("raw.secret") != "" { + if secret == "" { + secret = os.Getenv("SECRET") + } + + if secret != "" { domains := make([]string, 0, len(domainMap)) for name := range domainMap { domains = append(domains, name) } tokenData := jwt.MapClaims{"domains": domains} - secret := []byte(viper.GetString("raw.secret")) + secret := []byte(secret) jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenData) if tokenStr, err := jwtToken.SignedString(secret); err != nil { panic(err) } else { token = tokenStr } - } else { - panic("must provide either token or secret") + } else if token != "" { + fmt.Fprintf(os.Stderr, "must provide either token or secret\n") + os.Exit(1) } ctx, quit := context.WithCancel(context.Background()) defer quit() config := client.Config{ - Insecure: viper.GetBool("raw.insecure"), - Server: viper.GetString("raw.relay"), + Insecure: insecure, + Server: relay, Services: servicePorts, Token: token, } - panic(client.Run(ctx, &config)) + + fmt.Printf("config:\n%#v\n", config) + log.Fatal(client.Run(ctx, &config)) +} + +func stringSlice(csv string) []string { + list := []string{} + for _, item := range strings.Split(csv, ", ") { + if 0 == len(item) { + continue + } + list = append(list, item) + } + return list } diff --git a/examples/client.env b/examples/client.env new file mode 100644 index 0000000..a71318b --- /dev/null +++ b/examples/client.env @@ -0,0 +1,2 @@ +SECRET=xxxxyyyyssss8347 +RELAY=wss://example.com:8443 diff --git a/examples/example.env b/examples/relay.env similarity index 100% rename from examples/example.env rename to examples/relay.env diff --git a/relay/mplexy/listener.go b/relay/mplexy/listener.go index d1f297c..d9072dd 100644 --- a/relay/mplexy/listener.go +++ b/relay/mplexy/listener.go @@ -259,17 +259,18 @@ func (mx *MPlexy) acceptPlainStream(ctx context.Context, wConn *tunnel.WedgeConn } - if hostname == mx.adminHostName { - loginfo.Println("admin") - // TODO mx.Admin.CheckRemoteIP(conn) here - // handle admin path - mx.AcceptAdminClient(wConn) + if hostname == mx.wssHostName && + ("Upgrade" == r.Header.Get("Connection") || "WebSocket" == r.Header.Get("Upgrade")) { + loginfo.Println("WebSocket Upgrade is in order...") + mx.AcceptTargetServer(wConn) return } - if "Upgrade" == r.Header.Get("Connection") || "WebSocket" == r.Header.Get("Upgrade") { - loginfo.Println("WebSocket Upgrade is in order...") - mx.AcceptTargetServer(wConn) + if hostname == mx.adminHostName { + loginfo.Println("matched admin hostname") + // TODO mx.Admin.CheckRemoteIP(conn) here + // handle admin path + mx.AcceptAdminClient(wConn) return }