Add unwrap command, and change how the response is embedded (as a string, not an object)

This commit is contained in:
Jeff Mitchell
2016-05-19 11:25:15 -04:00
parent d261e9e52b
commit 0b59a54837
6 changed files with 201 additions and 21 deletions

View File

@@ -171,6 +171,12 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory {
}, nil
},
"unwrap": func() (cli.Command, error) {
return &command.UnwrapCommand{
Meta: *metaPtr,
}, nil
},
"list": func() (cli.Command, error) {
return &command.ListCommand{
Meta: *metaPtr,

View File

@@ -21,6 +21,7 @@ func HelpFunc(commands map[string]cli.CommandFactory) string {
"write": struct{}{},
"server": struct{}{},
"status": struct{}{},
"unwrap": struct{}{},
}
// Determine the maximum key length, and classify based on type

View File

@@ -3,8 +3,6 @@ package command
import (
"flag"
"fmt"
"os"
"reflect"
"strings"
"github.com/hashicorp/vault/api"
@@ -63,23 +61,7 @@ func (c *ReadCommand) Run(args []string) int {
// Handle single field output
if field != "" {
if val, ok := secret.Data[field]; ok {
// c.Ui.Output() prints a CR character which in this case is
// not desired. Since Vault CLI currently only uses BasicUi,
// which writes to standard output, os.Stdout is used here to
// directly print the message. If mitchellh/cli exposes method
// to print without CR, this check needs to be removed.
if reflect.TypeOf(c.Ui).String() == "*cli.BasicUi" {
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v", val))
} else {
c.Ui.Output(fmt.Sprintf("%v", val))
}
return 0
} else {
c.Ui.Error(fmt.Sprintf(
"Field %s not present in secret", field))
return 1
}
return PrintRawField(c.Ui, secret, field)
}
return OutputSecret(c.Ui, format, secret)

133
command/unwrap.go Normal file
View File

@@ -0,0 +1,133 @@
package command
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"strings"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/meta"
)
const (
wrappedResponseLocation = "cubbyhole/response"
)
// UnwrapCommand is a Command that behaves like ReadCommand but specifically
// for unwrapping cubbyhole-wrapped secrets
type UnwrapCommand struct {
meta.Meta
}
func (c *UnwrapCommand) Run(args []string) int {
var format string
var field string
var err error
var secret *api.Secret
var flags *flag.FlagSet
flags = c.Meta.FlagSet("unwrap", meta.FlagSetDefault)
flags.StringVar(&format, "format", "table", "")
flags.StringVar(&field, "field", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
args = flags.Args()
if len(args) != 1 || len(args[0]) == 0 {
c.Ui.Error("Unwrap expects one argument: the ID of the wrapping token")
flags.Usage()
return 1
}
tokenID := args[0]
_, err = uuid.ParseUUID(tokenID)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Given token could not be parsed as a UUID: %s", err))
return 1
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
return 2
}
client.SetToken(tokenID)
secret, err = c.getUnwrappedResponse(client)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if secret == nil {
c.Ui.Error("Secret returned was nil")
return 1
}
// Handle single field output
if field != "" {
return PrintRawField(c.Ui, secret, field)
}
return OutputSecret(c.Ui, format, secret)
}
// getUnwrappedResponse is a helper to do the actual reading and unwrapping
func (c *UnwrapCommand) getUnwrappedResponse(client *api.Client) (*api.Secret, error) {
secret, err := client.Logical().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(api.Secret)
buf := bytes.NewBufferString(secret.Data["response"].(string))
dec := json.NewDecoder(buf)
dec.UseNumber()
if err := dec.Decode(wrappedSecret); err != nil {
return nil, fmt.Errorf("Error unmarshaling wrapped secret: %s", err)
}
return wrappedSecret, nil
}
func (c *UnwrapCommand) Synopsis() string {
return "Unwrap a wrapped secret"
}
func (c *UnwrapCommand) Help() string {
helpText := `
Usage: vault unwrap [options] <wrapping token ID>
Unwrap a wrapped secret.
Unwraps the data wrapped by the given token ID. The returned result is the
same as a 'read' operation on a non-wrapped secret.
General Options:
` + meta.GeneralOptionsUsage() + `
Read Options:
-format=table The format for output. By default it is a whitespace-
delimited table. This can also be json or yaml.
-field=field If included, the raw value of the specified field
will be output raw to stdout.
`
return strings.TrimSpace(helpText)
}

View File

@@ -1,6 +1,14 @@
package command
import "github.com/hashicorp/vault/command/token"
import (
"fmt"
"os"
"reflect"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/token"
"github.com/mitchellh/cli"
)
// DefaultTokenHelper returns the token helper that is configured for Vault.
func DefaultTokenHelper() (token.TokenHelper, error) {
@@ -20,3 +28,39 @@ func DefaultTokenHelper() (token.TokenHelper, error) {
}
return &token.ExternalTokenHelper{BinaryPath: path}, nil
}
func PrintRawField(ui cli.Ui, secret *api.Secret, field string) int {
var val interface{}
switch field {
case "wrapping_token":
if secret.WrapInfo != nil {
val = secret.WrapInfo.Token
}
case "wrapping_token_ttl":
if secret.WrapInfo != nil {
val = secret.WrapInfo.TTL
}
case "refresh_interval":
val = secret.LeaseDuration
default:
val = secret.Data[field]
}
if val != nil {
// c.Ui.Output() prints a CR character which in this case is
// not desired. Since Vault CLI currently only uses BasicUi,
// which writes to standard output, os.Stdout is used here to
// directly print the message. If mitchellh/cli exposes method
// to print without CR, this check needs to be removed.
if reflect.TypeOf(ui).String() == "*cli.BasicUi" {
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v", val))
} else {
ui.Output(fmt.Sprintf("%v", val))
}
return 0
} else {
ui.Error(fmt.Sprintf(
"Field %s not present in secret", field))
return 1
}
}

View File

@@ -1,6 +1,7 @@
package vault
import (
"encoding/json"
"sort"
"strings"
"time"
@@ -391,12 +392,25 @@ func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*l
httpResponse := logical.SanitizeResponse(resp)
// Because of the way that JSON encodes (likely just in Go) we actually get
// mixed-up values for ints if we simply put this object in the response
// and encode the whole thing; so instead we marshal it first, then store
// the string response. This actually ends up making it easier on the
// client side, too, as it becomes a straight read-string-pass-to-unmarshal
// operation.
marshaledResponse, err := json.Marshal(httpResponse)
if err != nil {
c.logger.Printf("[ERR] core: failed to marshal wrapped response: %v", err)
return nil, ErrInternalError
}
cubbyReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "cubbyhole/response",
ClientToken: te.ID,
Data: map[string]interface{}{
"response": httpResponse,
"response": string(marshaledResponse),
},
}