├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── cmd.go ├── daemon.go ├── daemon_unix.go ├── daemon_windows.go ├── ping.go ├── ps.go ├── rm.go ├── root.go ├── tunnel │ └── main.go └── version.go ├── daemon ├── connection.go └── server.go ├── go.mod ├── go.sum └── tunnel-client.iml /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .idea 3 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: tunnel 2 | before: 3 | hooks: 4 | - go mod download 5 | builds: 6 | - main: cmd/tunnel/main.go 7 | goos: 8 | - darwin 9 | - linux 10 | - windows 11 | goarch: 12 | - 386 13 | - amd64 14 | - arm 15 | - arm64 16 | goarm: 17 | - 6 18 | - 7 19 | archives: 20 | - replacements: 21 | darwin: macos 22 | amd64: 64-bit 23 | 386: 32-bit 24 | format_overrides: 25 | - goos: windows 26 | format: zip 27 | nfpms: 28 | - formats: 29 | - deb 30 | - rpm 31 | replacements: 32 | amd64: 64-bit 33 | 386: 32-bit 34 | vendor: "LabStack" 35 | homepage: "https://tunnel.labstack.com" 36 | maintainer: "Vishal Rana " 37 | description: "Tunnel lets you expose local servers to the internet securely - ngrok alternative, compatible with SSH 38 | client." 39 | license: "MIT" 40 | checksum: 41 | name_template: "checksum.txt" 42 | snapshot: 43 | name_template: "{{ .Tag }}-next" 44 | changelog: 45 | sort: asc 46 | filters: 47 | exclude: 48 | - "^docs:" 49 | - "^test:" 50 | release: 51 | draft: true 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.10 2 | 3 | RUN apk add --no-cache ca-certificates 4 | 5 | COPY dist/tunnel_linux_amd64/tunnel /usr/local/bin/tunnel 6 | 7 | ENTRYPOINT ["tunnel"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 LabStack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE = labstack/tunnel 2 | VERSION = 0.5.15 3 | 4 | publish: 5 | git tag v$(VERSION) 6 | git push origin --tags 7 | goreleaser --rm-dist 8 | docker build -t $(IMAGE):$(VERSION) -t $(IMAGE) . 9 | docker push $(IMAGE):$(VERSION) 10 | docker push $(IMAGE):latest 11 | 12 | .PHONY: publish 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Tunnel 4 | 5 | Tunnel lets you expose local servers to the internet securely - ngrok alternative, compatible with SSH client. 6 | 7 | ## [Docs](https://tunnel.labstack.com/docs) | [Forum](https://forum.labstack.com) 8 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/briandowns/spinner" 6 | "github.com/spf13/viper" 7 | "io/ioutil" 8 | "net/rpc" 9 | "os" 10 | "time" 11 | ) 12 | 13 | var ( 14 | s = spinner.New(spinner.CharSets[32], 50*time.Millisecond) 15 | ) 16 | 17 | func exit(i interface{}) { 18 | fmt.Println(i) 19 | os.Exit(1) 20 | } 21 | 22 | func getClient() (*rpc.Client, error) { 23 | addr, _ := ioutil.ReadFile(viper.GetString("daemon_addr")) 24 | return rpc.Dial("tcp", string(addr)) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/daemon.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "github.com/labstack/tunnel-client/daemon" 6 | "github.com/radovskyb/watcher" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | func startDaemon() { 17 | if viper.GetString("api_key") == "" { 18 | exit("To use tunnel you need an api key (https://tunnel.labstack.com) in $HOME/.tunnel/config.yaml") 19 | } 20 | start := true 21 | d, err := ioutil.ReadFile(viper.GetString("daemon_pid")) 22 | if err == nil { 23 | pid, _ := strconv.Atoi(string(d)) 24 | if p, _ := os.FindProcess(pid); p != nil { 25 | start = false 26 | } 27 | } 28 | if start { 29 | exe, err := os.Executable() 30 | if err != nil { 31 | exit(err) 32 | } 33 | c := exec.Command(exe, "daemon", "start") 34 | c.SysProcAttr = sysProcAttr 35 | f, err := os.OpenFile(viper.GetString("log_file"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 36 | if err != nil { 37 | exit(err) 38 | } 39 | c.Stdout = f 40 | c.Stderr = f 41 | if err := c.Start(); err != nil { 42 | exit(err) 43 | } 44 | if err := ioutil.WriteFile(viper.GetString("daemon_pid"), []byte(strconv.Itoa(c.Process.Pid)), 0644); err != nil { 45 | exit(err) 46 | } 47 | 48 | // Wait for daemon to start 49 | w := watcher.New() 50 | w.SetMaxEvents(1) 51 | w.FilterOps(watcher.Create) 52 | go func() { 53 | e := <-w.Event 54 | if e.Name() == "daemon.addr" { 55 | w.Close() 56 | } 57 | }() 58 | w.Add(viper.GetString("root")) 59 | w.Start(50 * time.Millisecond) 60 | } 61 | } 62 | 63 | var daemonCmd = &cobra.Command{ 64 | Use: "daemon", 65 | Short: "Start/stop the tunnel daemon. It is automatically started as soon as the first command is executed.", 66 | Args: func(cmd *cobra.Command, args []string) error { 67 | if len(args) < 1 { 68 | return errors.New("requires an argument (start/stop)") 69 | } 70 | return nil 71 | }, 72 | Run: func(cmd *cobra.Command, args []string) { 73 | if args[0] == "start" { 74 | daemon.Start() 75 | } else if args[0] == "stop" { 76 | defer os.Remove(viper.GetString("daemon_addr")) 77 | defer os.Remove(viper.GetString("daemon_pid")) 78 | d, _ := ioutil.ReadFile(viper.GetString("daemon_pid")) 79 | pid, _ := strconv.Atoi(string(d)) 80 | p, _ := os.FindProcess(pid) 81 | p.Signal(os.Interrupt) 82 | } 83 | }, 84 | } 85 | 86 | func init() { 87 | rootCmd.AddCommand(daemonCmd) 88 | } 89 | -------------------------------------------------------------------------------- /cmd/daemon_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin linux 2 | 3 | package cmd 4 | 5 | import "syscall" 6 | 7 | var ( 8 | sysProcAttr = &syscall.SysProcAttr{ 9 | Setpgid: true, 10 | Pgid: 0, 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /cmd/daemon_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | package cmd 3 | 4 | import "syscall" 5 | 6 | var ( 7 | sysProcAttr = &syscall.SysProcAttr{ 8 | CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/ping.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | var pingCmd = &cobra.Command{ 12 | Use: "ping", 13 | Short: "Ping remote host", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | host := net.JoinHostPort(viper.GetString("hostname"), "22") 16 | conn, err := net.DialTimeout("tcp", host, 5*time.Second) 17 | if err != nil { 18 | exit(err) 19 | } 20 | defer conn.Close() 21 | }, 22 | } 23 | 24 | func init() { 25 | rootCmd.AddCommand(pingCmd) 26 | } 27 | -------------------------------------------------------------------------------- /cmd/ps.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/hako/durafmt" 5 | "github.com/jedib0t/go-pretty/table" 6 | "github.com/labstack/tunnel-client/daemon" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "time" 10 | ) 11 | 12 | var psCmd = &cobra.Command{ 13 | Use: "ps", 14 | Short: "List all the connections", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | psRPC() 17 | }, 18 | } 19 | 20 | func psRPC() { 21 | s.Start() 22 | startDaemon() 23 | c, err := getClient() 24 | if err != nil { 25 | exit(err) 26 | } 27 | defer c.Close() 28 | req := new(daemon.PSRequest) 29 | rep := new(daemon.PSReply) 30 | err = c.Call("Server.PS", req, rep) 31 | if err != nil { 32 | exit(err) 33 | } 34 | s.Stop() 35 | tbl := table.NewWriter() 36 | tbl.SetOutputMirror(os.Stdout) 37 | tbl.AppendHeader(table.Row{"Name", "Target Address", "Remote URI", "Status", "Uptime"}) 38 | for _, c := range rep.Connections { 39 | uptime := "-" 40 | since := time.Since(c.ConnectedAt) 41 | if c.Status == daemon.ConnectionStatusStatusOnline && since > 0 { 42 | uptime = durafmt.ParseShort(since).String() 43 | } 44 | tbl.AppendRow([]interface{}{c.Name, c.TargetAddress, c.RemoteURI, c.Status, uptime}) 45 | } 46 | tbl.Render() 47 | } 48 | 49 | func init() { 50 | rootCmd.AddCommand(psCmd) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/rm.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "github.com/labstack/tunnel-client/daemon" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var force bool 11 | var rmCmd = &cobra.Command{ 12 | Use: "rm [name]", 13 | Short: "Remove connection by name", 14 | Args: func(cmd *cobra.Command, args []string) error { 15 | if len(args) < 1 { 16 | return errors.New("requires a connection name") 17 | } 18 | return nil 19 | }, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | s.Start() 22 | startDaemon() 23 | c, err := getClient() 24 | if err != nil { 25 | exit(err) 26 | } 27 | defer c.Close() 28 | rep := new(daemon.RMReply) 29 | defer s.Stop() 30 | err = c.Call("Server.RM", daemon.RMRequest{ 31 | Name: args[0], 32 | Force: force, 33 | }, rep) 34 | if err != nil { 35 | exit(err) 36 | } 37 | psRPC() 38 | }, 39 | } 40 | 41 | func init() { 42 | rootCmd.AddCommand(rmCmd) 43 | } 44 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/labstack/tunnel-client/daemon" 12 | "github.com/spf13/cobra" 13 | 14 | "github.com/mitchellh/go-homedir" 15 | "github.com/spf13/viper" 16 | ) 17 | 18 | var configuration string 19 | var protocol string 20 | var rootCmd = &cobra.Command{ 21 | Use: "tunnel [address]", 22 | Short: "Tunnel lets you expose local servers to the internet securely", 23 | Long: "Signup @ https://tunnel.labstack.com to get an api key and get started", 24 | Args: func(cmd *cobra.Command, args []string) error { 25 | if len(args) < 1 { 26 | return errors.New("requires a target address") 27 | } 28 | return nil 29 | }, 30 | Run: func(cmd *cobra.Command, args []string) { 31 | s.Start() 32 | startDaemon() 33 | c, err := getClient() 34 | if err != nil { 35 | exit(err) 36 | } 37 | defer c.Close() 38 | rep := new(daemon.ConnectReply) 39 | addr := args[0] 40 | _, _, err = net.SplitHostPort(addr) 41 | if err != nil && strings.Contains(err.Error(), "missing port") { 42 | addr = ":" + addr 43 | } 44 | err = c.Call("Server.Connect", &daemon.ConnectRequest{ 45 | Configuration: configuration, 46 | Address: addr, 47 | Protocol: daemon.Protocol(protocol), 48 | }, rep) 49 | if err != nil { 50 | exit(err) 51 | } 52 | s.Stop() 53 | psRPC() 54 | }, 55 | } 56 | 57 | func Execute() { 58 | if err := rootCmd.Execute(); err != nil { 59 | exit(err) 60 | } 61 | } 62 | 63 | func init() { 64 | cobra.OnInitialize(initialize) 65 | } 66 | 67 | func initialize() { 68 | // Create directories 69 | dir, err := homedir.Dir() 70 | if err != nil { 71 | fmt.Printf("failed to find the home directory: %v", err) 72 | } 73 | root := filepath.Join(dir, ".tunnel") 74 | if err = os.MkdirAll(root, 0755); err != nil { 75 | fmt.Printf("failed to create root directory: %v", err) 76 | } 77 | if _, err := os.OpenFile(filepath.Join(root, "config.yaml"), os.O_RDONLY|os.O_CREATE, 0644); err != nil { 78 | fmt.Printf("failed to create config file: %v", err) 79 | } 80 | 81 | // Config 82 | viper.AutomaticEnv() 83 | viper.Set("root", root) 84 | viper.Set("log_file", filepath.Join(root, "daemon.log")) 85 | viper.Set("daemon_pid", filepath.Join(root, "daemon.pid")) 86 | viper.Set("daemon_addr", filepath.Join(root, "daemon.addr")) 87 | viper.Set("hostname", "labstack.me") 88 | viper.Set("port", 22) 89 | viper.Set("remote_port", 80) 90 | viper.Set("api_url", "https://tunnel.labstack.com/api/v1") 91 | if dev := viper.GetString("DC") == "dev"; dev { 92 | viper.Set("hostname", "labstack.d") 93 | viper.Set("port", 2200) 94 | viper.Set("remote_port", 8000) 95 | viper.Set("api_url", "http://tunnel.labstack.d/api/v1") 96 | viper.SetConfigName("config.dev") 97 | } else { 98 | viper.SetConfigName("config") 99 | } 100 | viper.AddConfigPath(root) 101 | viper.ReadInConfig() 102 | viper.WatchConfig() 103 | } 104 | 105 | func init() { 106 | rootCmd.PersistentFlags().StringVarP(&configuration, "configuration", "c", "", 107 | "configuration name from the console") 108 | rootCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", string(daemon.ProtocolHTTP), 109 | "connection protocol (http, tcp, tls)") 110 | } 111 | -------------------------------------------------------------------------------- /cmd/tunnel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/labstack/tunnel-client/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | version = "v0.5.15" 11 | ) 12 | 13 | var versionCmd = &cobra.Command{ 14 | Use: "version", 15 | Short: "Show the version information of Tunnel", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | fmt.Println(version) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(versionCmd) 23 | } 24 | -------------------------------------------------------------------------------- /daemon/connection.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "time" 13 | 14 | "github.com/labstack/gommon/log" 15 | gonanoid "github.com/matoous/go-nanoid" 16 | "github.com/spf13/viper" 17 | 18 | "golang.org/x/crypto/ssh" 19 | ) 20 | 21 | type ( 22 | User struct { 23 | ID string `json:"id"` 24 | Protocol Protocol `json:"protocol"` 25 | Target string `json:"target"` 26 | Configuration string `json:"configuration"` 27 | Key string `json:"key"` 28 | } 29 | 30 | Configuration struct { 31 | Name string `json:"name"` 32 | Protocol Protocol `json:"protocol"` 33 | Prefix string `json:"prefix"` 34 | Hostname string `json:"hostname"` 35 | Domain string `json:"domain"` 36 | Port int `json:"port"` 37 | } 38 | 39 | Connection struct { 40 | server *Server 41 | startChan chan error 42 | acceptChan chan net.Conn 43 | reconnectChan chan error 44 | stopChan chan bool 45 | started bool 46 | stopped bool 47 | retries time.Duration 48 | user *User 49 | ID string `json:"id"` 50 | Name string `json:"name"` 51 | Random bool `json:"random"` 52 | Hostname string `json:"hostname"` 53 | Port int `json:"port"` 54 | TargetAddress string `json:"target_address"` 55 | RemotePort string 56 | RemoteURI string `json:"remote_uri"` 57 | Status ConnectionStatus `json:"status"` 58 | ConnectedAt time.Time `json:"connected_at"` 59 | Configuration *Configuration `json:"-"` 60 | } 61 | 62 | ConnectionStatus string 63 | 64 | Error struct { 65 | Code int `json:"code"` 66 | Message string `json:"message"` 67 | } 68 | ) 69 | 70 | var ( 71 | hostBytes = []byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDoSLknvlFrFzroOlh1cqvcIFelHO+Wvj1UZ/p3J9bgsJGiKfh3DmBqEw1DOEwpHJz4zuV375TyjGuHuGZ4I4xztnwauhFplfEvriVHQkIDs6UnGwJVr15XUQX04r0i6mLbJs5KqIZTZuZ9ZGOj7ZWnaA7C07nPHGrERKV2Fm67rPvT6/qFikdWUbCt7KshbzdwwfxUohmv+NI7vw2X6vPU8pDaNEY7vS3YgwD/WlvQx+WDF2+iwLVW8OWWjFuQso6Eg1BSLygfPNhAHoiOWjDkijc8U9LYkUn7qsDCnvJxCoTTNmdECukeHfzrUjTSw72KZoM5KCRV78Wrctai1Qn6yRQz9BOSguxewLfzHtnT43/MLdwFXirJ/Ajquve2NAtYmyGCq5HcvpDAyi7lQ0nFBnrWv5zU3YxrISIpjovVyJjfPx8SCRlYZwVeUq6N2yAxCzJxbElZPtaTSoXBIFtoas2NXnCWPgenBa/2bbLQqfgbN8VQ9RaUISKNuYDIn4+eO72+RxF9THzZeV17pnhTVK88XU4asHot1gXwAt4vEhSjdUBC9KUIkfukI6F4JFxtvuO96octRahdV1Qg0vF+D0+SPy2HxqjgZWgPE2Xh/NmuIXwbE0wkymR2wrgj8Hd4C92keo2NBRh9dD7D2negnVYaYsC+3k/si5HNuCHnHQ== tunnel@labstack.com") 72 | 73 | ConnectionStatusStatusOnline = ConnectionStatus("online") 74 | ConnectionStatusReconnecting = ConnectionStatus("reconnecting") 75 | ) 76 | 77 | func (c *Connection) Host() (host string) { 78 | h := viper.GetString("hostname") 79 | if c.Configuration.Hostname != "" { 80 | h = c.Configuration.Hostname 81 | } else if c.Hostname != "" { 82 | h = c.Hostname 83 | } 84 | return net.JoinHostPort(h, viper.GetString("port")) 85 | } 86 | 87 | func (s *Server) newConnection(req *ConnectRequest) (c *Connection, err error) { 88 | id, _ := gonanoid.Nanoid() 89 | c = &Connection{ 90 | server: s, 91 | startChan: make(chan error), 92 | acceptChan: make(chan net.Conn), 93 | reconnectChan: make(chan error), 94 | stopChan: make(chan bool), 95 | ID: id, 96 | TargetAddress: req.Address, 97 | Configuration: new(Configuration), 98 | } 99 | 100 | // Lookup config 101 | if req.Configuration != "" { 102 | e := new(Error) 103 | res, err := s.resty.R(). 104 | SetResult(c.Configuration). 105 | SetError(e). 106 | Get("/configurations/" + req.Configuration) 107 | if err != nil { 108 | return nil, fmt.Errorf("failed to the find the configuration: %v", err) 109 | } else if res.IsError() { 110 | return nil, fmt.Errorf("failed to the find the configuration: %s", e.Message) 111 | } 112 | c.Name = req.Configuration 113 | req.Protocol = c.Configuration.Protocol 114 | } 115 | c.RemotePort = viper.GetString("remote_port") 116 | if req.Protocol != ProtocolHTTP { 117 | c.RemotePort = "0" 118 | } 119 | 120 | c.user = &User{ 121 | ID: id, 122 | Protocol: req.Protocol, 123 | Target: req.Address, 124 | Configuration: req.Configuration, 125 | Key: viper.GetString("api_key"), 126 | } 127 | return 128 | } 129 | 130 | func (c *Connection) connect() { 131 | RECONNECT: 132 | if c.Status == ConnectionStatusReconnecting { 133 | c.retries++ 134 | if c.retries > 5 { 135 | log.Errorf("failed to reconnect connection: %s", c.Name) 136 | return 137 | } 138 | time.Sleep(c.retries * c.retries * time.Second) 139 | log.Warnf("reconnecting connection: name=%s, retry=%d", c.Name, c.retries) 140 | } 141 | hostKey, _, _, _, err := ssh.ParseAuthorizedKey(hostBytes) 142 | if err != nil { 143 | c.startChan <- fmt.Errorf("failed to parse host key: %v", err) 144 | return 145 | } 146 | user, _ := json.Marshal(c.user) 147 | config := &ssh.ClientConfig{ 148 | User: string(user), 149 | Auth: []ssh.AuthMethod{ 150 | ssh.Password("password"), 151 | }, 152 | HostKeyCallback: ssh.FixedHostKey(hostKey), 153 | } 154 | 155 | // Connect 156 | sc := new(ssh.Client) 157 | proxy := os.Getenv("http_proxy") 158 | if proxy != "" { 159 | proxyURL, err := url.Parse(proxy) 160 | if err != nil { 161 | c.startChan <- fmt.Errorf("cannot open new session: %v", err) 162 | return 163 | } 164 | tcp, err := net.Dial("tcp", proxyURL.Hostname()) 165 | if err != nil { 166 | c.startChan <- fmt.Errorf("cannot open new session: %v", err) 167 | return 168 | } 169 | connReq := &http.Request{ 170 | Method: "CONNECT", 171 | URL: &url.URL{Path: c.Host()}, 172 | Host: c.Host(), 173 | Header: make(http.Header), 174 | } 175 | if proxyURL.User != nil { 176 | if p, ok := proxyURL.User.Password(); ok { 177 | connReq.SetBasicAuth(proxyURL.User.Username(), p) 178 | } 179 | } 180 | connReq.Write(tcp) 181 | resp, err := http.ReadResponse(bufio.NewReader(tcp), connReq) 182 | if err != nil { 183 | c.startChan <- fmt.Errorf("cannot open new session: %v", err) 184 | return 185 | } 186 | defer resp.Body.Close() 187 | 188 | conn, chans, reqs, err := ssh.NewClientConn(tcp, c.Host(), config) 189 | if err != nil { 190 | c.startChan <- fmt.Errorf("cannot open new session: %v", err) 191 | return 192 | } 193 | sc = ssh.NewClient(conn, chans, reqs) 194 | } else { 195 | sc, err = ssh.Dial("tcp", c.Host(), config) 196 | } 197 | if err != nil { 198 | log.Error(err) 199 | c.Status = ConnectionStatusReconnecting 200 | goto RECONNECT 201 | } 202 | 203 | // Close 204 | defer func() { 205 | log.Infof("closing connection: %s", c.Name) 206 | delete(connections, c.ID) 207 | defer sc.Close() 208 | }() 209 | 210 | // Remote listener 211 | l, err := sc.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", c.RemotePort)) 212 | if err != nil { 213 | c.startChan <- fmt.Errorf("failed to listen on remote host: %v", err) 214 | return 215 | } 216 | 217 | if err = c.server.findConnection(c); err != nil { 218 | c.startChan <- fmt.Errorf("failed to find connection: %v", err) 219 | return 220 | } 221 | // Note: Don't close the listener as it prevents closing the underlying connection 222 | c.retries = 0 223 | if !c.started { 224 | c.started = true 225 | c.startChan <- nil 226 | } 227 | connections[c.ID] = c 228 | log.Infof("connection %s is online", c.Name) 229 | 230 | // Accept connections 231 | go func() { 232 | for { 233 | in, err := l.Accept() 234 | if err != nil && !c.stopped { 235 | log.Error(err) 236 | c.reconnectChan <- err 237 | return 238 | } 239 | c.acceptChan <- in 240 | } 241 | }() 242 | 243 | // Listen events 244 | for { 245 | select { 246 | case <-c.stopChan: 247 | c.stopped = true 248 | return 249 | case in := <-c.acceptChan: 250 | go c.handle(in) 251 | case err = <-c.reconnectChan: 252 | c.Status = ConnectionStatusReconnecting 253 | goto RECONNECT 254 | } 255 | } 256 | } 257 | 258 | func (c *Connection) handle(in net.Conn) { 259 | defer in.Close() 260 | 261 | // Target connection 262 | out, err := net.Dial("tcp", c.TargetAddress) 263 | if err != nil { 264 | log.Printf("failed to connect to target: %v", err) 265 | return 266 | } 267 | defer out.Close() 268 | 269 | // Copy 270 | errCh := make(chan error, 2) 271 | cp := func(dst io.Writer, src io.Reader) { 272 | _, err := io.Copy(dst, src) 273 | errCh <- err 274 | } 275 | go cp(in, out) 276 | go cp(out, in) 277 | 278 | // Handle error 279 | err = <-errCh 280 | if err != nil && err != io.EOF { 281 | log.Printf("failed to copy: %v", err) 282 | } 283 | } 284 | 285 | func (c *Connection) stop() { 286 | log.Warnf("stopping connection: %s", c.Name) 287 | c.stopChan <- true 288 | } 289 | -------------------------------------------------------------------------------- /daemon/server.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "errors" 5 | "github.com/fsnotify/fsnotify" 6 | "github.com/labstack/gommon/log" 7 | "github.com/spf13/viper" 8 | "gopkg.in/resty.v1" 9 | "io/ioutil" 10 | "net" 11 | "net/rpc" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | ) 16 | 17 | type ( 18 | Server struct { 19 | resty *resty.Client 20 | } 21 | 22 | Protocol string 23 | 24 | ConnectRequest struct { 25 | Configuration string 26 | Address string 27 | Protocol Protocol 28 | } 29 | 30 | ConnectReply struct { 31 | } 32 | 33 | PSRequest struct { 34 | } 35 | 36 | PSReply struct { 37 | Connections []*Connection 38 | } 39 | 40 | RMRequest struct { 41 | Name string 42 | Force bool 43 | } 44 | 45 | RMReply struct { 46 | } 47 | ) 48 | 49 | const ( 50 | ProtocolHTTP = Protocol("http") 51 | ProtocolTCP = Protocol("tcp") 52 | ProtocolTLS = Protocol("tls") 53 | ) 54 | 55 | var ( 56 | connections = map[string]*Connection{} 57 | ) 58 | 59 | func (s *Server) Connect(req *ConnectRequest, rep *ConnectReply) (err error) { 60 | c, err := s.newConnection(req) 61 | if err != nil { 62 | return 63 | } 64 | go c.connect() 65 | return <-c.startChan 66 | } 67 | 68 | func (s *Server) PS(req *PSRequest, rep *PSReply) (err error) { 69 | for _, c := range connections { 70 | rep.Connections = append(rep.Connections, c) 71 | } 72 | return nil 73 | } 74 | 75 | func (s *Server) RM(req *RMRequest, rep *RMReply) error { 76 | for _, c := range connections { 77 | if c.Name == req.Name { 78 | c.stop() 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | func Start() { 85 | log.Info("starting daemon") 86 | r := resty.New() 87 | r.SetHostURL(viper.GetString("api_url")) 88 | r.SetAuthToken(viper.GetString("api_key")) 89 | viper.OnConfigChange(func(e fsnotify.Event) { 90 | r.SetAuthToken(viper.GetString("api_key")) 91 | }) 92 | r.SetHeader("Content-Type", "application/json") 93 | r.SetHeader("User-Agent", "tunnel/client") 94 | s := &Server{resty: r} 95 | rpc.Register(s) 96 | 97 | // Shutdown hook 98 | c := make(chan os.Signal) 99 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 100 | go func() { 101 | <-c 102 | log.Warn("stopping daemon") 103 | os.Exit(0) 104 | }() 105 | 106 | // Listen 107 | l, e := net.Listen("tcp", "127.0.0.1:0") 108 | if e != nil { 109 | log.Fatal(e) 110 | } 111 | err := ioutil.WriteFile(viper.GetString("daemon_addr"), []byte(l.Addr().String()), 0644) 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | defer l.Close() 116 | rpc.Accept(l) 117 | } 118 | 119 | func (s *Server) findConnection(c *Connection) (err error) { 120 | e := new(Error) 121 | res, err := s.resty.R(). 122 | SetResult(c). 123 | SetError(e). 124 | Get("/connections/" + c.ID) 125 | if err != nil { 126 | return 127 | } 128 | if res.IsError() { 129 | return errors.New(e.Message) 130 | } 131 | return 132 | } 133 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/labstack/tunnel-client 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/briandowns/spinner v1.7.0 7 | github.com/fsnotify/fsnotify v1.4.7 8 | github.com/go-openapi/strfmt v0.19.3 // indirect 9 | github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 10 | github.com/jedib0t/go-pretty v4.3.0+incompatible 11 | github.com/labstack/gommon v0.3.0 12 | github.com/matoous/go-nanoid v1.2.0 13 | github.com/mattn/go-runewidth v0.0.5 // indirect 14 | github.com/mitchellh/go-homedir v1.1.0 15 | github.com/radovskyb/watcher v1.0.7 16 | github.com/spf13/cobra v0.0.5 17 | github.com/spf13/viper v1.5.0 18 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf 19 | gopkg.in/resty.v1 v1.12.0 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 8 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= 9 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 10 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 11 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 12 | github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g= 13 | github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ= 14 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 15 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 16 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 17 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 18 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 19 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 20 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 21 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 22 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 27 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 28 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 29 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 30 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 31 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 32 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 33 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 34 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 35 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 36 | github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= 37 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 38 | github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= 39 | github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 40 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 41 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 42 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 43 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 44 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 45 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 46 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 47 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 48 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 49 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 50 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 51 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 52 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 53 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 54 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 55 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 56 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 57 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 58 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 59 | github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 h1:60gBOooTSmNtrqNaRvrDbi8VAne0REaek2agjnITKSw= 60 | github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= 61 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 62 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 63 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 64 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 65 | github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= 66 | github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= 67 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 68 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 69 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 70 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 71 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 72 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 73 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 74 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 75 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 76 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 77 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 78 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 79 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 80 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 81 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 82 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 83 | github.com/matoous/go-nanoid v1.2.0 h1:PUmsE14neivOQ7l3uB9PpFUCgIHveVHfm5eYUFFPYWw= 84 | github.com/matoous/go-nanoid v1.2.0/go.mod h1:fvGBnhcQ+zcrB3qJIG32PAN11J/y1IYkGX2/VeHzuH0= 85 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 86 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 87 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 88 | github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= 89 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 90 | github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA= 91 | github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 92 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 93 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 94 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 95 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 96 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 97 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 98 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 99 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 100 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 101 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 102 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 103 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 104 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 106 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 107 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 108 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 109 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 110 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 111 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 112 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 113 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 114 | github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= 115 | github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= 116 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 117 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 118 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 119 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 120 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 121 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 122 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 123 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 124 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 125 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 126 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 127 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 128 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 129 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 130 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 131 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 132 | github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= 133 | github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= 134 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 135 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 136 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 137 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 138 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 139 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 140 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 141 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 142 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 143 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 144 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 145 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 146 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 147 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 148 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 149 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 150 | github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= 151 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 152 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 153 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 154 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 155 | go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU= 156 | go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 157 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 158 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 159 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 160 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 161 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 162 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 163 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= 164 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 165 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 166 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 167 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 168 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 169 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 171 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 172 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= 173 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 174 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 175 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 176 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 178 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 181 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 182 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 183 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 184 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 185 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 186 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= 188 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 190 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 191 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 192 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 193 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 194 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 195 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 196 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 197 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 198 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 199 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 200 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 201 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 202 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 203 | gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= 204 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 205 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 206 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 207 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 208 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 209 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 210 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 211 | -------------------------------------------------------------------------------- /tunnel-client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------