diff --git a/.gitignore b/.gitignore index f1c181e..d7c7d63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.code-workspace # Binaries for programs and plugins *.exe *.exe~ diff --git a/auth.go b/auth.go index e357dca..c2ffe4e 100644 --- a/auth.go +++ b/auth.go @@ -3,6 +3,7 @@ package main import ( "bufio" "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -12,6 +13,7 @@ import ( "os/exec" "strings" "time" + "github.com/kirinlabs/HttpRequest" "golang.org/x/oauth2/google" ) @@ -31,32 +33,32 @@ type ClientSecrets struct { } type UserCredentials struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - RefreshToken string `json:"refresh_token"` - Scope string `json:"scope"` - Type string `json:"type"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + Type string `json:"type"` // The following two fields are option and exist after authentication - AccessToken string `json:"access_token"` - IDToken string `json:"id_token"` - Email string `json:"email"` - ExpiresAt int64 `json:"expires_at"` + AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` + Email string `json:"email"` + ExpiresAt int64 `json:"expires_at"` } type OAuthTokens struct { - AccessToken string `json:"access_token"` - ExpiresIn int `json:"expires_in"` - RefreshToken string `json:"refresh_token"` - Scope string `json:"scope"` - TokenType string `json:"token_type"` - IDToken string `json:"id_token"` - Error string `json:"error"` - ErrorDescription string `json:"error_description"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + TokenType string `json:"token_type"` + IDToken string `json:"id_token"` + Error string `json:"error"` + ErrorDescription string `json:"error_description"` } func readCredentials(filename string) ([]byte, error) { - in, err := os.Open(filename) + in, err := os.Open(filename) if err != nil { return []byte(""), err } @@ -70,11 +72,19 @@ func readCredentials(filename string) ([]byte, error) { func loadClientSecrets(filename string) (ClientSecrets, error) { var secrets ClientSecrets + var data []byte + var err error - data, err := readCredentials(filename) + if filename != "" { + data, err = readCredentials(filename) - if err != nil { - return secrets, err + if err != nil { + fmt.Println("Error: Cannot read credentials JSON file \""+filename+"\"", err) + os.Exit(1) + } + } else { + // + data, err = base64.StdEncoding.DecodeString("eyJpbnN0YWxsZWQiOnsiY2xpZW50X2lkIjoiNjU4OTM5NTQ0ODM3LXNtaHR1MG42N3A3MGdqM2o0Y2JtZGw2NmNma3RhcWx2LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwicHJvamVjdF9pZCI6InhjbG91ZHNoZWxsIiwiYXV0aF91cmkiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvYXV0aCIsInRva2VuX3VyaSI6Imh0dHBzOi8vb2F1dGgyLmdvb2dsZWFwaXMuY29tL3Rva2VuIiwiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vb2F1dGgyL3YxL2NlcnRzIiwiY2xpZW50X3NlY3JldCI6ImJEUEN4eTl3LTlac0ZoQ2hpX243Yk5ERyIsInJlZGlyZWN0X3VyaXMiOlsidXJuOmlldGY6d2c6b2F1dGg6Mi4wOm9vYiIsImh0dHA6Ly9sb2NhbGhvc3QiXX19") } // fmt.Println(string(data)) @@ -87,7 +97,7 @@ func loadClientSecrets(filename string) (ClientSecrets, error) { return secrets, err } - config.ProjectId = secrets.Installed.ProjectID + // config.ProjectId = secrets.Installed.ProjectID // fmt.Println("ClientID:", secrets.Installed.ClientID) @@ -119,7 +129,7 @@ func loadUserCredentials(filename string) (UserCredentials, error) { } func saveUserCredentials(filename string, creds UserCredentials) error { - if config.Debug == true { + if config.Debug { fmt.Println("Save Credentials to:", filename) } @@ -130,7 +140,7 @@ func saveUserCredentials(filename string, creds UserCredentials) error { return err } - // err = ioutil.WriteFile(filename + ".test", j, 0644) + // err = ioutil.WriteFile(filename+".test", j, 0644) err = ioutil.WriteFile(filename, j, 0644) if err != nil { @@ -179,6 +189,10 @@ func debug_PrintUserCredentials(creds UserCredentials) { func doRefresh(filename string) (string, string, bool) { endpoint := "https://www.googleapis.com/oauth2/v4/token" + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + creds, err := loadUserCredentials(filename) if err != nil { @@ -186,35 +200,48 @@ func doRefresh(filename string) (string, string, bool) { return "", "", false } - // debug_PrintUserCredentials(creds) - // We want an access token that is good for a while. // Brand new tokens are valid for 3600 seconds // For testing require 15 minutes or 900 seconds - var t time.Time = time.Unix(creds.ExpiresAt - (15 * 60), 0) + var t time.Time = time.Unix(creds.ExpiresAt-(15*60), 0) // fmt.Println(t) // fmt.Println(time.Now()) if time.Now().Before(t) { - if config.Debug == true { + if config.Debug { fmt.Println("Saved credentials (Access Token) have not expired") } return creds.AccessToken, creds.IDToken, true } - if config.Debug == true { + if config.Debug { fmt.Println("Must Refresh Token") } - content := "client_id=" + creds.ClientID + "&" - content += "client_secret=" + creds.ClientSecret + "&" + //************************************************************ + // Load the Google Client Secrets + //************************************************************ + + secrets, err := loadClientSecrets(config.ClientSecretsFile) + + if err != nil { + fmt.Println(err) + return "", "", false + } + + //************************************************************ + // Build the authenticate URL + //************************************************************ + + content := "client_id=" + secrets.Installed.ClientID + "&" + content += "client_secret=" + secrets.Installed.ClientSecret + "&" content += "grant_type=refresh_token&" content += "refresh_token=" + creds.RefreshToken - req := HttpRequest.NewRequest() + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) req.SetHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded"}) @@ -241,30 +268,30 @@ func doRefresh(filename string) (string, string, bool) { return "", "", false } - var expires_at int64 = int64(time.Now().UTC().Unix()) + int64(tokens.ExpiresIn) + var expires int64 = int64(time.Now().UTC().Unix()) + int64(tokens.ExpiresIn) -/* - fmt.Println("AccessToken:", tokens.AccessToken) - fmt.Println("ExpiresIn:", tokens.ExpiresIn) - fmt.Println("ExpiresAt:", expires_at) - fmt.Println("Scope:", tokens.Scope) - fmt.Println("TokenType:", tokens.TokenType) - fmt.Println("IDToken:", tokens.IDToken) -*/ + /* + fmt.Println("AccessToken:", tokens.AccessToken) + fmt.Println("ExpiresIn:", tokens.ExpiresIn) + fmt.Println("ExpiresAt:", expires_at) + fmt.Println("Scope:", tokens.Scope) + fmt.Println("TokenType:", tokens.TokenType) + fmt.Println("IDToken:", tokens.IDToken) + */ creds.AccessToken = tokens.AccessToken creds.IDToken = tokens.IDToken - creds.ExpiresAt = expires_at + creds.ExpiresAt = expires - email, err := get_email_address(tokens.AccessToken) + // email, err := get_email_address(tokens.AccessToken) - if err == nil { - if config.Debug == true { - fmt.Println("Email:", email) - } + // if err == nil { + // if config.Debug { + // fmt.Println("Email:", email) + // } - creds.Email = email - } + // creds.Email = email + // } err = saveUserCredentials(filename, creds) @@ -279,7 +306,11 @@ func doRefresh(filename string) (string, string, bool) { func debug_displayAccessToken(accessToken string) { endpoint := "https://www.googleapis.com/oauth2/v3/tokeninfo" - req := HttpRequest.NewRequest() + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) @@ -297,13 +328,18 @@ func debug_displayAccessToken(accessToken string) { return } + // fmt.Println("Token Info: ") fmt.Println(string(body)) } func debug_displayUserInfo(accessToken string) { endpoint := "https://www.googleapis.com/oauth2/v3/userinfo" - req := HttpRequest.NewRequest() + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) @@ -324,88 +360,96 @@ func debug_displayUserInfo(accessToken string) { fmt.Println(string(body)) } -func debug_displayIDToken(accessToken, idToken string) { - endpoint := "https://www.googleapis.com/oauth2/v3/tokeninfo" +// func debug_displayIDToken(accessToken, idToken string) { +// endpoint := "https://www.googleapis.com/oauth2/v3/tokeninfo" - endpoint += "?id_token=" + idToken +// endpoint += "?id_token=" + idToken - req := HttpRequest.NewRequest() +// if config.UrlFetch != "" { +// endpoint = config.UrlFetch + endpoint +// } - req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) +// req := HttpRequest.NewRequest() - res, err := req.Get(endpoint) +// req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) - if err != nil { - fmt.Println("Error: ", err) - return - } +// res, err := req.Get(endpoint) - body, err := res.Body() +// if err != nil { +// fmt.Println("Error: ", err) +// return +// } - if err != nil { - fmt.Println("Error: ", err) - return - } +// body, err := res.Body() - fmt.Println(string(body)) -} +// if err != nil { +// fmt.Println("Error: ", err) +// return +// } -func get_email_address(accessToken string) (string, error) { - type Access_Token struct { - Azp string `json:"azp"` - Aud string `json:"aud"` - Sub string `json:"sub"` - Scope string `json:"scope"` - Exp string `json:"exp"` - Expires_in string `json:"expires_in"` - Email string `json:"email"` - Email_verified string `json:"email_verified"` - Access_type string `json:"access_type"` - } +// fmt.Println(string(body)) +// } - //************************************************************ - // - //************************************************************ +// func get_email_address(accessToken string) (string, error) { +// type Access_Token struct { +// Azp string `json:"azp"` +// Aud string `json:"aud"` +// Sub string `json:"sub"` +// Scope string `json:"scope"` +// Exp string `json:"exp"` +// Expires_in string `json:"expires_in"` +// Email string `json:"email"` +// Email_verified string `json:"email_verified"` +// Access_type string `json:"access_type"` +// } - endpoint := "https://www.googleapis.com/oauth2/v3/tokeninfo" +// //************************************************************ +// // +// //************************************************************ - req := HttpRequest.NewRequest() +// endpoint := "https://www.googleapis.com/oauth2/v3/tokeninfo" - req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) +// if config.UrlFetch != "" { +// endpoint = config.UrlFetch + endpoint +// } - //************************************************************ - // - //************************************************************ +// req := HttpRequest.NewRequest() - res, err := req.Get(endpoint) +// req.SetHeaders(map[string]string{"Authorization": "Bearer " + accessToken}) - if err != nil { - fmt.Println("Error: ", err) - return "", err - } +// //************************************************************ +// // +// //************************************************************ - body, err := res.Body() +// res, err := req.Get(endpoint) - if err != nil { - fmt.Println("Error: ", err) - return "", err - } +// if err != nil { +// fmt.Println("Error: ", err) +// return "", err +// } - //************************************************************ - // - //************************************************************ +// body, err := res.Body() - var tokens Access_Token +// if err != nil { +// fmt.Println("Error: ", err) +// return "", err +// } - err = json.Unmarshal(body, &tokens) +// //************************************************************ +// // +// //************************************************************ - if err != nil { - fmt.Println("Error: Cannot unmarshal JSON: ", err) - return "", err - } +// var tokens Access_Token - return tokens.Email, nil -} +// err = json.Unmarshal(body, &tokens) + +// if err != nil { +// fmt.Println("Error: Cannot unmarshal JSON: ", err) +// return "", err +// } + +// return tokens.Email, nil +// } func get_tokens() (string, string, error) { //************************************************************ @@ -413,14 +457,10 @@ func get_tokens() (string, string, error) { // Engine when interfacing with Cloud Shell. //************************************************************ - if config.Flags.Adc == true { + if config.Flags.Adc { return get_sa_tokens() } - //************************************************************ - // - //************************************************************ - // fmt.Println("Auth:", config.Flags.Auth) // fmt.Println("Login:", config.Flags.Login) @@ -428,7 +468,7 @@ func get_tokens() (string, string, error) { if fileExists(SavedUserCredentials) { accessToken, idToken, valid := doRefresh(SavedUserCredentials) - if valid == true { + if valid { // fmt.Println("Access Token: ", accessToken) // fmt.Println("ID Token: ", idToken) @@ -436,7 +476,6 @@ func get_tokens() (string, string, error) { // debug_displayUserInfo(accessToken) // debug_displayIDToken(accessToken, idToken) - return accessToken, idToken, nil } } @@ -482,10 +521,10 @@ func get_tokens() (string, string, error) { url += "&login_hint=" + config.Flags.Login } - if isWindows() == true { + if isWindows() { url += "&redirect_uri=http://localhost:9000" } else { - if flag_desktop == true { + if flag_desktop { url += "&redirect_uri=http://localhost:9000" } else { url += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob" @@ -509,13 +548,13 @@ func get_tokens() (string, string, error) { } } - if config.Debug == true { + if config.Debug { fmt.Println("Python Path:", python_path) } //************************************************************ - if isWindows() == true { + if isWindows() { chrome, err := FindChromeBrowser() var cmd *exec.Cmd @@ -565,7 +604,7 @@ func get_tokens() (string, string, error) { log.Fatal("Error: Missing OAuth2 Code") } - if config.Debug == true { + if config.Debug { fmt.Println("OAuth2 Code:", string(out)) } @@ -575,33 +614,16 @@ func get_tokens() (string, string, error) { } func get_sa_tokens() (string, string, error) { - //************************************************************ - // - //************************************************************ - scope := "https://www.googleapis.com/auth/cloud-platform" - - //************************************************************ - // - //************************************************************ - - ctx := context.Background() - - //************************************************************ - // - //************************************************************ + ctx := context.Background() - creds, err := google.FindDefaultCredentials(ctx, scope) + creds, err := google.FindDefaultCredentials(ctx, SCOPE) if err != nil { fmt.Println(err) return "", "", err } - //************************************************************ - // - //************************************************************ - token, err := creds.TokenSource.Token() if err != nil { @@ -609,10 +631,6 @@ func get_sa_tokens() (string, string, error) { return "", "", err } - //************************************************************ - // - //************************************************************ - return token.AccessToken, "", nil } @@ -637,12 +655,12 @@ func FindChromeBrowser() (string, error) { } func manualAuthentication(secrets ClientSecrets, url string) (string, string, error) { + fmt.Println("Go to the following link in your browser:") fmt.Println() fmt.Println(url) fmt.Println() fmt.Print("Enter verification code: ") - reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n') @@ -667,9 +685,9 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st endpoint := "https://www.googleapis.com/oauth2/v4/token" - req := HttpRequest.NewRequest() + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) - req.SetHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded"}) + //req.SetHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded"}) res, err := req.Post(endpoint, content) @@ -685,7 +703,7 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st return "", "", err } - if config.Debug == true { + if config.Debug { fmt.Println("BODY:", string(body)) } @@ -702,7 +720,7 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st return "", "", err } - if config.Debug == true { + if config.Debug { fmt.Println("JSON:", tokens) } @@ -717,7 +735,7 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st // //************************************************************ - var expires_at int64 = int64(time.Now().UTC().Unix()) + int64(tokens.ExpiresIn) + var expires int64 = int64(time.Now().UTC().Unix()) + int64(tokens.ExpiresIn) var creds UserCredentials @@ -730,19 +748,19 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st creds.AccessToken = tokens.AccessToken creds.IDToken = tokens.IDToken - creds.ExpiresAt = expires_at + creds.ExpiresAt = expires //************************************************************ // //************************************************************ - email, err := get_email_address(creds.AccessToken) + // email, err := get_email_address(creds.AccessToken) - if err == nil { - fmt.Println("Email:", email) + // if err == nil { + // fmt.Println("Email:", email) - creds.Email = email - } + // creds.Email = email + // } //************************************************************ // @@ -759,10 +777,10 @@ func processAuthCode(secrets ClientSecrets, auth_code string, flag_oob bool) (st // //************************************************************ - if config.Debug == true { + if config.Debug { debug_displayAccessToken(creds.AccessToken) - debug_displayUserInfo(creds.AccessToken) - debug_displayIDToken(creds.AccessToken, creds.IDToken) + // debug_displayUserInfo(creds.AccessToken) + // debug_displayIDToken(creds.AccessToken, creds.IDToken) } return creds.AccessToken, creds.IDToken, nil diff --git a/bitvise.go b/bitvise.go index 6afc084..fcf51e2 100644 --- a/bitvise.go +++ b/bitvise.go @@ -10,7 +10,7 @@ import ( // Also supports loading a profile // BvSsh.exe -profile= -var path_bitvise = "C:\\Program Files (x86)\\Bitvise SSH Client\\BvSsh.exe" +var path_bitvise = "C:\\Program Files (x86)\\Bitvise SSH Client\\BvSsh.exe" func exec_bitvise(params CloudShellEnv) { key, err := env_get_ssh_ppk() @@ -27,13 +27,13 @@ func exec_bitvise(params CloudShellEnv) { args := []string{} - args = append(args, "-host=" + sshHost) - args = append(args, "-port=" + sshPort) - args = append(args, "-user=" + sshUsername) - args = append(args, "-keypairFile=" + key) + args = append(args, "-host="+sshHost) + args = append(args, "-port="+sshPort) + args = append(args, "-user="+sshUsername) + args = append(args, "-keypairFile="+key) args = append(args, "-loginOnStartup") - if config.Debug == true { + if config.Debug { fmt.Println(key) fmt.Println(sshUsername) fmt.Println(sshHost) diff --git a/cloudshell.go b/cloudshell.go index 9bccd16..58074ee 100644 --- a/cloudshell.go +++ b/cloudshell.go @@ -4,7 +4,12 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "net" + "os" + "strings" "time" + "github.com/kirinlabs/HttpRequest" ) @@ -28,45 +33,46 @@ import ( //****************************************************************************************** type CloudShellEnv struct { - Name string `json:"name"` - Id string `json:"id"` - DockerImage string `json:"dockerImage"` - State string `json:"state"` - SshUsername string `json:"sshUsername"` - SshHost string `json:"sshHost"` - SshPort int32 `json:"sshPort"` - Error struct { - Code int32 `json:"code"` - Message string `json:"message"` - Status string `json:"status"` + Name string `json:"name"` + Id string `json:"id"` + DockerImage string `json:"dockerImage"` + State string `json:"state"` + SshUsername string `json:"sshUsername"` + SshHost string `json:"sshHost"` + SshPort int32 `json:"sshPort"` + Error struct { + Code int32 `json:"code"` + Message string `json:"message"` + Status string `json:"status"` } `json:"error"` } //****************************************************************************************** // Method: users.environments.get -// https://cloud.google.com/shell/docs/reference/rest/v1alpha1/users.environments/get +// https://cloud.google.com/shell/docs/reference/rest/v1/users.environments/get //****************************************************************************************** func cloud_shell_get_environment(accessToken string, flag_info bool) (CloudShellEnv, error) { - //************************************************************ - // - //************************************************************ var params CloudShellEnv - endpoint := "https://cloudshell.googleapis.com/v1alpha1/users/me/environments/default" + endpoint := "https://cloudshell.googleapis.com/v1/users/me/environments/default" endpoint += "?alt=json" - req := HttpRequest.NewRequest() + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) - hdrs := map[string]string { - "Authorization": "Bearer " + accessToken, - "X-Goog-User-Project": config.ProjectId, + hdrs := map[string]string{ + "Authorization": "Bearer " + accessToken, + "X-Goog-User-Project": config.ProjectId, } req.SetHeaders(hdrs) - if config.Debug == true { + if config.Debug { fmt.Println("Access Token:", accessToken) fmt.Println("ProjectId:", config.ProjectId) } @@ -89,12 +95,9 @@ func cloud_shell_get_environment(accessToken string, flag_info bool) (CloudShell return params, err } - if flag_info == true { - fmt.Println("") - fmt.Println("************************************************************") + if flag_info { fmt.Println("Cloud Shell Info:") fmt.Println(string(body)) - fmt.Println("************************************************************") } err = json.Unmarshal(body, ¶ms) @@ -114,7 +117,7 @@ func cloud_shell_get_environment(accessToken string, flag_info bool) (CloudShell //****************************************************************************************** // Method: users.environment.start -// https://cloud.google.com/shell/docs/reference/rest/v1alpha1/users.environments/start +// https://cloud.google.com/shell/docs/reference/rest/v1/users.environments/start //****************************************************************************************** func cloudshell_start(accessToken string) error { @@ -122,28 +125,28 @@ func cloudshell_start(accessToken string) error { // //************************************************************ - if config.Debug == true { + if config.Debug { fmt.Println("Request users.environment.start") } - endpoint := "https://cloudshell.googleapis.com/v1alpha1/users/me/environments/default" + endpoint := "https://cloudshell.googleapis.com/v1/users/me/environments/default" endpoint += ":start" endpoint += "?alt=json" - req := HttpRequest.NewRequest() + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) - hdrs := map[string]string { - "Authorization": "Bearer " + accessToken, - "X-Goog-User-Project": config.ProjectId, + hdrs := map[string]string{ + "Authorization": "Bearer " + accessToken, + "X-Goog-User-Project": config.ProjectId, } req.SetHeaders(hdrs) - //************************************************************ - // - //************************************************************ - - res, err := req.Post(endpoint) + res, err := req.JSON().Post(endpoint, "{\"accessToken\": \""+accessToken+"\"}") if err != nil { fmt.Println("Error: ", err) @@ -157,7 +160,7 @@ func cloudshell_start(accessToken string) error { return err } - if config.Debug == true { + if config.Debug { fmt.Println("") fmt.Println("************************************************************") fmt.Println("Cloud Shell Info:") @@ -184,75 +187,157 @@ func cloudshell_start(accessToken string) error { return nil } -func env_get_ssh_pkey() (string, error) { - //************************************************************* - // Return the Google Cloud SSH Key for the current Windows User - //************************************************************* +func cloud_shell_create_publickeys(accessToken string) error { + + fmt.Println("Pushing your public key to Cloud Shell...") + + endpoint := "https://cloudshell.googleapis.com/v1/users/me/environments/default/publicKeys" + endpoint += "?alt=json" + + if config.UrlFetch != "" { + endpoint = config.UrlFetch + endpoint + } + + req := HttpRequest.NewRequest().SetTimeout(5 * time.Second) + + req.SetHeaders(map[string]string{ + "Authorization": "Bearer " + accessToken, + "X-Goog-User-Project": config.ProjectId}) - path, err := get_home_directory() + if config.Debug { + fmt.Println("Access Token:", accessToken) + fmt.Println("ProjectId:", config.ProjectId) + } + + //************************************************************ + // + //************************************************************ + path, err := env_get_ssh_pub_key() if err != nil { - fmt.Println(err) - return "", err + fmt.Println("Error: ", err) + return err } - if isWindows() == true { - path += "\\.ssh\\google_compute_engine" - } else { - path += "/.ssh/google_compute_engine" + bytes, err := ioutil.ReadFile(path) + if err != nil { + fmt.Println("Error: ", err) } - if config.Debug == true { - fmt.Println("Path:", path) + keyFields := strings.Fields(string(bytes)) + + pubkey := keyFields[1] + + res, err := req.JSON().Post(endpoint, "{\"key\":{\"format\": \"SSH_RSA\",\"key\": \""+pubkey+"\"}}") + if err != nil { + fmt.Println("Error: ", err) + return err } - if fileExists(path) == false { - err = errors.New("Google SSH Key does not exist") - fmt.Println("Error:", err) - fmt.Println("File:", path) - return "", err + body, err := res.Body() + if err != nil { + fmt.Println("Error: ", err) + return err } - return path, nil + type Params struct { + Name string `json:"name"` + Format string `json:"format"` + Key string `json:"key"` + Error struct { + Code int32 `json:"code"` + Message string `json:"message"` + } `json:"error"` + } + + var params Params + + err = json.Unmarshal(body, ¶ms) + + if err != nil { + fmt.Println("Error: Cannot unmarshal JSON: ", err) + return err + } + + if config.Debug { + fmt.Println("Body:", params) + } + + if params.Error.Code != 0 { + fmt.Println("Error:", params.Error.Message) + } + + return nil } -func env_get_ssh_ppk() (string, error) { +func env_get_ssh_key(filename string, ext string) (string, error) { //************************************************************* - // Return the Google Cloud SSH Key for the current Windows User + // Return the Google Cloud SSH Key for the current User //************************************************************* - path, err := get_home_directory() + // var path string + homepath, err := get_home_directory() if err != nil { fmt.Println(err) return "", err } - if isWindows() == true { - path += "\\.ssh\\google_compute_engine.ppk" - } else { - path += "/.ssh/google_compute_engine.ppk" + // if isWindows() { + // path = homepath + "\\.ssh\\" + filename + ext + // } else { + // path = homepath + "/.ssh/" + filename + ext + // } + + path := homepath + "/.ssh/" + filename + ext + if !fileExists(path) { + path = homepath + "/.ssh/id_rsa_cloudshell" + ext + if !fileExists(path) { + path = homepath + "/.ssh/id_rsa" + ext + if !fileExists(path) { + err = errors.New("SSH Key does not exist") + fmt.Println("Error:", err) + fmt.Println("File:", homepath+"/.ssh/id_rsa"+ext) + fmt.Println("Please create and upload SSH key first") + fmt.Println("\n\n$ ssh-keygen -t rsa -C \"Cloud Shell\"") + fmt.Println("$ cloudshell push_pubkey\n\n ") + return "", err + } + } } - if config.Debug == true { + if config.Debug { fmt.Println("Path:", path) } - if fileExists(path) == false { - err = errors.New("Google SSH Key does not exist") - fmt.Println("Error:", err) - fmt.Println("File:", path) - return "", err - } - return path, nil } +func env_get_ssh_pkey() (string, error) { + return env_get_ssh_key("google_compute_engine", "") +} + +func env_get_ssh_ppk() (string, error) { + return env_get_ssh_key("google_compute_engine", ".ppk") +} + +func env_get_ssh_pub_key() (string, error) { + return env_get_ssh_key("google_compute_engine", ".pub") +} + func call_cloud_shell(accessToken string) { //************************************************************ // //************************************************************ + if config.Command == CREATE_PUBKEY { + err := cloud_shell_create_publickeys(accessToken) + if err != nil { + fmt.Println("Error:", err) + } + os.Exit(0) + } + flag_info := false if config.Command == CMD_INFO { @@ -264,15 +349,19 @@ func call_cloud_shell(accessToken string) { params, err := cloud_shell_get_environment(accessToken, flag_info) if err != nil { + fmt.Println("Error:", err) return } if config.Command == CMD_INFO { return } + if params.State == "DISABLED" || params.State == "SUSPENDED" { + if config.Debug { + fmt.Println("CloudShell State:", params.State) + } - if params.State == "DISABLED" { - fmt.Println("CloudShell State:", params.State) + fmt.Println("Starting your Cloud Shell machine...") err = cloudshell_start(accessToken) @@ -299,9 +388,34 @@ func call_cloud_shell(accessToken string) { // I don't know how long this really takes // Perhaps a connection attempt is required time.Sleep(5000 * time.Millisecond) - break; + break + } + } + + // waiting + host := params.SshHost + port := fmt.Sprint(params.SshPort) + + for x := 0; x < 120; x++ { + time.Sleep(1000 * time.Millisecond) + + timeout := time.Second + conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout) + if err != nil { + if config.Debug { + fmt.Println("Connecting error:", err) + } + continue + } + if conn != nil { + defer conn.Close() + if config.Debug { + fmt.Println("Opened", net.JoinHostPort(host, port)) + } + break } } + } if params.State != "RUNNING" { @@ -309,19 +423,28 @@ func call_cloud_shell(accessToken string) { return } + if config.Debug { + fmt.Println("Command:", config.Command) + } + if config.Command == CMD_PUTTY { + fmt.Println("Your Cloud Shell machine is RUNNING, connecting...") exec_putty(params) } if config.Command == CMD_INLINE_SSH { - exec_inline_ssh(params) + // exec_inline_ssh(params) + fmt.Println("Your Cloud Shell machine is RUNNING, connecting...") + exec_winssh(params) } if config.Command == CMD_WINSSH { + fmt.Println("Your Cloud Shell machine is RUNNING, connecting...") exec_winssh(params) } if config.Command == CMD_SSH { + fmt.Println("Your Cloud Shell machine is RUNNING, connecting...") exec_ssh(params) } diff --git a/cmdline.go b/cmdline.go index 867a742..a2288d9 100644 --- a/cmdline.go +++ b/cmdline.go @@ -23,6 +23,8 @@ const ( CMD_WINSCP CMD_BENCHMARK_DOWNLOAD CMD_BENCHMARK_UPLOAD + CMD_SSH_VSCODE + CREATE_PUBKEY ) func process_cmdline() { @@ -69,12 +71,12 @@ func process_cmdline() { fmt.Println("index:", x) fmt.Println("count:", len(os.Args)) - if x == len(os.Args) - 1 { + if x == len(os.Args)-1 { fmt.Println("Error: Missing email address to --login") os.Exit(1) } - config.Flags.Login = os.Args[x + 1] + config.Flags.Login = os.Args[x+1] config.Flags.Auth = true x++ continue @@ -94,6 +96,64 @@ func process_cmdline() { continue } + // SSH args + if arg == "-o" { + config.sshFlags = append(config.sshFlags, "-o", os.Args[x+1]) + x++ + continue + } + if arg == "-D" { + // support vs code + if x == 1 && len(os.Args) == 5 && os.Args[4] == "bash" { + + if os.Args[3] == "cloudshell" { + config.Debug = true + + if isWindows() { + config.Command = CMD_WINSSH + } else { + config.Command = CMD_SSH + } + } else { + config.Command = CMD_SSH_VSCODE + config.sshFlags = append(config.sshFlags, "-D", os.Args[2], os.Args[3]) + break + } + } + + config.sshFlags = append(config.sshFlags, "-D", os.Args[x+1]) + x++ + continue + } + if arg == "-V" { + fmt.Println("OpenSSH_for_Windows_7.7p1, LibreSSL 2.6.5") + os.Exit(0) + } + // WINSCP args + if strings.HasPrefix(arg, "/rawsettings") { + // config.sshFlags = append(config.sshFlags, os.Args[x:]...) + config.WinscpFlags = os.Args[x:] + break + } + // Proxy + if arg == "-proxy" || arg == "--proxy" { + config.Proxy = os.Args[x+1] + x++ + continue + } + if arg == "-v2" || arg == "--v2ray" { + config.Proxy = "v2ray" + continue + } + if arg == "-ss" || arg == "--shadowsocks" { + config.Proxy = "shadowsocks" + continue + } + if arg == "-urlfetch" || arg == "--urlfetch" { + config.UrlFetch = os.Args[x+1] + x++ + continue + } // WINSCP args if strings.HasPrefix(arg, "/rawsettings") { // config.sshFlags = append(config.sshFlags, os.Args[x:]...) @@ -125,39 +185,23 @@ func process_cmdline() { case "info": config.Command = CMD_INFO - case "putty": - if isWindows() == true { - config.Command = CMD_PUTTY - } else { - fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") - os.Exit(1) - } - case "ssh": - if isWindows() == true { + if isWindows() { config.Command = CMD_INLINE_SSH } else { config.Command = CMD_SSH } case "bitvise": - if isWindows() == true { + if isWindows() { config.Command = CMD_BITVISE } else { fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") os.Exit(1) } - case "winscp": - if isWindows() == true { - config.Command = CMD_WINSCP - } else { - fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") - os.Exit(1) - } - case "winssh": - if isWindows() == true { + if isWindows() { config.Command = CMD_WINSSH } else { fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") @@ -171,7 +215,7 @@ func process_cmdline() { } config.Command = CMD_EXEC - config.RemoteCommand = args[x + 1] + config.RemoteCommand = args[x+1] x++ case "download": @@ -181,11 +225,11 @@ func process_cmdline() { } config.Command = CMD_DOWNLOAD - config.SrcFile = strings.ReplaceAll(args[x + 1], "\\", "/") + config.SrcFile = strings.ReplaceAll(args[x+1], "\\", "/") x++ if len(args) >= 3 { - config.DstFile = strings.ReplaceAll(args[x + 1], "\\", "/") + config.DstFile = strings.ReplaceAll(args[x+1], "\\", "/") x++ } else { _, file := path.Split(config.SrcFile) @@ -193,7 +237,7 @@ func process_cmdline() { config.DstFile = file } - if config.Debug == true { + if config.Debug { fmt.Println("SrcFile:", config.SrcFile) fmt.Println("DstFile:", config.DstFile) } @@ -204,7 +248,7 @@ func process_cmdline() { os.Exit(1) } - path, err := filepath.Abs(args[x + 1]) + path, err := filepath.Abs(args[x+1]) if err != nil { fmt.Println(err) @@ -216,7 +260,7 @@ func process_cmdline() { x++ if len(args) >= 3 { - config.DstFile = strings.ReplaceAll(args[x + 1], "\\", "/") + config.DstFile = strings.ReplaceAll(args[x+1], "\\", "/") x++ } else { file := strings.ReplaceAll(config.SrcFile, "\\", "/") @@ -226,11 +270,31 @@ func process_cmdline() { config.DstFile = file } - if config.Debug == true { + if config.Debug { fmt.Println("SrcFile:", config.SrcFile) fmt.Println("DstFile:", config.DstFile) } + case "putty": + if isWindows() { + config.Command = CMD_PUTTY + } else { + fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") + os.Exit(1) + } + + case "winscp": + if isWindows() { + config.Command = CMD_WINSCP + } else { + fmt.Println("Error: This command is only supported on Windows. For Linux use ssh") + os.Exit(1) + } + + case "push_pubkey": + config.Command = CREATE_PUBKEY + return + case "benchmark": if len(args) < 2 { fmt.Println("Error: expected download or upload option") @@ -262,7 +326,7 @@ func process_cmdline() { os.Exit(1) } - if isWindows() == true { + if isWindows() { fmt.Println("Error: expected a command (info, putty, ssh, winssh, exec, upload, download)") } else { fmt.Println("Error: expected a command (info, ssh, exec, upload, download)") @@ -276,8 +340,9 @@ func cmd_help() { fmt.Println("Usage: cloudshell [command]") fmt.Println(" cloudshell - display cloudshell program help") fmt.Println(" cloudshell info - display Cloud Shell information") - if isWindows() == true { + if isWindows() { fmt.Println(" cloudshell putty - connect to Cloud Shell with Putty") + fmt.Println(" cloudshell winscp - connect to Cloud Shell with WinSCP") } fmt.Println(" cloudshell ssh - connect to Cloud Shell with SSH") fmt.Println(" cloudshell winssh - connect to Cloud Shell with Windows OpenSSH") diff --git a/config.go b/config.go index 34892b5..bec3b8c 100644 --- a/config.go +++ b/config.go @@ -5,85 +5,164 @@ import ( "fmt" "io/ioutil" "os" + "os/user" + "path/filepath" "strings" ) type ConfigJson struct { - ClientSecretsFile string `json:"client_secrets_file"` - WinscpFlags string `json:"winscp_flags"` - + ClientSecretsFile string `json:"oauth_json_file"` + UserCredentials string `json:"user_credentials_json_file"` + SSHFlags []string `json:"ssh_flags"` + Debug bool `json:"debug"` + Proxy string `json:"proxy"` + UrlFetch string `json:"urlfetch"` + WinscpFlags string `json:"winscp_flags"` } // Global Flags type FlagsStruct struct { - Adc bool - Auth bool - Login string - Info bool + Adc bool + Auth bool + Login string + Info bool } type Config struct { // Global debug flag - Debug bool + Debug bool - UseAdcCredentials bool + UseAdcCredentials bool - ProjectId string + ProjectId string - ClientSecretsFile string + ClientSecretsFile string // Command to execute - Command int + Command int // Command "exec" - RemoteCommand string + RemoteCommand string // Commands "download" and "upload" - SrcFile string - DstFile string + SrcFile string + DstFile string // Command line global options - Flags FlagsStruct + Flags FlagsStruct - // Commands "benchmark download" and "benchark upload" - benchmark_size int64 + // Command line ssh options + sshFlags []string // Command line winscp options WinscpFlags []string + + // Path + AbsPath string + PluginsPath string + + // proxy + Proxy string + + // api proxy + UrlFetch string + + // Commands "benchmark download" and "benchark upload" + benchmark_size int64 } var config Config func init_config() error { - in, err := os.Open("config.json") + // path, err := filepath.Abs(filepath.Dir(os.Args[0])) + path, err := os.Executable() if err != nil { fmt.Println(err) - return err } - defer in.Close() - - data, err := ioutil.ReadAll(in) + path = filepath.Dir(path) - var configJson ConfigJson - - err = json.Unmarshal(data, &configJson) + config.AbsPath = path + config.PluginsPath = path + "/plugins" + // config.json + in, err := os.Open(user_config_path("/config.json")) if err != nil { - fmt.Println("Error: Cannot unmarshal JSON: ", err) - return err + in, err = os.Open(config.AbsPath + "/config.json") } - config.ClientSecretsFile = configJson.ClientSecretsFile + if err != nil { + // fmt.Println(err) + } else { + + defer in.Close() - // fmt.Println("Client Secrets File:", config.ClientSecretsFile) + data, _ := ioutil.ReadAll(in) + + var configJson ConfigJson + + err = json.Unmarshal(data, &configJson) + + if err != nil { + fmt.Println("Error: Cannot unmarshal JSON: ", err) + return err + } - if configJson.WinscpFlags != "" { - config.WinscpFlags = append([]string{"/rawsettings"}, strings.Fields(configJson.WinscpFlags)...) + if configJson.ClientSecretsFile != "" && configJson.ClientSecretsFile != "default" { + config.ClientSecretsFile = configJson.ClientSecretsFile + } + + if configJson.UserCredentials != "" { + SavedUserCredentials = configJson.UserCredentials + if strings.HasPrefix(SavedUserCredentials, "./") { + SavedUserCredentials = config.AbsPath + "/" + SavedUserCredentials + } + } + + if len(configJson.SSHFlags) > 0 { + config.sshFlags = configJson.SSHFlags + } + + config.Debug = configJson.Debug + + if configJson.Proxy != "" { + config.Proxy = configJson.Proxy + } + + config.UrlFetch = configJson.UrlFetch + + if configJson.WinscpFlags != "" { + config.WinscpFlags = append([]string{"/rawsettings"}, strings.Fields(configJson.WinscpFlags)...) + } + + config.ClientSecretsFile = configJson.ClientSecretsFile } + // fmt.Println("Client Secrets File:", config.ClientSecretsFile) + process_cmdline() return nil } + +func user_config_path(filename string) string { + + // UserCredentials + user, err := user.Current() + if err == nil { + configPath := user.HomeDir + "/.config/cloudshell/" + + _, err := os.Stat(configPath) + if err != nil { + err = os.Mkdir(configPath, os.ModePerm) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + return configPath + filename + } + return filename +} diff --git a/config.json b/config.json index 6fdd01e..17d747a 100644 --- a/config.json +++ b/config.json @@ -1,3 +1,8 @@ { - "client_secrets_file": "" -} + "debug": false, + "ssh_flags": ["-o","LocalForward 127.0.0.1:8080 127.0.0.1:8080","-o","LocalForward 127.0.0.1:8443 127.0.0.1:8443"], + "winscp_flags": "", + "proxy": "", + "urlfetch": "", + "oauth_json_file": "default" +} \ No newline at end of file diff --git a/env.go b/env.go index 2cf7981..146b3f8 100644 --- a/env.go +++ b/env.go @@ -9,14 +9,14 @@ import ( func isWindows() bool { if runtime.GOOS == "windows" { - return true; + return true } return false } func check_os() error { - if config.Debug == true { + if config.Debug { fmt.Println("Runtime Environment:") fmt.Printf(" OS: %s\n", runtime.GOOS) fmt.Printf(" ARCH: %s\n", runtime.GOARCH) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..605673d --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module example.com/m + +go 1.22.3 + +require ( + github.com/kirinlabs/HttpRequest v1.1.2 + github.com/pkg/sftp v1.13.6 + golang.org/x/crypto v0.24.0 + golang.org/x/oauth2 v0.21.0 +) + +require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect + github.com/kr/fs v0.1.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..96e4410 --- /dev/null +++ b/go.sum @@ -0,0 +1,62 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kirinlabs/HttpRequest v1.1.2 h1:W7EkRCTnxwq9PcIMXvITX8rCHfoPNzqR13RObSEe6bI= +github.com/kirinlabs/HttpRequest v1.1.2/go.mod h1:XV38fA4rXZox83tlEV9KIQ7Cdsut319x6NGzVLuRlB8= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index b32182e..503c431 100644 --- a/main.go +++ b/main.go @@ -12,11 +12,12 @@ import ( // This is the file where User Credentails are saved after authorization // This credentials are loaded on program start and refreshed if previously saved -var SavedUserCredentials = "user_credentials.json" -var SavedAdcCredentials = "adc_credentials.json" +// ~/.config/cloudshell/user_credentials.json +var SavedUserCredentials = user_config_path("user_credentials.json") // If you change the scopes, delete the saved user_credentials.json -var SCOPE = "https://www.googleapis.com/auth/cloud-platform openid https://www.googleapis.com/auth/userinfo.email" +// var SCOPE = "https://www.googleapis.com/auth/cloud-platform openid https://www.googleapis.com/auth/userinfo.email" +var SCOPE = "https://www.googleapis.com/auth/cloud-platform" func main() { //************************************************************ @@ -29,6 +30,12 @@ func main() { os.Exit(1) } + // vscode + if config.Command == CMD_SSH_VSCODE { + exec_vscode_ssh() + os.Exit(1) + } + //************************************************************ // //************************************************************ diff --git a/putty.go b/putty.go index 919fec1..3b164a0 100644 --- a/putty.go +++ b/putty.go @@ -18,7 +18,7 @@ func exec_putty(params CloudShellEnv) { sshPort := fmt.Sprint(params.SshPort) sshUrl := sshUsername + "@" + sshHost - if config.Debug == true { + if config.Debug { fmt.Println(key) fmt.Println(sshUsername) fmt.Println(sshHost) diff --git a/scripts-windows/setup_go.bat b/scripts-windows/setup_go.bat index 0e322e0..9551010 100644 --- a/scripts-windows/setup_go.bat +++ b/scripts-windows/setup_go.bat @@ -1,4 +1,5 @@ go get github.com/kirinlabs/HttpRequest go get github.com/pkg/sftp +go get github.com/gookit/color go get golang.org/x/crypto/ssh go get golang.org/x/oauth2/google diff --git a/scripts-windows/vscode_ssh.cmd b/scripts-windows/vscode_ssh.cmd new file mode 100644 index 0000000..2a05a7b --- /dev/null +++ b/scripts-windows/vscode_ssh.cmd @@ -0,0 +1,15 @@ +@echo off +cd %~dp0 +if "%1"=="" ( + type %UserProfile%\.ssh\config | find /i "host " + exit /b +) +set vscode_host="%3" +set port="%2" +if "%vscode_host:cloudshell=%"=="%vscode_host%" ( + ssh %* +) else ( + REM cloudshell.exe ssh -D %port% --urlfetch https://****/urlfetch/ --v2ray + cloudshell.exe ssh -D %port% --proxy 127.0.0.1:1080 --debug + REM cloudshell.exe ssh -D %port% -o LocalForward="127.0.0.1:22080 127.0.0.1:22080" -o ProxyCommand="C:\Program Files\Git\mingw64\bin\connect.exe -S 127.0.0.1:1080 %%h %%p" +) \ No newline at end of file diff --git a/sftp.go b/sftp.go index 7e177f3..529c333 100644 --- a/sftp.go +++ b/sftp.go @@ -7,6 +7,7 @@ import ( "net" "os" "time" + "github.com/pkg/sftp" "golang.org/x/crypto/ssh" "golang.org/x/text/language" @@ -36,7 +37,7 @@ func sftp_download(params CloudShellEnv) { host := sshHost + ":" + sshPort - if config.Debug == true { + if config.Debug { fmt.Println("Dial") fmt.Println(host) } @@ -68,7 +69,7 @@ func sftp_download(params CloudShellEnv) { log.Fatal(err) } defer srcFile.Close() - + // create destination file fmt.Println("create destination file") dstFile, err := os.Create(config.DstFile) @@ -108,7 +109,7 @@ func sftp_upload(params CloudShellEnv) { host := sshHost + ":" + sshPort - if config.Debug == true { + if config.Debug { fmt.Println("Dial") fmt.Println(host) } @@ -146,7 +147,7 @@ func sftp_upload(params CloudShellEnv) { log.Fatal(err) } defer dstFile.Close() - + // copy source file to destination file bytes, err := io.Copy(dstFile, srcFile) if err != nil { @@ -165,7 +166,7 @@ func print_transfer_stats(total_time time.Duration, bytes int64, flag bool) { // Convert to milliseconds ms := int64(total_time) / 1000000 - if (bytes == 0) { + if bytes == 0 { fmt.Printf("%v bytes in %s - Speed 0 KBS\n", bytes, total_time) } else { kbs := bytes / ms diff --git a/sftp_benchmark.go b/sftp_benchmark.go index ec6ad6a..a5bad75 100644 --- a/sftp_benchmark.go +++ b/sftp_benchmark.go @@ -6,11 +6,11 @@ import ( "strconv" "syscall" "time" + "golang.org/x/text/language" "golang.org/x/text/message" ) - func sftp_benchmark_download(params CloudShellEnv) { //************************************************************ // @@ -25,7 +25,7 @@ func sftp_benchmark_download(params CloudShellEnv) { defer connection.Close() defer client.Close() - if config.Debug == true { + if config.Debug { fmt.Println("Connected") } @@ -80,7 +80,7 @@ func sftp_benchmark_download(params CloudShellEnv) { if err != nil { fmt.Println(err) - break; + break } if count != bufsize { @@ -121,7 +121,7 @@ func sftp_benchmark_upload(params CloudShellEnv) { defer connection.Close() defer client.Close() - if config.Debug == true { + if config.Debug { fmt.Println("Connected") } @@ -184,7 +184,7 @@ func sftp_benchmark_upload(params CloudShellEnv) { if err != nil { fmt.Println(err) - break; + break } if count != bufsize { diff --git a/sftp_conn.go b/sftp_conn.go index b21696b..aac55ff 100644 --- a/sftp_conn.go +++ b/sftp_conn.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net" + "github.com/pkg/sftp" "golang.org/x/crypto/ssh" ) @@ -30,7 +31,7 @@ func sftp_open_connection(params CloudShellEnv) (*ssh.Client, *sftp.Client, erro host := sshHost + ":" + sshPort - if config.Debug == true { + if config.Debug { fmt.Println("Dial: " + host) } diff --git a/ssh.go b/ssh.go index f3b81d1..f59aadd 100644 --- a/ssh.go +++ b/ssh.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "time" + "golang.org/x/crypto/ssh" ) @@ -68,7 +69,7 @@ func exec_command(params CloudShellEnv) { defer connection.Close() - if config.Debug == true { + if config.Debug { fmt.Println("Connect") } @@ -88,7 +89,7 @@ func exec_command(params CloudShellEnv) { session.Stdout = &stdoutBuf session.Stderr = &stderrBuf - if config.Debug == true { + if config.Debug { fmt.Println("Run Command:", config.RemoteCommand) } @@ -110,7 +111,7 @@ func exec_ssh(params CloudShellEnv) { sshPort := fmt.Sprint(params.SshPort) sshUrl := sshUsername + "@" + sshHost - if config.Debug == true { + if config.Debug { fmt.Println(key) fmt.Println(sshUsername) fmt.Println(sshHost) @@ -118,7 +119,7 @@ func exec_ssh(params CloudShellEnv) { fmt.Println(sshUrl) } - for x:= 0; x < 3; x++ { + for x := 0; x < 3; x++ { if x > 0 { fmt.Println("Retrying ...") diff --git a/webserver.py b/webserver.py deleted file mode 100644 index 837b458..0000000 --- a/webserver.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -This program implements a webserver for receiving the OAuth 2.0 code - -This is a single shot web server. Only one request is processed and then the -web server exits. -""" - -import sys -from http.server import BaseHTTPRequestHandler, HTTPServer -from urllib.parse import urlparse, parse_qsl - -# The listening port can be anything but must be available -web_port = int(9000) - -flag_continue = True - -class ProcessRequests(BaseHTTPRequestHandler): - """ - Web server callback function to process GET, POST, etc. - """ - - def log_message(self, format, *args): - """ - This function noops the log message for requests received. - """ - return - - def do_GET(self): - """ - Process the web server GET request. - """ - - rcvd_code = None - - # - # process the query parameters. We are looking for a "code". - # - - sys.stderr.write('Query: {}\n'.format(urlparse(self.path).query)) - - items = parse_qsl(urlparse(self.path).query) - - for item in items: - if 'code' in item: - global flag_continue - - sys.stderr.write('Found item: {} = {}\n'.format(item[0], item[1])) - sys.stdout.write(item[1]) - rcvd_code = item[1] - - flag_continue = False - - if 'error' in item: - sys.stderr.write('Found item: {} = {}\n'.format(item[0], item[1])) - sys.stderr.write(item[1] + '\n') - - if rcvd_code is None: - sys.stderr.write('Error: Invalid request: {}\n'.format(self.path)) - sys.stderr.write('Error: Request does not include query param "code=value"') - self.send_response(404) - self.send_header('Content-type', 'text/html') - self.end_headers() - return - - # Notice that this url is 'https' which must be specified for the redirect - # FIX - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - - html = b""" -" -Please return to the app.") - """ - self.wfile.write(html) - -def run_local_webserver(server_class=HTTPServer, handler_class=ProcessRequests, port=web_port): - """ - This function implements a web server. Once the web browser calls this server - with a 'code', the server prints the code and exits. - """ - - server_address = ('', port) - - try: - httpd = server_class(server_address, handler_class) - except: - e_type = sys.exc_info()[0] - e_msg = sys.exc_info()[1] - sys.stderr.write('\n') - sys.stderr.write('****************************************\n') - sys.stderr.write('Error: Cannot start local web server on port {}\n'. format(web_port)) - sys.stderr.write('Error: %s\n', e_type) - sys.stderr.write('Error: %s\n', e_msg) - exit(1) - - # sys.stderr.write('Starting httpd...\n') - - sys.stderr.write('Listening on port {} ...\n'.format(web_port)) - while flag_continue: - httpd.handle_request() - - # All done. - -if __name__ == "__main__": - # print('Hello world from Python') - - run_local_webserver() - - exit(0) diff --git a/winscp.go b/winscp.go index 299ba46..55c7f8c 100644 --- a/winscp.go +++ b/winscp.go @@ -5,7 +5,7 @@ import ( "os/exec" ) -var path_winscp = "C:\\Program Files (x86)\\WinSCP\\WinSCP.exe" +var path_winscp = "C:\\Program Files (x86)\\WinSCP\\WinSCP.exe" func exec_winscp(params CloudShellEnv) { key, err := env_get_ssh_ppk() @@ -22,7 +22,7 @@ func exec_winscp(params CloudShellEnv) { args := append([]string{"/ini=nul", "/privatekey=" + key, "/hostkey=*", sshUrl}, config.WinscpFlags...) - if config.Debug == true { + if config.Debug { fmt.Println(key) fmt.Println(sshUsername) fmt.Println(sshHost) diff --git a/winssh.go b/winssh.go index 6cd19d5..777f782 100644 --- a/winssh.go +++ b/winssh.go @@ -2,13 +2,22 @@ package main import ( "fmt" + "net" + "net/http" "os/exec" + "time" + "strconv" + + "io" + "os" + "runtime" // "golang.org/x/crypto/ssh" - "github.com/docker/machine/libmachine/ssh" + // "github.com/docker/machine/libmachine/ssh" ) -var path_winssh = "C:/Windows/System32/OpenSSH/ssh.exe" +// var path_winssh = "C:/Windows/System32/OpenSSH/ssh.exe" +var proxy *exec.Cmd func exec_winssh(params CloudShellEnv) { key, err := env_get_ssh_pkey() @@ -23,7 +32,7 @@ func exec_winssh(params CloudShellEnv) { sshPort := fmt.Sprint(params.SshPort) sshUrl := sshUsername + "@" + sshHost - if config.Debug == true { + if config.Debug { fmt.Println(key) fmt.Println(sshUsername) fmt.Println(sshHost) @@ -31,53 +40,443 @@ func exec_winssh(params CloudShellEnv) { fmt.Println(sshUrl) } - if config.Debug == true { - fmt.Println("cmd.exe /C start " + path_winssh + " " + sshUrl + " -p " + sshPort + " -i " + key) + // if config.Debug { + // fmt.Println("cmd.exe /C start " + path_winssh + " " + sshUrl + " -p " + sshPort + " -i " + key) + // } + + // cmd := exec.Command("cmd.exe", "/C", "start", path_winssh, sshUrl, "-p", sshPort, "-i", key) + + if config.Proxy == "v2ray" { + if config.Debug { + fmt.Println("Proxy: V2ray") + } + + CheckUrlConfig(sshHost, sshPort) + V2ray(sshHost, sshPort) + CheckPort("127.0.0.1", "8022") + } else if config.Proxy == "shadowsocks" { + if config.Debug { + fmt.Println("Proxy: shadowsocks") + } + + ShadowSocks(sshHost, sshPort) + CheckPort("127.0.0.1", "8022") + } else if config.Proxy != "" { + config.sshFlags = append(config.sshFlags, "-o", "ProxyCommand=connect.exe -S "+config.Proxy+" %h %p") + } + + sshBinaryPath, err := exec.LookPath(config.PluginsPath + "/ssh") + if err != nil { + sshBinaryPath, err = exec.LookPath("ssh") + if err != nil { + if runtime.GOOS != "windows" { + sshBinaryPath = "ssh" + } else { + sshBinaryPath = "ssh.exe" + } + } } - cmd := exec.Command("cmd.exe", "/C", "start", path_winssh, sshUrl, "-p", sshPort, "-i", key) + if config.Debug { + fmt.Println("Use:", sshBinaryPath) + } - err = cmd.Start() + auth := Auth{Keys: []string{key}} + sshPortInt, _ := strconv.Atoi(sshPort) + client, err := NewExternalClient(sshBinaryPath, sshUsername, sshHost, sshPortInt, &auth, config.sshFlags) if err != nil { - fmt.Println(err) + fmt.Println("Failed to create new client - ", err) return } + + // Set heartbeats + go client.HeartBeats() + + // "curl -sSL https://raw.githubusercontent.com/ixiumu/google-cloud-shell-cli-go/patch-1/scripts-remote/heartbeats | sh & bash" + err = client.Shell() + if err != nil && config.Debug && err.Error() != "exit status 255" { + fmt.Println("Failed to request shell - ", err) + // return + } + + if config.Proxy == "v2ray" || config.Proxy == "shadowsocks" { + proxy.Process.Kill() + } + } -func exec_inline_ssh(params CloudShellEnv) { - key, err := env_get_ssh_pkey() +func exec_vscode_ssh() { + sshBinaryPath, err := exec.LookPath(config.PluginsPath + "/ssh") if err != nil { - fmt.Println("\nTip: Run the command: \"gcloud alpha cloud-shell ssh --dry-run\" to setup Cloud Shell SSH keys") + sshBinaryPath, err = exec.LookPath("ssh") + if err != nil { + if runtime.GOOS != "windows" { + sshBinaryPath = "ssh" + } else { + sshBinaryPath = "ssh.exe" + } + } + } + + cmd := exec.Command(sshBinaryPath, config.sshFlags...) + + // log.Debug(cmd) + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil && err.Error() != "exit status 255" { + fmt.Println("Failed to request shell - ", err) + // return + } + +} + +func V2ray(sshHost string, sshPort string) { + ssBinaryPath, err := exec.LookPath(config.PluginsPath + "/v2ray/v2ray.exe") + if err != nil { + fmt.Println("Failed to create proxy client - ", err) return } - sshUsername := params.SshUsername - sshHost := params.SshHost - sshPort := fmt.Sprint(params.SshPort) - sshUrl := sshUsername + "@" + sshHost + url := "http://" + sshHost + ":" + sshPort + "/api/v2ray.json" - if config.Debug == true { - fmt.Println(key) - fmt.Println(sshUsername) - fmt.Println(sshHost) - fmt.Println(sshPort) - fmt.Println(sshUrl) + proxy = exec.Command(ssBinaryPath, "-config", url) + + err = proxy.Start() + if err != nil { + fmt.Println("Failed to create proxy client - ", err) + return } - auth := ssh.Auth{Keys: []string{key}} - sshPortInt, err := strconv.Atoi(sshPort) - client, err := ssh.NewClient(sshUsername, sshHost, sshPortInt, &auth) + if config.Debug { + fmt.Println("Proxy Config:", url) + } + + // config.sshFlags = append(config.sshFlags, "-o", "ProxyCommand=\"C:\\Program Files\\Git\\mingw64\\bin\\connect.exe -S 127.0.0.1:8022 %h %p\"") + + config.sshFlags = append(config.sshFlags, "-o", "ProxyCommand=connect.exe -S 127.0.0.1:8022 %h %p") + + // https://stackoverflow.com/questions/34730941/ensure-executables-called-in-go-process-get-killed-when-process-is-killed + +} + +func ShadowSocks(sshHost string, sshPort string) { + // ss-local -s 1.1.1.1 -p 6000 -k 7758521 -m rc4-md5 -l 8022 --plugin v2ray-plugin --plugin-opts "tls;path=/api/shadowsocks/;host=localhost" + ssArgs := []string{"-m", "rc4-md5", "-l", "8022", "--plugin-opts", "tls;path=/api/shadowsocks/;host=localhost"} + + ssArgs = append(ssArgs, "-k", "7758521") + ssArgs = append(ssArgs, "-s", sshHost) + ssArgs = append(ssArgs, "-p", sshPort) + + ssBinaryPath, err := exec.LookPath(config.PluginsPath + "/shadowsocks-libev/ss-local.exe") + + // fmt.Println(ssBinaryPath) + if err != nil { - fmt.Errorf("Failed to create new client - %s", err) + fmt.Println("Failed to create proxy client - ", err) return } - err = client.Shell() - if err != nil && err.Error() != "exit status 255" { - fmt.Errorf("Failed to request shell - %s", err) + ssPluginBinaryPath, err := exec.LookPath(config.PluginsPath + "/shadowsocks-libev/v2ray-plugin.exe") + if err != nil { + fmt.Println("Failed to find proxy plugin - ", err) return } + ssArgs = append(ssArgs, "--plugin", ssPluginBinaryPath) + + proxy = exec.Command(ssBinaryPath, ssArgs...) + // output, err := proxy.CombinedOutput() + // fmt.Println(string(output)) + // return + err = proxy.Start() + if err != nil { + fmt.Println("Failed to create proxy client - ", err) + return + } + + if config.Debug { + fmt.Println("Proxy:", ssArgs) + } + + config.sshFlags = append(config.sshFlags, "-o", "ProxyCommand=connect.exe -S 127.0.0.1:8022 %h %p") + } + +func CheckUrlConfig(sshHost string, sshPort string) { + for x := 0; x < 60; x++ { + time.Sleep(500 * time.Millisecond) + + resp, err := http.Get("http://" + sshHost + ":" + sshPort + "/api/v2ray.json") + + if err != nil { + return + } + + defer resp.Body.Close() + + // if resp.StatusCode != http.StatusOK { + // if config.Debug { + // fmt.Println("Error: status code", resp.StatusCode) + // } + // continue + // } + + if resp.StatusCode == 200 { + if config.Debug { + fmt.Println("CheckPort: status code", resp.StatusCode) + } + break + } + } +} + +func CheckPort(host string, port string) { + for x := 0; x < 60; x++ { + time.Sleep(500 * time.Millisecond) + + timeout := time.Second + conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout) + if err != nil { + if config.Debug { + fmt.Println("Connecting error:", err) + } + continue + } + if conn != nil { + defer conn.Close() + if config.Debug { + fmt.Println("Opened", net.JoinHostPort(host, port)) + } + break + } + } +} + +func (client *ExternalClient) HeartBeats() { + + time.Sleep(10 * time.Second) + + if config.Debug { + fmt.Print("Start Send HeartBeats") + } + + for { + args := append(client.BaseArgs, "curl -I -H \"Devshell-Vm-Ip-Address:${DEVSHELL_IP_ADDRESS}\" -X POST -s -w %{http_code} -o /dev/null ${DEVSHELL_SERVER_URL}/devshell/vmheartbeat") + + cmd := getSSHCmd(client.BinaryPath, args...) + output, err := cmd.CombinedOutput() + + if err != nil { + fmt.Print("HeartBeats error: ", err) + } + + if config.Debug { + fmt.Print("HeartBeats: ", string(output)) + } + + time.Sleep(360 * time.Second) + } +} + +// "github.com/docker/machine/libmachine/ssh" + +type ExternalClient struct { + BaseArgs []string + BinaryPath string + cmd *exec.Cmd +} + +type Auth struct { + Passwords []string + Keys []string +} + +const ( + External ClientType = "external" + Native ClientType = "native" +) + +type ClientType string + +var ( + baseSSHArgs = []string{ + // "-F", "/dev/null", // disable load ~/.ssh/config + "-o", "ConnectionAttempts=5", // retry 5 times if SSH connection fails + // "-o", "ConnectTimeout=5", // timeout after 5 seconds + "-o", "ControlMaster=no", // disable ssh multiplexing + "-o", "ControlPath=none", + "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." + "-o", "PasswordAuthentication=no", + "-o", "ServerAliveInterval=30", // prevents connection to be dropped if command takes too long + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + } + // defaultClientType = External +) + +func NewExternalClient(sshBinaryPath, user, host string, port int, auth *Auth, flags []string) (*ExternalClient, error) { + client := &ExternalClient{ + BinaryPath: sshBinaryPath, + } + + args := append(baseSSHArgs, fmt.Sprintf("%s@%s", user, host)) + + // If no identities are explicitly provided, also look at the identities + // offered by ssh-agent + if len(auth.Keys) > 0 { + args = append(args, "-o", "IdentitiesOnly=yes") + } + + // Specify which private keys to use to authorize the SSH request. + for _, privateKeyPath := range auth.Keys { + if privateKeyPath != "" { + // Check each private key before use it + fi, err := os.Stat(privateKeyPath) + if err != nil { + // Abort if key not accessible + return nil, err + } + if runtime.GOOS != "windows" { + mode := fi.Mode() + // log.Debugf("Using SSH private key: %s (%s)", privateKeyPath, mode) + // Private key file should have strict permissions + perm := mode.Perm() + if perm&0400 == 0 { + return nil, fmt.Errorf("'%s' is not readable", privateKeyPath) + } + if perm&0077 != 0 { + return nil, fmt.Errorf("permissions %#o for '%s' are too open", perm, privateKeyPath) + } + } + args = append(args, "-i", privateKeyPath) + } + } + + // Set which port to use for SSH. + args = append(args, "-p", fmt.Sprintf("%d", port)) + + if len(flags) > 0 { + args = append(args, flags...) + // fmt.Println(args) + } + + client.BaseArgs = args + + if config.Debug { + fmt.Println("sshArgs:", client.BaseArgs) + } + + return client, nil +} + +func getSSHCmd(binaryPath string, args ...string) *exec.Cmd { + return exec.Command(binaryPath, args...) +} + +func (client *ExternalClient) Output(command string) (string, error) { + args := append(client.BaseArgs, command) + cmd := getSSHCmd(client.BinaryPath, args...) + output, err := cmd.CombinedOutput() + return string(output), err +} + +func (client *ExternalClient) Shell(args ...string) error { + args = append(client.BaseArgs, args...) + cmd := getSSHCmd(client.BinaryPath, args...) + + // log.Debug(cmd) + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +func (client *ExternalClient) Start(command string) (io.ReadCloser, io.ReadCloser, error) { + args := append(client.BaseArgs, command) + cmd := getSSHCmd(client.BinaryPath, args...) + + // log.Debug(cmd) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, nil, err + } + stderr, err := cmd.StderrPipe() + if err != nil { + if closeErr := stdout.Close(); closeErr != nil { + return nil, nil, fmt.Errorf("%s, %s", err, closeErr) + } + return nil, nil, err + } + if err := cmd.Start(); err != nil { + stdOutCloseErr := stdout.Close() + stdErrCloseErr := stderr.Close() + if stdOutCloseErr != nil || stdErrCloseErr != nil { + return nil, nil, fmt.Errorf("%s, %s, %s", + err, stdOutCloseErr, stdErrCloseErr) + } + return nil, nil, err + } + + client.cmd = cmd + return stdout, stderr, nil +} + +func (client *ExternalClient) Wait() error { + err := client.cmd.Wait() + client.cmd = nil + return err +} + +func closeConn(c io.Closer) { + err := c.Close() + if err != nil { + // log.Debugf("Error closing SSH Client: %s", err) + fmt.Println("Error closing SSH Client: ", err) + } +} + +// func exec_inline_ssh(params CloudShellEnv) { +// key, err := env_get_ssh_pkey() + +// if err != nil { +// fmt.Println("\nTip: Run the command: \"gcloud alpha cloud-shell ssh --dry-run\" to setup Cloud Shell SSH keys") +// return +// } + +// sshUsername := params.SshUsername +// sshHost := params.SshHost +// sshPort := fmt.Sprint(params.SshPort) +// sshUrl := sshUsername + "@" + sshHost + +// if config.Debug { +// fmt.Println(key) +// fmt.Println(sshUsername) +// fmt.Println(sshHost) +// fmt.Println(sshPort) +// fmt.Println(sshUrl) +// } + +// auth := ssh.Auth{Keys: []string{key}} +// sshPortInt, err := strconv.Atoi(sshPort) +// client, err := ssh.NewClient(sshUsername, sshHost, sshPortInt, &auth) +// if err != nil { +// fmt.Errorf("Failed to create new client - %s", err) +// return +// } + +// err = client.Shell() +// if err != nil && err.Error() != "exit status 255" { +// fmt.Errorf("Failed to request shell - %s", err) +// return +// } + +// }