vault/api/logical.go
Jeff Mitchell 19e5061779
Allow returning warnings and other data in 404s in the Go API (#4256)
* Allow returning list information and other data in 404s.

On read it'll output data and/or warnings on a 404 if they exist. On
list, the same behavior; the actual 'vault list' command doesn't change
behavior though in terms of output unless there are no actual keys (so
it doesn't just magically show other data).

This corrects some assumptions in response_util and wrapping.go; it also
corrects a few places in the latter where it could leak a (useless)
token in some error cases.

* Use same 404 logic in delete/put too

* Add the same secret parsing logic to the KV request functions
2018-04-03 22:35:45 -04:00

238 lines
5.2 KiB
Go

package api
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"github.com/hashicorp/vault/helper/jsonutil"
)
const (
wrappedResponseLocation = "cubbyhole/response"
)
var (
// The default TTL that will be used with `sys/wrapping/wrap`, can be
// changed
DefaultWrappingTTL = "5m"
// The default function used if no other function is set, which honors the
// env var and wraps `sys/wrapping/wrap`
DefaultWrappingLookupFunc = func(operation, path string) string {
if os.Getenv(EnvVaultWrapTTL) != "" {
return os.Getenv(EnvVaultWrapTTL)
}
if (operation == "PUT" || operation == "POST") && path == "sys/wrapping/wrap" {
return DefaultWrappingTTL
}
return ""
}
)
// Logical is used to perform logical backend operations on Vault.
type Logical struct {
c *Client
}
// Logical is used to return the client for logical-backend API calls.
func (c *Client) Logical() *Logical {
return &Logical{c: c}
}
func (c *Logical) Read(path string) (*Secret, error) {
r := c.c.NewRequest("GET", "/v1/"+path)
resp, err := c.c.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == 404 {
secret, err := ParseSecret(resp.Body)
switch err {
case nil:
case io.EOF:
return nil, nil
default:
return nil, err
}
if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
return secret, nil
}
return nil, nil
}
if err != nil {
return nil, err
}
return ParseSecret(resp.Body)
}
func (c *Logical) List(path string) (*Secret, error) {
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 {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == 404 {
secret, err := ParseSecret(resp.Body)
switch err {
case nil:
case io.EOF:
return nil, nil
default:
return nil, err
}
if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
return secret, nil
}
return nil, nil
}
if err != nil {
return nil, err
}
return ParseSecret(resp.Body)
}
func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, error) {
r := c.c.NewRequest("PUT", "/v1/"+path)
if err := r.SetJSONBody(data); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == 404 {
secret, err := ParseSecret(resp.Body)
switch err {
case nil:
case io.EOF:
return nil, nil
default:
return nil, err
}
if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
return secret, nil
}
return nil, nil
}
if err != nil {
return nil, err
}
if resp.StatusCode == 200 {
return ParseSecret(resp.Body)
}
return nil, nil
}
func (c *Logical) Delete(path string) (*Secret, error) {
r := c.c.NewRequest("DELETE", "/v1/"+path)
resp, err := c.c.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == 404 {
secret, err := ParseSecret(resp.Body)
switch err {
case nil:
case io.EOF:
return nil, nil
default:
return nil, err
}
if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
return secret, nil
}
return nil, nil
}
if err != nil {
return nil, err
}
if resp.StatusCode == 200 {
return ParseSecret(resp.Body)
}
return nil, nil
}
func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
var data map[string]interface{}
if wrappingToken != "" {
if c.c.Token() == "" {
c.c.SetToken(wrappingToken)
} else if wrappingToken != c.c.Token() {
data = map[string]interface{}{
"token": wrappingToken,
}
}
}
r := c.c.NewRequest("PUT", "/v1/sys/wrapping/unwrap")
if err := r.SetJSONBody(data); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
// Return all errors except those that are from a 404 as we handle the not
// found error as a special case.
if err != nil && (resp == nil || resp.StatusCode != 404) {
return nil, err
}
if resp == nil {
return nil, nil
}
switch resp.StatusCode {
case http.StatusOK: // New method is supported
return ParseSecret(resp.Body)
case http.StatusNotFound: // Fall back to old method
default:
return nil, nil
}
if wrappingToken != "" {
origToken := c.c.Token()
defer c.c.SetToken(origToken)
c.c.SetToken(wrappingToken)
}
secret, err := c.Read(wrappedResponseLocation)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", wrappedResponseLocation, err)
}
if secret == nil {
return nil, fmt.Errorf("no value found at %s", wrappedResponseLocation)
}
if secret.Data == nil {
return nil, fmt.Errorf("\"data\" not found in wrapping response")
}
if _, ok := secret.Data["response"]; !ok {
return nil, fmt.Errorf("\"response\" not found in wrapping response \"data\" map")
}
wrappedSecret := new(Secret)
buf := bytes.NewBufferString(secret.Data["response"].(string))
if err := jsonutil.DecodeJSONFromReader(buf, wrappedSecret); err != nil {
return nil, fmt.Errorf("error unmarshalling wrapped secret: %s", err)
}
return wrappedSecret, nil
}