From 1c6f2fd82b8c0ff217922e72e6cdc787f2039b38 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 2 Sep 2016 01:13:14 -0400 Subject: [PATCH] Add response wrapping to list operations (#1814) --- api/logical.go | 5 ++++- command/list.go | 4 ++++ command/unwrap.go | 9 +++++++++ command/unwrap_test.go | 33 +++++++++++++++++++++++++++++++++ http/logical.go | 8 ++++---- vault/request_handling.go | 19 +++++++++++++++++++ 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/api/logical.go b/api/logical.go index fb8288e73..e6ff875ce 100644 --- a/api/logical.go +++ b/api/logical.go @@ -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 { diff --git a/command/list.go b/command/list.go index 0573c9b1b..f3ddebdc9 100644 --- a/command/list.go +++ b/command/list.go @@ -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 diff --git a/command/unwrap.go b/command/unwrap.go index df7bc3f9e..e664d98ad 100644 --- a/command/unwrap.go +++ b/command/unwrap.go @@ -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) } diff --git a/command/unwrap_test.go b/command/unwrap_test.go index ec7759edc..7fb28e8e9 100644 --- a/command/unwrap_test.go +++ b/command/unwrap_test.go @@ -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) + } } diff --git a/http/logical.go b/http/logical.go index 70636c566..b4289d115 100644 --- a/http/logical.go +++ b/http/logical.go @@ -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 diff --git a/vault/request_handling.go b/vault/request_handling.go index 96560e88c..e9c7b7ca9 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -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