Add response wrapping to list operations (#1814)

This commit is contained in:
Jeff Mitchell 2016-09-02 01:13:14 -04:00 committed by GitHub
parent 90737d3b44
commit 1c6f2fd82b
6 changed files with 73 additions and 5 deletions

View file

@ -38,7 +38,10 @@ func (c *Logical) Read(path string) (*Secret, error) {
}
func (c *Logical) List(path string) (*Secret, error) {
r := c.c.NewRequest("GET", "/v1/"+path)
r := c.c.NewRequest("LIST", "/v1/"+path)
// Set this for broader compatibility, but we use LIST above to be able to
// handle the wrapping lookup function
r.Method = "GET"
r.Params.Set("list", "true")
resp, err := c.c.RawRequest(r)
if resp != nil {

View file

@ -60,6 +60,10 @@ func (c *ListCommand) Run(args []string) int {
"No value found at %s", path))
return 1
}
if secret.WrapInfo != nil && secret.WrapInfo.TTL != 0 {
return OutputSecret(c.Ui, format, secret)
}
if secret.Data["keys"] == nil {
c.Ui.Error("No entries found")
return 0

View file

@ -67,6 +67,15 @@ func (c *UnwrapCommand) Run(args []string) int {
return PrintRawField(c.Ui, secret, field)
}
// Check if the original was a list response and format as a list if so
if secret.Data != nil &&
len(secret.Data) == 1 &&
secret.Data["keys"] != nil {
_, ok := secret.Data["keys"].([]interface{})
if ok {
return OutputList(c.Ui, format, secret)
}
}
return OutputSecret(c.Ui, format, secret)
}

View file

@ -1,6 +1,7 @@
package command
import (
"strings"
"testing"
"github.com/hashicorp/vault/http"
@ -40,6 +41,9 @@ func TestUnwrap(t *testing.T) {
if method == "GET" && path == "secret/foo" {
return "60s"
}
if method == "LIST" && path == "secret" {
return "60s"
}
return ""
}
client.SetWrappingLookupFunc(wrapLookupFunc)
@ -71,4 +75,33 @@ func TestUnwrap(t *testing.T) {
if output != "zap\n" {
t.Fatalf("unexpectd output:\n%s", output)
}
// Now test with list handling, specifically that it will be called with
// the list output formatter
ui.OutputWriter.Reset()
outer, err = client.Logical().List("secret")
if err != nil {
t.Fatalf("err: %s", err)
}
if outer == nil {
t.Fatal("outer response was nil")
}
if outer.WrapInfo == nil {
t.Fatal("outer wrapinfo was nil, response was %#v", *outer)
}
args = []string{
"-address", addr,
outer.WrapInfo.Token,
}
// Run the read
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
output = ui.OutputWriter.String()
if strings.TrimSpace(output) != "Keys\n----\nfoo" {
t.Fatalf("unexpected output:\n%s", output)
}
}

View file

@ -120,17 +120,17 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa
// Basically: if we have empty "keys" or no keys at all, 404. This
// provides consistency with GET.
case req.Operation == logical.ListOperation:
case req.Operation == logical.ListOperation && resp.WrapInfo == nil:
if resp == nil || len(resp.Data) == 0 {
respondError(w, http.StatusNotFound, nil)
return
}
keysInt, ok := resp.Data["keys"]
if !ok || keysInt == nil {
keysRaw, ok := resp.Data["keys"]
if !ok || keysRaw == nil {
respondError(w, http.StatusNotFound, nil)
return
}
keys, ok := keysInt.([]string)
keys, ok := keysRaw.([]string)
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return

View file

@ -357,6 +357,25 @@ func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *log
}
func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*logical.Response, error) {
// Before wrapping, obey special rules for listing: if no entries are
// found, 404. This prevents unwrapping only to find empty data.
if req.Operation == logical.ListOperation {
if resp == nil || len(resp.Data) == 0 {
return nil, logical.ErrUnsupportedPath
}
keysRaw, ok := resp.Data["keys"]
if !ok || keysRaw == nil {
return nil, logical.ErrUnsupportedPath
}
keys, ok := keysRaw.([]string)
if !ok {
return nil, logical.ErrUnsupportedPath
}
if len(keys) == 0 {
return nil, logical.ErrUnsupportedPath
}
}
// If we are wrapping, the first part (performed in this functions) happens
// before auditing so that resp.WrapInfo.Token can contain the HMAC'd
// wrapping token ID in the audit logs, so that it can be determined from