From 12689fbf09b4c9e7d9a4b10cafc8d39f194b7b39 Mon Sep 17 00:00:00 2001 From: Jorge Alarcon Ochoa Date: Sat, 27 Oct 2018 17:31:10 -0500 Subject: [PATCH] Add client creation and subscritpion Resolves #3 Signed-off-by: Jorge Alarcon Ochoa --- .gitignore | 1 + tacc-keys/README.md | 31 ++----- tacc-keys/cmd/refresh_tokens.go | 10 ++- tacc-keys/cmd/root.go | 9 +- tacc-keys/cmd/subscribe.go | 95 ++++++++++++++++++++ tacc-keys/docs/tacc-keys.md | 31 +++++++ tacc-keys/{ => docs}/tacc-keys_completion.md | 4 +- tacc-keys/{ => docs}/tacc-keys_delete.md | 4 +- tacc-keys/{ => docs}/tacc-keys_docs.md | 4 +- tacc-keys/{ => docs}/tacc-keys_list.md | 4 +- tacc-keys/{ => docs}/tacc-keys_post.md | 4 +- tacc-keys/docs/tacc-keys_subscribe.md | 33 +++++++ tacc-keys/tacc-services/credentials.go | 31 +++++++ tacc-keys/tacc-services/oauth_clients.go | 72 +++++++++++++++ tacc-keys/tacc-services/subscribe_client.go | 54 +++++++++++ tacc-keys/tacc-services/tacc-tokens.go | 70 +++++++++++++-- 16 files changed, 415 insertions(+), 42 deletions(-) create mode 100644 tacc-keys/cmd/subscribe.go create mode 100644 tacc-keys/docs/tacc-keys.md rename tacc-keys/{ => docs}/tacc-keys_completion.md (83%) rename tacc-keys/{ => docs}/tacc-keys_delete.md (78%) rename tacc-keys/{ => docs}/tacc-keys_docs.md (73%) rename tacc-keys/{ => docs}/tacc-keys_list.md (78%) rename tacc-keys/{ => docs}/tacc-keys_post.md (82%) create mode 100644 tacc-keys/docs/tacc-keys_subscribe.md create mode 100644 tacc-keys/tacc-services/credentials.go create mode 100644 tacc-keys/tacc-services/oauth_clients.go create mode 100644 tacc-keys/tacc-services/subscribe_client.go diff --git a/.gitignore b/.gitignore index a0cf1cc..0e1ef95 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ tacc-keys/.tacc-keys.json +tacc-keys/.tacc-keys.yaml diff --git a/tacc-keys/README.md b/tacc-keys/README.md index 221d554..1be9ddd 100644 --- a/tacc-keys/README.md +++ b/tacc-keys/README.md @@ -1,25 +1,12 @@ -## tacc-keys +# Oficial client for TACC's keys service -Client for TACC's public ssh keys service +Hello, if you have found yourself here then you are probably in need of a +client for TACC's public ssh keys service. -### Synopsis +If this is so, then you have found the perfect tool! +This client will allow you to interact with the keys service by listing keys, +creating public and private key pairs, posting public keys, deleting them, +and managing the oauth client you need to interact with TACC. -Client for TACC's public ssh keys service. - - It allows you to create, list, publish, and delete public ssh keys from the - keys service - -### Options - -``` - --config string config file (default is $HOME/.tacc-keys.yaml) - -h, --help help for tacc-keys -``` - -### SEE ALSO - -* [tacc-keys completion](tacc-keys_completion.md) - Generate bash completion scripts and documentation -* [tacc-keys delete](tacc-keys_delete.md) - Delete a public ssh key from TACC's keys service -* [tacc-keys docs](tacc-keys_docs.md) - Generate markdown documentation -* [tacc-keys list](tacc-keys_list.md) - List all public ssh keys registered to a user -* [tacc-keys post](tacc-keys_post.md) - Post a public ssh key to TACC's keys service +To see how you can get started, +[read the short documentation for tacc-keys](./docs/tacc-keys.md). diff --git a/tacc-keys/cmd/refresh_tokens.go b/tacc-keys/cmd/refresh_tokens.go index a1198c8..34b4897 100644 --- a/tacc-keys/cmd/refresh_tokens.go +++ b/tacc-keys/cmd/refresh_tokens.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" "os" + "strconv" "time" "github.com/TACC-Cloud/ssh-keys-client/tacc-keys/tacc-services" @@ -36,13 +37,14 @@ func refreshTokenPair() error { now := time.Now().Unix() - 100 if (createdAt + expiresIn) < now { fmt.Fprintln(os.Stderr, "Refreshing token...") - newTokens, err := services.RefreshToken(baseURL, refreshToken, apiKey, apiSecret) + access, refresh, err := services.RefreshToken( + baseURL, refreshToken, apiKey, apiSecret) if err != nil { return err } - viper.Set("access_token", newTokens.AccessToken) - viper.Set("refresh_token", newTokens.RefreshToken) - viper.Set("created_at", newTokens.CreatedAt) + viper.Set("access_token", access) + viper.Set("refresh_token", refresh) + viper.Set("created_at", strconv.FormatInt(time.Now().Unix(), 10)) viper.WriteConfig() } diff --git a/tacc-keys/cmd/root.go b/tacc-keys/cmd/root.go index 15d7e1f..09a8535 100644 --- a/tacc-keys/cmd/root.go +++ b/tacc-keys/cmd/root.go @@ -29,9 +29,12 @@ var rootCmd = &cobra.Command{ Use: "tacc-keys", Short: "Client for TACC's public ssh keys service", Long: `Client for TACC's public ssh keys service. - - It allows you to create, list, publish, and delete public ssh keys from the - keys service`, +It allows you to create, list, publish, and delete public ssh keys from the +keys service. + +To use this tool, you'll need a configuration file, ".tacc-keys.(yaml|json|toml)". +You can use the "curent.json" file created by TACC-Cloud/agave-cli. +`, } // Execute root command. diff --git a/tacc-keys/cmd/subscribe.go b/tacc-keys/cmd/subscribe.go new file mode 100644 index 0000000..9bf5638 --- /dev/null +++ b/tacc-keys/cmd/subscribe.go @@ -0,0 +1,95 @@ +// Copyright © 2018 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "os" + "strconv" + "time" + + "github.com/TACC-Cloud/ssh-keys-client/tacc-keys/tacc-services" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + name string + description string +) + +// subscribeCmd represents the subscribe command +var subscribeCmd = &cobra.Command{ + Use: "subscribe", + Short: "Create an oauth client and subscribe to TACC's keys service", + Long: `Create a TACC oauth client, obtain an access token, and subscribe the +client to the keys service. +This will also generate a config file if one doesn't already exist.`, + Run: func(cmd *cobra.Command, args []string) { + // Set name to hostname if not defined. + if name == "" { + var err error + name, err = os.Hostname() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + baseURL := "https://api.tacc.utexas.edu" + + // Ge user credentials. + username, password, err := services.Credentials() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Create an oauth client. + key, secret, err := services.CreateClient( + baseURL, name, description, username, password) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Subscribe client to TACC's keys service. + err = services.SubscribeClient(baseURL, name, username, password) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + access, refresh, err := services.GetTokens( + baseURL, key, secret, username, password) + + // Save configurations. + viper.Set("apikey", key) + viper.Set("apisecret", secret) + viper.Set("baseurl", baseURL) + viper.Set("username", username) + viper.Set("access_token", access) + viper.Set("refresh_token", refresh) + viper.Set("created_at", strconv.FormatInt(time.Now().Unix(), 10)) + viper.WriteConfigAs(".tacc-keys.yaml") + }, +} + +func init() { + rootCmd.AddCommand(subscribeCmd) + + subscribeCmd.Flags().StringVarP(&name, "name", "n", "", "Name of aouth client") + subscribeCmd.Flags().StringVarP( + &description, "description", "d", "", "Oauth lient description") +} diff --git a/tacc-keys/docs/tacc-keys.md b/tacc-keys/docs/tacc-keys.md new file mode 100644 index 0000000..085de7b --- /dev/null +++ b/tacc-keys/docs/tacc-keys.md @@ -0,0 +1,31 @@ +## tacc-keys + +Client for TACC's public ssh keys service + +### Synopsis + +Client for TACC's public ssh keys service. +It allows you to create, list, publish, and delete public ssh keys from the +keys service. + +To use this tool, you'll need a configuration file, ".tacc-keys.(yaml|json|toml)". +You can use the "curent.json" file created by TACC-Cloud/agave-cli. + + +### Options + +``` + --config string config file (default is $HOME/.tacc-keys.yaml) + -h, --help help for tacc-keys +``` + +### SEE ALSO + +* [tacc-keys completion](tacc-keys_completion.md) - Generate bash completion scripts and documentation +* [tacc-keys delete](tacc-keys_delete.md) - Delete a public ssh key from TACC's keys service +* [tacc-keys docs](tacc-keys_docs.md) - Generate markdown documentation +* [tacc-keys list](tacc-keys_list.md) - List all public ssh keys registered to a user +* [tacc-keys post](tacc-keys_post.md) - Post a public ssh key to TACC's keys service +* [tacc-keys subscribe](tacc-keys_subscribe.md) - Create an oauth client and subscribe to TACC's keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-keys_completion.md b/tacc-keys/docs/tacc-keys_completion.md similarity index 83% rename from tacc-keys/tacc-keys_completion.md rename to tacc-keys/docs/tacc-keys_completion.md index fe32007..e1bbca2 100644 --- a/tacc-keys/tacc-keys_completion.md +++ b/tacc-keys/docs/tacc-keys_completion.md @@ -33,4 +33,6 @@ tacc-keys completion [flags] ### SEE ALSO -* [tacc-keys](README.md) - Client for TACC's public ssh keys service +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-keys_delete.md b/tacc-keys/docs/tacc-keys_delete.md similarity index 78% rename from tacc-keys/tacc-keys_delete.md rename to tacc-keys/docs/tacc-keys_delete.md index 01d2a15..97d5d63 100644 --- a/tacc-keys/tacc-keys_delete.md +++ b/tacc-keys/docs/tacc-keys_delete.md @@ -25,4 +25,6 @@ tacc-keys delete [key ID] [flags] ### SEE ALSO -* [tacc-keys](README.md) - Client for TACC's public ssh keys service +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-keys_docs.md b/tacc-keys/docs/tacc-keys_docs.md similarity index 73% rename from tacc-keys/tacc-keys_docs.md rename to tacc-keys/docs/tacc-keys_docs.md index a1c9539..097c849 100644 --- a/tacc-keys/tacc-keys_docs.md +++ b/tacc-keys/docs/tacc-keys_docs.md @@ -24,4 +24,6 @@ tacc-keys docs [flags] ### SEE ALSO -* [tacc-keys](README.md) - Client for TACC's public ssh keys service +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-keys_list.md b/tacc-keys/docs/tacc-keys_list.md similarity index 78% rename from tacc-keys/tacc-keys_list.md rename to tacc-keys/docs/tacc-keys_list.md index e2de814..2ffc8b3 100644 --- a/tacc-keys/tacc-keys_list.md +++ b/tacc-keys/docs/tacc-keys_list.md @@ -28,4 +28,6 @@ tacc-keys list [username] [flags] ### SEE ALSO -* [tacc-keys](README.md) - Client for TACC's public ssh keys service +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-keys_post.md b/tacc-keys/docs/tacc-keys_post.md similarity index 82% rename from tacc-keys/tacc-keys_post.md rename to tacc-keys/docs/tacc-keys_post.md index 77a8f5a..d9df02f 100644 --- a/tacc-keys/tacc-keys_post.md +++ b/tacc-keys/docs/tacc-keys_post.md @@ -28,4 +28,6 @@ tacc-keys post [username] [flags] ### SEE ALSO -* [tacc-keys](README.md) - Client for TACC's public ssh keys service +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/docs/tacc-keys_subscribe.md b/tacc-keys/docs/tacc-keys_subscribe.md new file mode 100644 index 0000000..c27d486 --- /dev/null +++ b/tacc-keys/docs/tacc-keys_subscribe.md @@ -0,0 +1,33 @@ +## tacc-keys subscribe + +Create an oauth client and subscribe to TACC's keys service + +### Synopsis + +Create a TACC oauth client, obtain an access token, and subscribe the +client to the keys service. +This will also generate a config file if one doesn't already exist. + +``` +tacc-keys subscribe [flags] +``` + +### Options + +``` + -d, --description string Oauth lient description + -h, --help help for subscribe + -n, --name string Name of aouth client +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.tacc-keys.yaml) +``` + +### SEE ALSO + +* [tacc-keys](tacc-keys.md) - Client for TACC's public ssh keys service + +###### Auto generated by spf13/cobra on 27-Oct-2018 diff --git a/tacc-keys/tacc-services/credentials.go b/tacc-keys/tacc-services/credentials.go new file mode 100644 index 0000000..40731e3 --- /dev/null +++ b/tacc-keys/tacc-services/credentials.go @@ -0,0 +1,31 @@ +package services + +import ( + "bufio" + "fmt" + "os" + "strings" + "syscall" + + "golang.org/x/crypto/ssh/terminal" +) + +// Credentials ask for username and password at runtime. +func Credentials() (string, string, error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Username: ") + username, err := reader.ReadString('\n') + if err != nil { + return "", "", err + } + + fmt.Print("Password: ") + bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", "", err + } + password := string(bytePassword) + + return strings.TrimSpace(username), strings.TrimSpace(password), nil +} diff --git a/tacc-keys/tacc-services/oauth_clients.go b/tacc-keys/tacc-services/oauth_clients.go new file mode 100644 index 0000000..18b63e2 --- /dev/null +++ b/tacc-keys/tacc-services/oauth_clients.go @@ -0,0 +1,72 @@ +package services + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" +) + +type clientResponse struct { + Result clientData `json:"result"` +} + +type clientData struct { + APIKey string `json:"consumerKey"` + APISecret string `json:"consumerSecret"` +} + +// CreateClient creates a TACC oauth client with the provided name and +// description. +func CreateClient(baseURL, name, description, username, password string) (string, string, error) { + // Oauth clients endpoint. + clientEndpoint := baseURL + "/clients/v2" + + // Request data. + v := url.Values{} + v.Set("clientName", name) + v.Set("tier", "Unlimited") + v.Set("description", description) + v.Set("callbackUrl", "") + data := v.Encode() + // Form request. + req, err := http.NewRequest("POST", clientEndpoint, strings.NewReader(data)) + if err != nil { + return "", "", err + } + + // Set headers. + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Basic http authentication. + req.SetBasicAuth(username, password) + + // Create http client. + client := &http.Client{ + Timeout: time.Second * 5, + } + // Make request. + resp, err := client.Do(req) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusCreated { + var client clientResponse + if err := json.NewDecoder(resp.Body).Decode(&client); err != nil { + return "", "", err + } + + return client.Result.APIKey, client.Result.APISecret, nil + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", "", err + } + return "", "", errors.New(string(body)) +} diff --git a/tacc-keys/tacc-services/subscribe_client.go b/tacc-keys/tacc-services/subscribe_client.go new file mode 100644 index 0000000..8ffe214 --- /dev/null +++ b/tacc-keys/tacc-services/subscribe_client.go @@ -0,0 +1,54 @@ +package services + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" +) + +// SubscribeClient a TACC oauth client to TACC's public ssh keys service. +func SubscribeClient(baseURL, name, username, password string) error { + // Oauth client ssubscription endpoint. + clientEndpoint := baseURL + "/clients/v2/" + name + "/subscriptions" + + // Request data. + v := url.Values{} + v.Set("apiName", "PublicKeys") + v.Set("apiVersion", "v2") + v.Set("apiProvider", "admin") + data := v.Encode() + // Form request. + req, err := http.NewRequest("POST", clientEndpoint, strings.NewReader(data)) + if err != nil { + return err + } + + // Set headers. + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Basic http authentication. + req.SetBasicAuth(username, password) + + // Create http client. + client := &http.Client{ + Timeout: time.Second * 5, + } + // Make request. + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + fmt.Println(string(body)) + + return nil +} diff --git a/tacc-keys/tacc-services/tacc-tokens.go b/tacc-keys/tacc-services/tacc-tokens.go index d8f14d4..b7fe3b5 100644 --- a/tacc-keys/tacc-services/tacc-tokens.go +++ b/tacc-keys/tacc-services/tacc-tokens.go @@ -24,7 +24,7 @@ type TokenResponse struct { // RefreshToken makes an API call via HTTP to obtain a a new access and refresh // token pair. -func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (*TokenResponse, error) { +func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (string, string, error) { // Token refresh endpoint. tokenEndpoint := baseURL + "/token" @@ -37,14 +37,14 @@ func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (*TokenRespon // Make request. req, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(data)) if err != nil { - return nil, err + return "", "", err } // Set request headers. req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Basic HTTTP authentication. req.SetBasicAuth(apiKey, apiSecret) - // Create HTTP client with timeout of 10s. + // Create HTTP client with timeout of 5s. client := &http.Client{ Timeout: time.Second * 5, } @@ -52,7 +52,7 @@ func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (*TokenRespon // Make HTTP request. resp, err := client.Do(req) if err != nil { - return nil, err + return "", "", err } defer resp.Body.Close() @@ -60,7 +60,7 @@ func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (*TokenRespon var refreshedToken TokenResponse if resp.StatusCode == http.StatusOK { if err := json.NewDecoder(resp.Body).Decode(&refreshedToken); err != nil { - return nil, err + return "", "", err } // Add creation time. @@ -69,10 +69,64 @@ func RefreshToken(baseURL, refreshToken, apiKey, apiSecret string) (*TokenRespon } else { // API call returned an error. body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, err + return "", "", err } - return nil, errors.New(string(body)) + return "", "", errors.New(string(body)) } - return &refreshedToken, nil + return refreshedToken.AccessToken, refreshedToken.RefreshToken, nil +} + +// GetTokens obtains an access and a refresh token pair. +func GetTokens(baseURL, apiKey, apiSecret, username, password string) (string, string, error) { + // Token refresh endpoint. + tokenEndpoint := baseURL + "/token" + + // Request data. + v := url.Values{} + v.Set("grant_type", "password") + v.Set("scope", "PRODUCTION") + v.Set("username", username) + v.Set("password", password) + data := v.Encode() + // Make request. + req, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(data)) + if err != nil { + return "", "", err + } + // Set request headers. + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + // Basic HTTTP authentication. + req.SetBasicAuth(apiKey, apiSecret) + + // Create HTTP client with timeout of 5s. + client := &http.Client{ + Timeout: time.Second * 5, + } + + // Make HTTP request. + resp, err := client.Do(req) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + // Parse in response. + var tokens TokenResponse + if resp.StatusCode == http.StatusOK { + if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil { + return "", "", err + } + + // Add creation time. + tokens.CreatedAt = strconv.FormatInt(time.Now().Unix(), 10) + + } else { // API call returned an error. + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", "", err + } + return "", "", errors.New(string(body)) + } + return tokens.AccessToken, tokens.RefreshToken, nil }