mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Add the ability to print curl commands from CLI (#6113)
This commit is contained in:
@@ -84,6 +84,14 @@ type Config struct {
|
||||
// then that limiter will be used. Note that an empty Limiter
|
||||
// is equivalent blocking all events.
|
||||
Limiter *rate.Limiter
|
||||
|
||||
// OutputCurlString causes the actual request to return an error of type
|
||||
// *OutputStringError. Type asserting the error message will allow
|
||||
// fetching a cURL-compatible string for the operation.
|
||||
//
|
||||
// Note: It is not thread-safe to set this and make concurrent requests
|
||||
// with the same client. Cloning a client will not clone this value.
|
||||
OutputCurlString bool
|
||||
}
|
||||
|
||||
// TLSConfig contains the parameters needed to configure TLS on the HTTP client
|
||||
@@ -438,6 +446,24 @@ func (c *Client) SetClientTimeout(timeout time.Duration) {
|
||||
c.config.Timeout = timeout
|
||||
}
|
||||
|
||||
func (c *Client) OutputCurlString() bool {
|
||||
c.modifyLock.RLock()
|
||||
c.config.modifyLock.RLock()
|
||||
defer c.config.modifyLock.RUnlock()
|
||||
c.modifyLock.RUnlock()
|
||||
|
||||
return c.config.OutputCurlString
|
||||
}
|
||||
|
||||
func (c *Client) SetOutputCurlString(curl bool) {
|
||||
c.modifyLock.RLock()
|
||||
c.config.modifyLock.Lock()
|
||||
defer c.config.modifyLock.Unlock()
|
||||
c.modifyLock.RUnlock()
|
||||
|
||||
c.config.OutputCurlString = curl
|
||||
}
|
||||
|
||||
// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
|
||||
// for a given operation and path
|
||||
func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
|
||||
@@ -662,6 +688,7 @@ func (c *Client) RawRequestWithContext(ctx context.Context, r *Request) (*Respon
|
||||
backoff := c.config.Backoff
|
||||
httpClient := c.config.HttpClient
|
||||
timeout := c.config.Timeout
|
||||
outputCurlString := c.config.OutputCurlString
|
||||
c.config.modifyLock.RUnlock()
|
||||
|
||||
c.modifyLock.RUnlock()
|
||||
@@ -688,6 +715,11 @@ START:
|
||||
return nil, fmt.Errorf("nil request created")
|
||||
}
|
||||
|
||||
if outputCurlString {
|
||||
LastOutputStringError = &OutputStringError{Request: req}
|
||||
return nil, LastOutputStringError
|
||||
}
|
||||
|
||||
if timeout != 0 {
|
||||
ctx, _ = context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
69
api/output_string.go
Normal file
69
api/output_string.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
retryablehttp "github.com/hashicorp/go-retryablehttp"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrOutputStringRequest = "output a string, please"
|
||||
)
|
||||
|
||||
var (
|
||||
LastOutputStringError *OutputStringError
|
||||
)
|
||||
|
||||
type OutputStringError struct {
|
||||
*retryablehttp.Request
|
||||
parsingError error
|
||||
parsedCurlString string
|
||||
}
|
||||
|
||||
func (d *OutputStringError) Error() string {
|
||||
if d.parsedCurlString == "" {
|
||||
d.parseRequest()
|
||||
if d.parsingError != nil {
|
||||
return d.parsingError.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return ErrOutputStringRequest
|
||||
}
|
||||
|
||||
func (d *OutputStringError) parseRequest() {
|
||||
body, err := d.Request.BodyBytes()
|
||||
if err != nil {
|
||||
d.parsingError = err
|
||||
return
|
||||
}
|
||||
|
||||
// Build cURL string
|
||||
d.parsedCurlString = "curl "
|
||||
d.parsedCurlString = fmt.Sprintf("%s-X %s ", d.parsedCurlString, d.Request.Method)
|
||||
for k, v := range d.Request.Header {
|
||||
for _, h := range v {
|
||||
if strings.ToLower(k) == "x-vault-token" {
|
||||
h = `$(vault print token)`
|
||||
}
|
||||
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
|
||||
}
|
||||
}
|
||||
|
||||
if len(body) > 0 {
|
||||
// We need to escape single quotes since that's what we're using to
|
||||
// quote the body
|
||||
escapedBody := strings.Replace(string(body), "'", "'\"'\"'", -1)
|
||||
d.parsedCurlString = fmt.Sprintf("%s-d '%s' ", d.parsedCurlString, escapedBody)
|
||||
}
|
||||
|
||||
d.parsedCurlString = fmt.Sprintf("%s%s", d.parsedCurlString, d.Request.URL.String())
|
||||
}
|
||||
|
||||
func (d *OutputStringError) CurlString() string {
|
||||
if d.parsedCurlString == "" {
|
||||
d.parseRequest()
|
||||
}
|
||||
return d.parsedCurlString
|
||||
}
|
||||
@@ -52,6 +52,7 @@ type BaseCommand struct {
|
||||
|
||||
flagFormat string
|
||||
flagField string
|
||||
flagOutputCurlString bool
|
||||
|
||||
flagMFA []string
|
||||
|
||||
@@ -78,6 +79,10 @@ func (c *BaseCommand) Client() (*api.Client, error) {
|
||||
config.Address = c.flagAddress
|
||||
}
|
||||
|
||||
if c.flagOutputCurlString {
|
||||
config.OutputCurlString = c.flagOutputCurlString
|
||||
}
|
||||
|
||||
// If we need custom TLS configuration, then set it
|
||||
if c.flagCACert != "" || c.flagCAPath != "" || c.flagClientCert != "" ||
|
||||
c.flagClientKey != "" || c.flagTLSServerName != "" || c.flagTLSSkipVerify {
|
||||
@@ -325,6 +330,15 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Supply MFA credentials as part of X-Vault-MFA header.",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "output-curl-string",
|
||||
Target: &c.flagOutputCurlString,
|
||||
Default: false,
|
||||
Usage: "Instead of executing the request, print an equivalent cURL " +
|
||||
"command string and exit.",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if bit&(FlagSetOutputField|FlagSetOutputFormat) != 0 {
|
||||
|
||||
@@ -423,6 +423,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"print token": func() (cli.Command, error) {
|
||||
return &PrintTokenCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"read": func() (cli.Command, error) {
|
||||
return &ReadCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
|
||||
@@ -46,6 +46,9 @@ func kvPreflightVersionRequest(client *api.Client, path string) (string, int, er
|
||||
currentWrappingLookupFunc := client.CurrentWrappingLookupFunc()
|
||||
client.SetWrappingLookupFunc(nil)
|
||||
defer client.SetWrappingLookupFunc(currentWrappingLookupFunc)
|
||||
currentOutputCurlString := client.OutputCurlString()
|
||||
client.SetOutputCurlString(false)
|
||||
defer client.SetOutputCurlString(currentOutputCurlString)
|
||||
|
||||
r := client.NewRequest("GET", "/v1/sys/internal/ui/mounts/"+path)
|
||||
resp, err := client.RawRequest(r)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -23,7 +24,7 @@ type VaultUI struct {
|
||||
|
||||
// setupEnv parses args and may replace them and sets some env vars to known
|
||||
// values based on format options
|
||||
func setupEnv(args []string) (retArgs []string, format string) {
|
||||
func setupEnv(args []string) (retArgs []string, format string, outputCurlString bool) {
|
||||
var nextArgFormat bool
|
||||
|
||||
for _, arg := range args {
|
||||
@@ -42,6 +43,11 @@ func setupEnv(args []string) (retArgs []string, format string) {
|
||||
break
|
||||
}
|
||||
|
||||
if arg == "-output-curl-string" {
|
||||
outputCurlString = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse a given flag here, which overrides the env var
|
||||
if strings.HasPrefix(arg, "--format=") {
|
||||
format = strings.TrimPrefix(arg, "--format=")
|
||||
@@ -66,7 +72,7 @@ func setupEnv(args []string) (retArgs []string, format string) {
|
||||
format = "table"
|
||||
}
|
||||
|
||||
return args, format
|
||||
return args, format, outputCurlString
|
||||
}
|
||||
|
||||
type RunOptions struct {
|
||||
@@ -89,7 +95,8 @@ func RunCustom(args []string, runOpts *RunOptions) int {
|
||||
}
|
||||
|
||||
var format string
|
||||
args, format = setupEnv(args)
|
||||
var outputCurlString bool
|
||||
args, format, outputCurlString = setupEnv(args)
|
||||
|
||||
// Don't use color if disabled
|
||||
useColor := true
|
||||
@@ -117,13 +124,18 @@ func RunCustom(args []string, runOpts *RunOptions) int {
|
||||
runOpts.Stderr = colorable.NewNonColorable(runOpts.Stderr)
|
||||
}
|
||||
|
||||
uiErrWriter := runOpts.Stderr
|
||||
if outputCurlString {
|
||||
uiErrWriter = ioutil.Discard
|
||||
}
|
||||
|
||||
ui := &VaultUI{
|
||||
Ui: &cli.ColoredUi{
|
||||
ErrorColor: cli.UiColorRed,
|
||||
WarnColor: cli.UiColorYellow,
|
||||
Ui: &cli.BasicUi{
|
||||
Writer: runOpts.Stdout,
|
||||
ErrorWriter: runOpts.Stderr,
|
||||
ErrorWriter: uiErrWriter,
|
||||
},
|
||||
},
|
||||
format: format,
|
||||
@@ -168,7 +180,27 @@ func RunCustom(args []string, runOpts *RunOptions) int {
|
||||
}
|
||||
|
||||
exitCode, err := cli.Run()
|
||||
if err != nil {
|
||||
if outputCurlString {
|
||||
if exitCode == 0 {
|
||||
fmt.Fprint(runOpts.Stderr, "Could not generate cURL command")
|
||||
return 1
|
||||
} else {
|
||||
if api.LastOutputStringError == nil {
|
||||
if exitCode == 127 {
|
||||
// Usage, just pass it through
|
||||
return exitCode
|
||||
}
|
||||
fmt.Fprint(runOpts.Stderr, "cURL command not set by API operation; run without -output-curl-string to see the generated error\n")
|
||||
return exitCode
|
||||
}
|
||||
if api.LastOutputStringError.Error() != api.ErrOutputStringRequest {
|
||||
runOpts.Stdout.Write([]byte(fmt.Sprintf("Error creating request string: %s\n", api.LastOutputStringError.Error())))
|
||||
return 1
|
||||
}
|
||||
runOpts.Stdout.Write([]byte(fmt.Sprintf("%s\n", api.LastOutputStringError.CurlString())))
|
||||
return 0
|
||||
}
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(runOpts.Stderr, "Error executing CLI: %s\n", err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
56
command/print_token.go
Normal file
56
command/print_token.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*PrintTokenCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*PrintTokenCommand)(nil)
|
||||
|
||||
type PrintTokenCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) Synopsis() string {
|
||||
return "Prints the contents of a policy"
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault print token
|
||||
|
||||
Prints the value of the Vault token that will be used for commands, after
|
||||
taking into account the configured token-helper and the environment.
|
||||
|
||||
$ vault print token
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) Flags() *FlagSets {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) AutocompleteFlags() complete.Flags {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PrintTokenCommand) Run(args []string) int {
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Output(client.Token())
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user