diff --git a/api/logical.go b/api/logical.go index 5add065ff086..d13daac6e97f 100644 --- a/api/logical.go +++ b/api/logical.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net/url" "os" "github.com/hashicorp/errwrap" @@ -46,8 +47,26 @@ func (c *Client) Logical() *Logical { } func (c *Logical) Read(path string) (*Secret, error) { + return c.ReadWithData(path, nil) +} + +func (c *Logical) ReadWithData(path string, data map[string][]string) (*Secret, error) { r := c.c.NewRequest("GET", "/v1/"+path) + var values url.Values + for k, v := range data { + if values == nil { + values = make(url.Values) + } + for _, val := range v { + values.Add(k, val) + } + } + + if values != nil { + r.Params = values + } + ctx, cancelFunc := context.WithCancel(context.Background()) defer cancelFunc() resp, err := c.c.RawRequestWithContext(ctx, r) diff --git a/command/base_helpers.go b/command/base_helpers.go index 98d2f3a93386..d4bf3b734f36 100644 --- a/command/base_helpers.go +++ b/command/base_helpers.go @@ -152,6 +152,22 @@ func parseArgsDataString(stdin io.Reader, args []string) (map[string]string, err return result, nil } +// parseArgsDataStringLists parses the args data and returns the values as +// string lists. If the values cannot be represented as strings, an error is +// returned. +func parseArgsDataStringLists(stdin io.Reader, args []string) (map[string][]string, error) { + raw, err := parseArgsData(stdin, args) + if err != nil { + return nil, err + } + + var result map[string][]string + if err := mapstructure.WeakDecode(raw, &result); err != nil { + return nil, errors.Wrap(err, "failed to convert values to strings") + } + return result, nil +} + // truncateToSeconds truncates the given duration to the number of seconds. If // the duration is less than 1s, it is returned as 0. The integer represents // the whole number unit of seconds for the duration. diff --git a/command/read.go b/command/read.go index 47d8e707444b..0b3660838741 100644 --- a/command/read.go +++ b/command/read.go @@ -2,6 +2,8 @@ package command import ( "fmt" + "io" + "os" "strings" "github.com/mitchellh/cli" @@ -13,6 +15,8 @@ var _ cli.CommandAutocomplete = (*ReadCommand)(nil) type ReadCommand struct { *BaseCommand + + testStdin io.Reader // for tests } func (c *ReadCommand) Synopsis() string { @@ -63,9 +67,6 @@ func (c *ReadCommand) Run(args []string) int { case len(args) < 1: c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) return 1 - case len(args) > 1: - c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) - return 1 } client, err := c.Client() @@ -74,9 +75,21 @@ func (c *ReadCommand) Run(args []string) int { return 2 } + // Pull our fake stdin if needed + stdin := (io.Reader)(os.Stdin) + if c.testStdin != nil { + stdin = c.testStdin + } + path := sanitizePath(args[0]) - secret, err := client.Logical().Read(path) + data, err := parseArgsDataStringLists(stdin, args[1:]) + if err != nil { + c.UI.Error(fmt.Sprintf("Failed to parse K=V data: %s", err)) + return 1 + } + + secret, err := client.Logical().ReadWithData(path, data) if err != nil { c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err)) return 2 diff --git a/http/logical.go b/http/logical.go index fb60010d1914..c8640e7835d1 100644 --- a/http/logical.go +++ b/http/logical.go @@ -28,75 +28,79 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques return nil, http.StatusNotFound, nil } + var data map[string]interface{} + // Determine the operation var op logical.Operation switch r.Method { case "DELETE": op = logical.DeleteOperation + case "GET": op = logical.ReadOperation - // Need to call ParseForm to get query params loaded queryVals := r.URL.Query() + var list bool + var err error listStr := queryVals.Get("list") if listStr != "" { - list, err := strconv.ParseBool(listStr) + list, err = strconv.ParseBool(listStr) if err != nil { return nil, http.StatusBadRequest, nil } if list { op = logical.ListOperation + if !strings.HasSuffix(path, "/") { + path += "/" + } } } - case "POST", "PUT": - op = logical.UpdateOperation - case "LIST": - op = logical.ListOperation - case "OPTIONS": - default: - return nil, http.StatusMethodNotAllowed, nil - } - if op == logical.ListOperation { - if !strings.HasSuffix(path, "/") { - path += "/" - } - } + if !list { + getData := map[string]interface{}{} + + for k, v := range r.URL.Query() { + // Skip the help key as this is a reserved parameter + if k == "help" { + continue + } + + switch { + case len(v) == 0: + case len(v) == 1: + getData[k] = v[0] + default: + getData[k] = v + } + } - // Parse the request if we can - var data map[string]interface{} - if op == logical.UpdateOperation { - err := parseRequest(r, w, &data) - if err == io.EOF { - data = nil - err = nil - } - if err != nil { - return nil, http.StatusBadRequest, err + if len(getData) > 0 { + data = getData + } } - } - - // If we are a read operation, try and parse any parameters - if op == logical.ReadOperation { - getData := map[string]interface{}{} - for k, v := range r.URL.Query() { - // Skip the help key as this is a reserved parameter - if k == "help" { - continue + case "POST", "PUT": + op = logical.UpdateOperation + // Parse the request if we can + if op == logical.UpdateOperation { + err := parseRequest(r, w, &data) + if err == io.EOF { + data = nil + err = nil } - - switch { - case len(v) == 0: - case len(v) == 1: - getData[k] = v[0] - default: - getData[k] = v + if err != nil { + return nil, http.StatusBadRequest, err } } - if len(getData) > 0 { - data = getData + case "LIST": + op = logical.ListOperation + if !strings.HasSuffix(path, "/") { + path += "/" } + + case "OPTIONS": + default: + return nil, http.StatusMethodNotAllowed, nil } var err error