Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
Added auth to client, tribe and cli.
Browse files Browse the repository at this point in the history
  • Loading branch information
IRCody committed Mar 4, 2016
1 parent 8a31dfd commit d6a1fbe
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 66 deletions.
15 changes: 9 additions & 6 deletions cmd/snapctl/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,15 @@ var (
},
},
}

tribeWarning = "Can only be used when tribe mode is enabled."
tribeCommands = []cli.Command{
{
Name: "member",
Name: "member",
Usage: tribeWarning,
Subcommands: []cli.Command{
{
Name: "list",
Usage: "list",
Usage: "list" + tribeWarning,
Action: listMembers,
},
{
Expand All @@ -162,7 +163,8 @@ var (
},
},
{
Name: "agreement",
Name: "agreement",
Usage: tribeWarning,
Subcommands: []cli.Command{
{
Name: "list",
Expand Down Expand Up @@ -197,11 +199,12 @@ var (
},
},
{
Name: "plugin-config",
Name: "plugin-config",
Usage: tribeWarning,
Subcommands: []cli.Command{
{
Name: "get",
Usage: "get <plugin_type>:<plugin_name>:<plugin_version> or unload -t <plugin_type> -n <plugin_version> -v <plugin_version>",
Usage: "get <plugin_type>:<plugin_name>:<plugin_version> or get -t <plugin_type> -n <plugin_version> -v <plugin_version>",
Action: getConfig,
Flags: []cli.Flag{
flPluginName,
Expand Down
10 changes: 10 additions & 0 deletions cmd/snapctl/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ var (
Name: "running",
Usage: "Shows running plugins",
}
flPassword = cli.BoolFlag{
Name: "password, p",
Usage: "Password for REST API authentication",
}
flConfig = cli.StringFlag{
Name: "config, c",
EnvVar: "SNAPCTL_CONFIG_PATH",
Usage: "Path to a config file",
Value: "",
}

// Plugin flags
flPluginAsc = cli.StringFlag{
Expand Down
122 changes: 73 additions & 49 deletions cmd/snapctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ limitations under the License.
package main

import (
"flag"
"fmt"
"os"
"sort"
"strings"
"time"

"golang.org/x/crypto/ssh/terminal"

"github.com/codegangsta/cli"
"github.com/intelsdi-x/snap/mgmt/rest/client"
)
Expand All @@ -42,64 +44,86 @@ func main() {
app.Name = "snapctl"
app.Version = gitversion
app.Usage = "A powerful telemetry framework"
app.Flags = []cli.Flag{flURL, flSecure, flAPIVer}
app.Commands = commands
app.Flags = []cli.Flag{flURL, flSecure, flAPIVer, flPassword, flConfig}
app.Commands = append(commands, tribeCommands...)
sort.Sort(ByCommand(app.Commands))
app.Before = beforeAction
app.Run(os.Args)
}

func init() {
f1 := flag.NewFlagSet("f1", flag.ContinueOnError)
prtURL := f1.String("url", flURL.Value, flURL.Usage)
prtU := f1.String("u", flURL.Value, flURL.Usage)
prtAv := f1.String("api-version", flAPIVer.Value, flAPIVer.Usage)
prtA := f1.String("a", flAPIVer.Value, flAPIVer.Usage)
prti := f1.Bool("insecure", false, flSecure.Usage)

url := flURL.Value
ver := flAPIVer.Value
secure := false
// Run before every command
func beforeAction(ctx *cli.Context) error {
username, password := checkForAuth(ctx)
pClient, err = client.New(ctx.String("url"), ctx.String("api-version"), ctx.Bool("insecure"))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
pClient.Password = password
pClient.Username = username
if err = checkTribeCommand(ctx); err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
}

for idx, a := range os.Args {
switch a {
case "--url":
if len(os.Args) >= idx+2 {
if err := f1.Parse(os.Args[idx : idx+2]); err == nil {
url = *prtURL
}
}
case "-u":
if len(os.Args) >= idx+2 {
if err := f1.Parse(os.Args[idx : idx+2]); err == nil {
url = *prtU
}
}
case "--api-version":
if len(os.Args) >= idx+2 {
if err := f1.Parse(os.Args[idx : idx+2]); err == nil {
ver = *prtAv
}
}
case "-a":
if len(os.Args) >= idx+2 {
if err := f1.Parse(os.Args[idx : idx+2]); err == nil {
ver = *prtA
}
}
case "--insecure":
if err := f1.Parse([]string{os.Args[idx]}); err == nil {
secure = *prti
// Checks if a tribe command was issued when tribe mode was not
// enabled on the specified snapd instance.
func checkTribeCommand(ctx *cli.Context) error {
tribe := false
for _, a := range os.Args {
for _, command := range tribeCommands {
if strings.Contains(a, command.Name) {
tribe = true
break
}
}
if tribe {
break
}
}
pClient, err = client.New(url, ver, secure)
if err != nil {
fmt.Println(err)
os.Exit(1)
if !tribe {
return nil
}
resp := pClient.ListAgreements()
if resp.Err == nil {
commands = append(commands, tribeCommands...)
if resp.Err != nil {
if resp.Err.Error() == "Invalid credentials" {
return resp.Err
}
return fmt.Errorf("Tribe mode must be enabled in snapd to use tribe command")
}
return nil
}

// Checks for authentication flags and returns a username/password
// from the specified settings
func checkForAuth(ctx *cli.Context) (username, password string) {
if ctx.IsSet("password") {
username = "snap" // for now since username is unused but needs to exist for basicAuth
// Prompt for password
fmt.Print("Password:")
pass, err := terminal.ReadPassword(0)
if err != nil {
password = ""
} else {
password = string(pass)
}
// Go to next line after password prompt
fmt.Println()
return
}
//Get config file path in the order:
if ctx.IsSet("config") {
cfg := &config{}
if err := cfg.loadConfig(ctx.String("config")); err != nil {
fmt.Println(err)
}
if cfg.RestAPI.Password != nil {
password = *cfg.RestAPI.Password
} else {
fmt.Println("Error config password field 'rest-auth-pwd' is empty")
}
}
return
}
82 changes: 75 additions & 7 deletions mgmt/rest/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type Client struct {
// prefix is the string concatenation of a request URL, forward slash
// and the request client version.
prefix string
// Basic http auth username/password
Username string
Password string
}

// Checks validity of URL
Expand All @@ -82,9 +85,25 @@ func parseURL(url string) error {
return nil
}

type metaOp func(c *Client)

//Password is an option than can be provided to the func client.New.
func Password(p string) metaOp {
return func(c *Client) {
c.Password = strings.TrimSpace(p)
}
}

//Username is an option that can be provided to the func client.New.
func Username(u string) metaOp {
return func(c *Client) {
c.Username = u
}
}

// New returns a pointer to a snap api client
// if ver is an empty string, v1 is used by default
func New(url, ver string, insecure bool) (*Client, error) {
func New(url, ver string, insecure bool, opts ...metaOp) (*Client, error) {
if err := parseURL(url); err != nil {
return nil, err
}
Expand All @@ -103,6 +122,9 @@ func New(url, ver string, insecure bool) (*Client, error) {
},
},
}
for _, opt := range opts {
opt(c)
}
c.prefix = url + "/" + ver
return c, nil
}
Expand All @@ -112,6 +134,18 @@ func (t contentType) String() string {
return contentTypes[t]
}

/*
Add's auth info to request if password is set.
*/
func addAuth(req *http.Request, username, password string) {
if password != "" {
if username == "" {
username = "snap"
}
req.SetBasicAuth(username, password)
}
}

/*
do handles all interactions with snap's REST API.
we use the variadic function signature so that all actions can use the same
Expand All @@ -122,10 +156,16 @@ func (c *Client) do(method, path string, ct contentType, body ...[]byte) (*rbody
var (
rsp *http.Response
err error
req *http.Request
)
switch method {
case "GET":
rsp, err = c.http.Get(c.prefix + path)
req, err = http.NewRequest(method, c.prefix+path, nil)
if err != nil {
return nil, err
}
addAuth(req, c.Username, c.Password)
rsp, err = c.http.Do(req)
if err != nil {
if strings.Contains(err.Error(), "tls: oversized record") || strings.Contains(err.Error(), "malformed HTTP response") {
return nil, fmt.Errorf("error connecting to API URI: %s. Do you have an http/https mismatch?", c.URL)
Expand All @@ -139,11 +179,13 @@ func (c *Client) do(method, path string, ct contentType, body ...[]byte) (*rbody
} else {
b = bytes.NewReader(body[0])
}
req, err := http.NewRequest("PUT", c.prefix+path, b)
req.Header.Add("Content-Type", ct.String())
req, err = http.NewRequest(method, c.prefix+path, b)
if err != nil {
return nil, fmt.Errorf("URL target is not available. %v", err)
}
addAuth(req, c.Username, c.Password)
req.Header.Add("Content-Type", ct.String())

rsp, err = c.http.Do(req)
if err != nil {
if strings.Contains(err.Error(), "tls: oversized record") || strings.Contains(err.Error(), "malformed HTTP response") {
Expand All @@ -158,11 +200,13 @@ func (c *Client) do(method, path string, ct contentType, body ...[]byte) (*rbody
} else {
b = bytes.NewReader(body[0])
}
req, err := http.NewRequest("DELETE", c.prefix+path, b)
req.Header.Add("Content-Type", "application/json")

req, err = http.NewRequest(method, c.prefix+path, b)
if err != nil {
return nil, fmt.Errorf("URL target is not available. %v", err)
}
addAuth(req, c.Username, c.Password)
req.Header.Add("Content-Type", "application/json")
rsp, err = c.http.Do(req)
if err != nil {
if strings.Contains(err.Error(), "tls: oversized record") || strings.Contains(err.Error(), "malformed HTTP response") {
Expand All @@ -177,7 +221,13 @@ func (c *Client) do(method, path string, ct contentType, body ...[]byte) (*rbody
} else {
b = bytes.NewReader(body[0])
}
rsp, err = c.http.Post(c.prefix+path, ct.String(), b)
req, err = http.NewRequest(method, c.prefix+path, b)
if err != nil {
return nil, err
}
addAuth(req, c.Username, c.Password)
req.Header.Add("Content-Type", ct.String())
rsp, err = c.http.Do(req)
if err != nil {
if strings.Contains(err.Error(), "tls: oversized record") || strings.Contains(err.Error(), "malformed HTTP response") {
return nil, fmt.Errorf("error connecting to API URI: %s. Do you have an http/https mismatch?", c.URL)
Expand All @@ -190,6 +240,9 @@ func (c *Client) do(method, path string, ct contentType, body ...[]byte) (*rbody
}

func httpRespToAPIResp(rsp *http.Response) (*rbody.APIResponse, error) {
if rsp.StatusCode == 401 {
return nil, fmt.Errorf("Invalid credentials")
}
resp := new(rbody.APIResponse)
b, err := ioutil.ReadAll(rsp.Body)
rsp.Body.Close()
Expand Down Expand Up @@ -248,6 +301,7 @@ func (c *Client) pluginUploadRequest(pluginPaths []string) (*rbody.APIResponse,
go writePluginToWriter(pw, bufins, writer, paths, errChan)

req, err := http.NewRequest("POST", c.prefix+"/plugins", pr)
addAuth(req, c.Username, c.Password)
if err != nil {
return nil, fmt.Errorf("URL target is not available. %v", err)
}
Expand Down Expand Up @@ -309,3 +363,17 @@ func writePluginToWriter(pw io.WriteCloser, bufin []*bufio.Reader, writer *multi
}
errChan <- nil
}

// Passthrough for tribe request to allow use of client auth.
func (c *Client) TribeRequest() (*http.Response, error) {
req, err := http.NewRequest("GET", c.URL, nil)
if err != nil {
return nil, err
}
addAuth(req, "snap", c.Password)
rsp, err := c.http.Do(req)
if err != nil {
return nil, err
}
return rsp, nil
}
Loading

0 comments on commit d6a1fbe

Please sign in to comment.