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 30e199cccf
commit 75f792b27e
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) { 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") r.Params.Set("list", "true")
resp, err := c.c.RawRequest(r) resp, err := c.c.RawRequest(r)
if resp != nil { if resp != nil {

View File

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

View File

@@ -67,6 +67,15 @@ func (c *UnwrapCommand) Run(args []string) int {
return PrintRawField(c.Ui, secret, field) 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) return OutputSecret(c.Ui, format, secret)
} }

View File

@@ -1,6 +1,7 @@
package command package command
import ( import (
"strings"
"testing" "testing"
"github.com/hashicorp/vault/http" "github.com/hashicorp/vault/http"
@@ -40,6 +41,9 @@ func TestUnwrap(t *testing.T) {
if method == "GET" && path == "secret/foo" { if method == "GET" && path == "secret/foo" {
return "60s" return "60s"
} }
if method == "LIST" && path == "secret" {
return "60s"
}
return "" return ""
} }
client.SetWrappingLookupFunc(wrapLookupFunc) client.SetWrappingLookupFunc(wrapLookupFunc)
@@ -71,4 +75,33 @@ func TestUnwrap(t *testing.T) {
if output != "zap\n" { if output != "zap\n" {
t.Fatalf("unexpectd output:\n%s", output) 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 // Basically: if we have empty "keys" or no keys at all, 404. This
// provides consistency with GET. // provides consistency with GET.
case req.Operation == logical.ListOperation: case req.Operation == logical.ListOperation && resp.WrapInfo == nil:
if resp == nil || len(resp.Data) == 0 { if resp == nil || len(resp.Data) == 0 {
respondError(w, http.StatusNotFound, nil) respondError(w, http.StatusNotFound, nil)
return return
} }
keysInt, ok := resp.Data["keys"] keysRaw, ok := resp.Data["keys"]
if !ok || keysInt == nil { if !ok || keysRaw == nil {
respondError(w, http.StatusNotFound, nil) respondError(w, http.StatusNotFound, nil)
return return
} }
keys, ok := keysInt.([]string) keys, ok := keysRaw.([]string)
if !ok { if !ok {
respondError(w, http.StatusInternalServerError, nil) respondError(w, http.StatusInternalServerError, nil)
return 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) { 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 // If we are wrapping, the first part (performed in this functions) happens
// before auditing so that resp.WrapInfo.Token can contain the HMAC'd // 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 // wrapping token ID in the audit logs, so that it can be determined from