@@ -2,25 +2,37 @@ package cmd
22
33import (
44 "fmt"
5- "github.com/pkg/errors"
6- "github.com/rancher/remotedialer"
7- "github.com/rancher/tunnelware/pkg/rdns"
8- cli "github.com/rancher/wrangler-cli"
9- "github.com/rancher/wrangler/pkg/ticker"
10- "github.com/sirupsen/logrus"
11- "github.com/spf13/cobra"
12- "net"
5+ "io/ioutil"
136 "net/http"
147 "net/url"
158 "os"
9+ "os/exec"
1610 "path/filepath"
1711 "strings"
18- "time"
12+ 13+ "github.com/dgrijalva/jwt-go"
14+ "github.com/pkg/browser"
15+ "github.com/pkg/errors"
16+ "github.com/rancher/remotedialer"
17+ "github.com/rancher/tunnelware/pkg/questions"
18+ cli "github.com/rancher/wrangler-cli"
19+ "github.com/rancher/wrangler/pkg/randomtoken"
20+ "github.com/sirupsen/logrus"
21+ "github.com/spf13/cobra"
22+ )
23+ 24+ var (
25+ domain = "tunnelware.do.rancher.space"
1926)
2027
28+ type Claim struct {
29+ Username string `json:"username"`
30+ jwt.StandardClaims
31+ }
32+ 2133func NewClientCommand () * cobra.Command {
2234 client := cli .Command (& Client {}, cobra.Command {
23- Short : "Run tunnel client" ,
35+ Short : "Run tunnel client" ,
2436 Example : " tunnelware client http 8080" ,
2537 })
2638 return client
@@ -43,54 +55,100 @@ func (c *Client) Run(cmd *cobra.Command, args []string) error {
4355 if err != nil {
4456 return err
4557 }
46- 4758 if err := os .MkdirAll (filepath .Join (homedir , ".tunnelware" ), 0755 ); err != nil {
4859 return err
4960 }
5061
51- u , err := url .Parse (c .Server )
52- if err != nil {
53- return err
54- }
62+ tokenFile := filepath .Join (homedir , ".tunnelware" , "token" )
63+ if _ , err := os .Stat (filepath .Join (homedir , ".tunnelware" , "token" )); err != nil {
64+ // request auth token
65+ state , err := randomtoken .Generate ()
66+ if err != nil {
67+ return err
68+ }
5569
56- rdnsClient := rdns .NewClient ()
70+ serverURL , err := url .Parse (c .Server )
71+ if err != nil {
72+ return err
73+ }
5774
58- domain , err := rdns .GetDomain (cmd .Context (), rdnsClient , []string {u .Hostname ()}, net .ParseIP (u .Hostname ()) == nil )
59- if err != nil {
60- return err
61- }
62- go func () {
63- for range ticker .Context (cmd .Context (), time .Hour * 6 ) {
64- if _ , err := rdnsClient .RenewDomain (); err != nil {
65- logrus .Error (err )
75+ url := fmt .Sprintf ("https://%s/login/github?state=%s" , serverURL .Host , state )
76+ fmt .Printf ("Redirecting to %v for github login\n " , url )
77+ // credit from https://github.com/hashicorp/terraform/pull/24107/files
78+ // Windows Subsystem for Linux (bash for Windows) doesn't have xdg-open available
79+ // but you can execute cmd.exe from there; try to identify it
80+ if ! hasProgram ("xdg-open" ) && hasProgram ("cmd.exe" ) {
81+ r := strings .NewReplacer ("&" , "^&" )
82+ if out , err := exec .Command ("cmd.exe" , "/c" , "start" , r .Replace (url )).Output (); err != nil {
83+ return errors .Wrap (err , string (out ))
84+ }
85+ } else {
86+ if err := browser .OpenURL (url ); err != nil {
87+ return err
6688 }
6789 }
68- }()
6990
70- clientID := strings .Split (domain , "." )[0 ]
91+ finished , err := questions .PromptBool ("Once you finish github login, enter y to continue" , true )
92+ if err != nil {
93+ return err
94+ }
95+ if ! finished {
96+ return nil
97+ }
98+ resp , err := http .Get (fmt .Sprintf ("https://%s/jwt/%s" , serverURL .Host , state ))
99+ if err != nil {
100+ return err
101+ }
102+ defer resp .Body .Close ()
103+ token , err := ioutil .ReadAll (resp .Body )
104+ if err != nil {
105+ return err
106+ }
107+ if err := ioutil .WriteFile (tokenFile , token , 0755 ); err != nil {
108+ return err
109+ }
110+ }
71111
72- headers := http.Header {
73- "X-Tunnel-ID" : []string {clientID },
112+ token , err := ioutil .ReadFile (tokenFile )
113+ if err != nil {
114+ return err
74115 }
75116
76- query := u .Query ()
77- query .Add ("fqdn" , domain )
117+ claim := & Claim {}
118+ if _ , err := jwt .ParseWithClaims (string (token ), claim , nil ); err != nil {
119+ if err .(* jwt.ValidationError ).Errors != jwt .ValidationErrorUnverifiable {
120+ return err
121+ }
122+ }
123+ 124+ u , err := url .Parse (c .Server )
125+ if err != nil {
126+ return err
127+ }
78128 scheme := args [0 ]
79- host := args [1 ]
80- if ! strings .Contains (host , ":" ) {
81- host = fmt .Sprintf ("%v://127.0.0.1:%v" , scheme , host )
129+ forwardHost := args [1 ]
130+ if ! strings .Contains (forwardHost , ":" ) {
131+ forwardHost = fmt .Sprintf ("%v://127.0.0.1:%v" , scheme , forwardHost )
82132 } else {
83- host = fmt .Sprintf ("%v://%v" , scheme , host )
133+ forwardHost = fmt .Sprintf ("%v://%v" , scheme , forwardHost )
84134 }
85- query .Add ("forward" , host )
86- u .RawQuery = query .Encode ()
87135
88- logrus .Debugf ("connecting to %v" , u .String ())
136+ headers := http.Header {
137+ "X-TUNNEL-ID" : []string {fmt .Sprintf ("%s:%s" , strings .ToLower (claim .Username ), forwardHost )},
138+ "Authorization" : []string {"Bearer " + string (token )},
139+ }
89140
141+ logrus .Debugf ("connecting to %v" , u .String ())
142+ domainWithUsername := fmt .Sprintf ("%s.%s" , strings .ToLower (claim .Username ), domain )
90143 go func () {
91- fmt .Printf ("http://%v --------> %v\n https://%v -------> %v\n " , domain , host , domain , host )
144+ fmt .Printf ("http://%v --------> %v\n https://%v -------> %v\n " , domainWithUsername , forwardHost , domainWithUsername , forwardHost )
92145 remotedialer .ClientConnect (cmd .Context (), u .String (), headers , nil , func (string , string ) bool { return true }, nil )
93146 }()
94- <- cmd .Context ().Done ()
147+ <- cmd .Context ().Done ()
95148 return nil
96149}
150+ 151+ func hasProgram (name string ) bool {
152+ _ , err := exec .LookPath (name )
153+ return err == nil
154+ }
0 commit comments