mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add unwrap command, and change how the response is embedded (as a string, not an object)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
133
command/unwrap.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user