open-vault/command/write.go

186 lines
4.6 KiB
Go
Raw Permalink Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2015-03-04 19:08:13 +00:00
package command
import (
2015-03-16 03:35:33 +00:00
"fmt"
"io"
"os"
2015-03-04 19:08:13 +00:00
"strings"
2015-04-08 05:30:25 +00:00
"github.com/hashicorp/vault/api"
2017-09-05 04:05:47 +00:00
"github.com/mitchellh/cli"
"github.com/posener/complete"
2015-03-04 19:08:13 +00:00
)
var (
_ cli.Command = (*WriteCommand)(nil)
_ cli.CommandAutocomplete = (*WriteCommand)(nil)
)
2017-09-05 04:05:47 +00:00
// MFAMethodInfo contains the information about an MFA method
type MFAMethodInfo struct {
methodID string
methodType string
usePasscode bool
}
2015-03-16 03:35:33 +00:00
// WriteCommand is a Command that puts data into the Vault.
type WriteCommand struct {
2017-09-05 04:05:47 +00:00
*BaseCommand
flagForce bool
2017-09-05 04:05:47 +00:00
testStdin io.Reader // for tests
}
func (c *WriteCommand) Synopsis() string {
2017-09-08 02:04:16 +00:00
return "Write data, configuration, and secrets"
2017-09-05 04:05:47 +00:00
}
func (c *WriteCommand) Help() string {
helpText := `
Usage: vault write [options] PATH [DATA K=V...]
Writes data to Vault at the given path. The data can be credentials, secrets,
configuration, or arbitrary data. The specific behavior of this command is
2017-09-08 02:04:16 +00:00
determined at the thing mounted at the path.
2017-09-05 04:05:47 +00:00
Data is specified as "key=value" pairs. If the value begins with an "@", then
it is loaded from a file. If the value is "-", Vault will read the value from
stdin.
2017-09-08 02:04:16 +00:00
Persist data in the generic secrets engine:
2017-09-05 04:05:47 +00:00
$ vault write secret/my-secret foo=bar
2017-09-08 02:04:16 +00:00
Create a new encryption key in the transit secrets engine:
2017-09-05 04:05:47 +00:00
$ vault write -f transit/keys/my-key
Upload an AWS IAM policy from a file on disk:
$ vault write aws/roles/ops policy=@policy.json
Configure access to Consul by providing an access token:
$ echo $MY_TOKEN | vault write consul/config/access token=-
For a full list of examples and paths, please see the documentation that
2017-09-08 02:04:16 +00:00
corresponds to the secret engines in use.
2017-09-05 04:05:47 +00:00
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *WriteCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
f.BoolVar(&BoolVar{
Name: "force",
Aliases: []string{"f"},
Target: &c.flagForce,
Default: false,
EnvVar: "",
Completion: complete.PredictNothing,
Usage: "Allow the operation to continue with no key=value pairs. This " +
"allows writing to keys that do not need or expect data.",
})
return set
}
func (c *WriteCommand) AutocompleteArgs() complete.Predictor {
// Return an anything predictor here. Without a way to access help
// information, we don't know what paths we could write to.
return complete.PredictAnything
}
func (c *WriteCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
2015-03-04 19:08:13 +00:00
}
2015-03-16 03:35:33 +00:00
func (c *WriteCommand) Run(args []string) int {
2017-09-05 04:05:47 +00:00
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
2015-03-04 19:08:13 +00:00
return 1
}
2017-09-08 02:04:16 +00:00
args = f.Args()
switch {
case len(args) < 1:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
return 1
2017-09-08 02:04:16 +00:00
case len(args) == 1 && !c.flagForce:
c.UI.Error("Must supply data or use -force")
2015-03-16 03:35:33 +00:00
return 1
}
2017-09-05 04:05:47 +00:00
// Pull our fake stdin if needed
stdin := (io.Reader)(os.Stdin)
if c.testStdin != nil {
stdin = c.testStdin
2015-03-16 03:35:33 +00:00
}
2017-09-08 02:04:16 +00:00
path := sanitizePath(args[0])
data, err := parseArgsData(stdin, args[1:])
2015-04-01 00:16:12 +00:00
if err != nil {
2017-09-05 04:05:47 +00:00
c.UI.Error(fmt.Sprintf("Failed to parse K=V data: %s", err))
2015-04-01 00:16:12 +00:00
return 1
}
2015-03-16 03:35:33 +00:00
client, err := c.Client()
if err != nil {
2017-09-05 04:05:47 +00:00
c.UI.Error(err.Error())
2015-03-16 03:35:33 +00:00
return 2
}
secret, err := client.Logical().Write(path, data)
return handleWriteSecretOutput(c.BaseCommand, path, secret, err)
}
func handleWriteSecretOutput(c *BaseCommand, path string, secret *api.Secret, err error) int {
if err != nil {
2017-09-05 04:05:47 +00:00
c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", path, err))
if secret != nil {
OutputSecret(c.UI, secret)
}
2017-09-05 04:05:47 +00:00
return 2
2015-03-16 03:35:33 +00:00
}
if secret == nil {
2017-09-05 04:05:47 +00:00
// Don't output anything unless using the "table" format
CLI Enhancements (#3897) * Use Colored UI if stdout is a tty * Add format options to operator unseal * Add format test on operator unseal * Add -no-color output flag, and use BasicUi if no-color flag is provided * Move seal status formatting logic to OutputSealStatus * Apply no-color to warnings from DeprecatedCommands as well * Add OutputWithFormat to support arbitrary data, add format option to auth list * Add ability to output arbitrary list data on TableFormatter * Clear up switch logic on format * Add format option for list-related commands * Add format option to rest of commands that returns a client API response * Remove initOutputYAML and initOutputJSON, and use OutputWithFormat instead * Remove outputAsYAML and outputAsJSON, and use OutputWithFormat instead * Remove -no-color flag, use env var exclusively to toggle colored output * Fix compile * Remove -no-color flag in main.go * Add missing FlagSetOutputFormat * Fix generate-root/decode test * Migrate init functions to main.go * Add no-color flag back as hidden * Handle non-supported data types for TableFormatter.OutputList * Pull formatting much further up to remove the need to use c.flagFormat (#3950) * Pull formatting much further up to remove the need to use c.flagFormat Also remove OutputWithFormat as the logic can cause issues. * Use const for env var * Minor updates * Remove unnecessary check * Fix SSH output and some tests * Fix tests * Make race detector not run on generate root since it kills Travis these days * Update docs * Update docs * Address review feedback * Handle --format as well as -format
2018-02-12 23:12:16 +00:00
if Format(c.UI) == "table" {
2017-09-05 04:05:47 +00:00
c.UI.Info(fmt.Sprintf("Success! Data written to: %s", path))
}
return 0
}
// Currently, if there is only one MFA method configured, the login
// request is validated interactively
methodInfo := c.getInteractiveMFAMethodInfo(secret)
if methodInfo != nil {
secret, err = c.validateMFA(secret.Auth.MFARequirement.MFARequestID, *methodInfo)
if err != nil {
c.UI.Error(err.Error())
return 2
}
} else if c.getMFAValidationRequired(secret) {
c.UI.Warn(wrapAtLength("A login request was issued that is subject to "+
"MFA validation. Please make sure to validate the login by sending another "+
"request to sys/mfa/validate endpoint.") + "\n")
}
// Handle single field output
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, secret)
}