| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package command |
| |
| import ( |
| "flag" |
| "fmt" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/hashicorp/vault/api" |
| "github.com/mitchellh/cli" |
| "github.com/posener/complete" |
| ) |
| |
| var ( |
| _ cli.Command = (*SecretsEnableCommand)(nil) |
| _ cli.CommandAutocomplete = (*SecretsEnableCommand)(nil) |
| ) |
| |
| type SecretsEnableCommand struct { |
| *BaseCommand |
| |
| flagDescription string |
| flagPath string |
| flagDefaultLeaseTTL time.Duration |
| flagMaxLeaseTTL time.Duration |
| flagAuditNonHMACRequestKeys []string |
| flagAuditNonHMACResponseKeys []string |
| flagListingVisibility string |
| flagPassthroughRequestHeaders []string |
| flagAllowedResponseHeaders []string |
| flagForceNoCache bool |
| flagPluginName string |
| flagPluginVersion string |
| flagOptions map[string]string |
| flagLocal bool |
| flagSealWrap bool |
| flagExternalEntropyAccess bool |
| flagVersion int |
| flagAllowedManagedKeys []string |
| } |
| |
| func (c *SecretsEnableCommand) Synopsis() string { |
| return "Enable a secrets engine" |
| } |
| |
| func (c *SecretsEnableCommand) Help() string { |
| helpText := ` |
| Usage: vault secrets enable [options] TYPE |
| |
| Enables a secrets engine. By default, secrets engines are enabled at the path |
| corresponding to their TYPE, but users can customize the path using the |
| -path option. |
| |
| Once enabled, Vault will route all requests which begin with the path to the |
| secrets engine. |
| |
| Enable the AWS secrets engine at aws/: |
| |
| $ vault secrets enable aws |
| |
| Enable the SSH secrets engine at ssh-prod/: |
| |
| $ vault secrets enable -path=ssh-prod ssh |
| |
| Enable the database secrets engine with an explicit maximum TTL of 30m: |
| |
| $ vault secrets enable -max-lease-ttl=30m database |
| |
| Enable a custom plugin (after it is registered in the plugin registry): |
| |
| $ vault secrets enable -path=my-secrets -plugin-name=my-plugin plugin |
| |
| OR (preferred way): |
| |
| $ vault secrets enable -path=my-secrets my-plugin |
| |
| For a full list of secrets engines and examples, please see the documentation. |
| |
| ` + c.Flags().Help() |
| |
| return strings.TrimSpace(helpText) |
| } |
| |
| func (c *SecretsEnableCommand) Flags() *FlagSets { |
| set := c.flagSet(FlagSetHTTP) |
| |
| f := set.NewFlagSet("Command Options") |
| |
| f.StringVar(&StringVar{ |
| Name: "description", |
| Target: &c.flagDescription, |
| Completion: complete.PredictAnything, |
| Usage: "Human-friendly description for the purpose of this engine.", |
| }) |
| |
| f.StringVar(&StringVar{ |
| Name: "path", |
| Target: &c.flagPath, |
| Default: "", // The default is complex, so we have to manually document |
| Completion: complete.PredictAnything, |
| Usage: "Place where the secrets engine will be accessible. This must be " + |
| "unique cross all secrets engines. This defaults to the \"type\" of the " + |
| "secrets engine.", |
| }) |
| |
| f.DurationVar(&DurationVar{ |
| Name: "default-lease-ttl", |
| Target: &c.flagDefaultLeaseTTL, |
| Completion: complete.PredictAnything, |
| Usage: "The default lease TTL for this secrets engine. If unspecified, " + |
| "this defaults to the Vault server's globally configured default lease " + |
| "TTL.", |
| }) |
| |
| f.DurationVar(&DurationVar{ |
| Name: "max-lease-ttl", |
| Target: &c.flagMaxLeaseTTL, |
| Completion: complete.PredictAnything, |
| Usage: "The maximum lease TTL for this secrets engine. If unspecified, " + |
| "this defaults to the Vault server's globally configured maximum lease " + |
| "TTL.", |
| }) |
| |
| f.StringSliceVar(&StringSliceVar{ |
| Name: flagNameAuditNonHMACRequestKeys, |
| Target: &c.flagAuditNonHMACRequestKeys, |
| Usage: "Key that will not be HMAC'd by audit devices in the request data object. " + |
| "To specify multiple values, specify this flag multiple times.", |
| }) |
| |
| f.StringSliceVar(&StringSliceVar{ |
| Name: flagNameAuditNonHMACResponseKeys, |
| Target: &c.flagAuditNonHMACResponseKeys, |
| Usage: "Key that will not be HMAC'd by audit devices in the response data object. " + |
| "To specify multiple values, specify this flag multiple times.", |
| }) |
| |
| f.StringVar(&StringVar{ |
| Name: flagNameListingVisibility, |
| Target: &c.flagListingVisibility, |
| Usage: "Determines the visibility of the mount in the UI-specific listing endpoint.", |
| }) |
| |
| f.StringSliceVar(&StringSliceVar{ |
| Name: flagNamePassthroughRequestHeaders, |
| Target: &c.flagPassthroughRequestHeaders, |
| Usage: "Request header value that will be sent to the plugins. To specify multiple " + |
| "values, specify this flag multiple times.", |
| }) |
| |
| f.StringSliceVar(&StringSliceVar{ |
| Name: flagNameAllowedResponseHeaders, |
| Target: &c.flagAllowedResponseHeaders, |
| Usage: "Response header value that plugins will be allowed to set. To specify multiple " + |
| "values, specify this flag multiple times.", |
| }) |
| |
| f.BoolVar(&BoolVar{ |
| Name: "force-no-cache", |
| Target: &c.flagForceNoCache, |
| Default: false, |
| Usage: "Force the secrets engine to disable caching. If unspecified, this " + |
| "defaults to the Vault server's globally configured cache settings. " + |
| "This does not affect caching of the underlying encrypted data storage.", |
| }) |
| |
| f.StringVar(&StringVar{ |
| Name: "plugin-name", |
| Target: &c.flagPluginName, |
| Completion: c.PredictVaultPlugins(api.PluginTypeSecrets, api.PluginTypeDatabase), |
| Usage: "Name of the secrets engine plugin. This plugin name must already " + |
| "exist in Vault's plugin catalog.", |
| }) |
| |
| f.StringVar(&StringVar{ |
| Name: flagNamePluginVersion, |
| Target: &c.flagPluginVersion, |
| Default: "", |
| Usage: "Select the semantic version of the plugin to enable.", |
| }) |
| |
| f.StringMapVar(&StringMapVar{ |
| Name: "options", |
| Target: &c.flagOptions, |
| Completion: complete.PredictAnything, |
| Usage: "Key-value pair provided as key=value for the mount options. " + |
| "This can be specified multiple times.", |
| }) |
| |
| f.BoolVar(&BoolVar{ |
| Name: "local", |
| Target: &c.flagLocal, |
| Default: false, |
| Usage: "Mark the secrets engine as local-only. Local engines are not " + |
| "replicated or removed by replication.", |
| }) |
| |
| f.BoolVar(&BoolVar{ |
| Name: "seal-wrap", |
| Target: &c.flagSealWrap, |
| Default: false, |
| Usage: "Enable seal wrapping of critical values in the secrets engine.", |
| }) |
| |
| f.BoolVar(&BoolVar{ |
| Name: "external-entropy-access", |
| Target: &c.flagExternalEntropyAccess, |
| Default: false, |
| Usage: "Enable secrets engine to access Vault's external entropy source.", |
| }) |
| |
| f.IntVar(&IntVar{ |
| Name: "version", |
| Target: &c.flagVersion, |
| Default: 0, |
| Usage: "Select the version of the engine to run. Not supported by all engines.", |
| }) |
| |
| f.StringSliceVar(&StringSliceVar{ |
| Name: flagNameAllowedManagedKeys, |
| Target: &c.flagAllowedManagedKeys, |
| Usage: "Managed key name(s) that the mount in question is allowed to access. " + |
| "Note that multiple keys may be specified by providing this option multiple times, " + |
| "each time with 1 key.", |
| }) |
| |
| return set |
| } |
| |
| func (c *SecretsEnableCommand) AutocompleteArgs() complete.Predictor { |
| return c.PredictVaultAvailableMounts() |
| } |
| |
| func (c *SecretsEnableCommand) AutocompleteFlags() complete.Flags { |
| return c.Flags().Completions() |
| } |
| |
| func (c *SecretsEnableCommand) Run(args []string) int { |
| f := c.Flags() |
| |
| if err := f.Parse(args); err != nil { |
| c.UI.Error(err.Error()) |
| return 1 |
| } |
| |
| args = f.Args() |
| switch { |
| case len(args) < 1: |
| c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) |
| return 1 |
| case len(args) > 1: |
| c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) |
| return 1 |
| } |
| |
| client, err := c.Client() |
| if err != nil { |
| c.UI.Error(err.Error()) |
| return 2 |
| } |
| |
| // Get the engine type type (first arg) |
| engineType := strings.TrimSpace(args[0]) |
| if engineType == "plugin" { |
| engineType = c.flagPluginName |
| } |
| |
| // If no path is specified, we default the path to the backend type |
| // or use the plugin name if it's a plugin backend |
| mountPath := c.flagPath |
| if mountPath == "" { |
| if engineType == "plugin" { |
| mountPath = c.flagPluginName |
| } else { |
| mountPath = engineType |
| } |
| } |
| |
| if c.flagVersion > 0 { |
| if c.flagOptions == nil { |
| c.flagOptions = make(map[string]string) |
| } |
| c.flagOptions["version"] = strconv.Itoa(c.flagVersion) |
| } |
| |
| // Append a trailing slash to indicate it's a path in output |
| mountPath = ensureTrailingSlash(mountPath) |
| |
| // Build mount input |
| mountInput := &api.MountInput{ |
| Type: engineType, |
| Description: c.flagDescription, |
| Local: c.flagLocal, |
| SealWrap: c.flagSealWrap, |
| ExternalEntropyAccess: c.flagExternalEntropyAccess, |
| Config: api.MountConfigInput{ |
| DefaultLeaseTTL: c.flagDefaultLeaseTTL.String(), |
| MaxLeaseTTL: c.flagMaxLeaseTTL.String(), |
| ForceNoCache: c.flagForceNoCache, |
| }, |
| Options: c.flagOptions, |
| } |
| |
| // Set these values only if they are provided in the CLI |
| f.Visit(func(fl *flag.Flag) { |
| if fl.Name == flagNameAuditNonHMACRequestKeys { |
| mountInput.Config.AuditNonHMACRequestKeys = c.flagAuditNonHMACRequestKeys |
| } |
| |
| if fl.Name == flagNameAuditNonHMACResponseKeys { |
| mountInput.Config.AuditNonHMACResponseKeys = c.flagAuditNonHMACResponseKeys |
| } |
| |
| if fl.Name == flagNameListingVisibility { |
| mountInput.Config.ListingVisibility = c.flagListingVisibility |
| } |
| |
| if fl.Name == flagNamePassthroughRequestHeaders { |
| mountInput.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders |
| } |
| |
| if fl.Name == flagNameAllowedResponseHeaders { |
| mountInput.Config.AllowedResponseHeaders = c.flagAllowedResponseHeaders |
| } |
| |
| if fl.Name == flagNameAllowedManagedKeys { |
| mountInput.Config.AllowedManagedKeys = c.flagAllowedManagedKeys |
| } |
| |
| if fl.Name == flagNamePluginVersion { |
| mountInput.Config.PluginVersion = c.flagPluginVersion |
| } |
| }) |
| |
| if err := client.Sys().Mount(mountPath, mountInput); err != nil { |
| c.UI.Error(fmt.Sprintf("Error enabling: %s", err)) |
| return 2 |
| } |
| |
| thing := engineType + " secrets engine" |
| if engineType == "plugin" { |
| thing = c.flagPluginName + " plugin" |
| } |
| if c.flagPluginVersion != "" { |
| thing += " version " + c.flagPluginVersion |
| } |
| c.UI.Output(fmt.Sprintf("Success! Enabled the %s at: %s", thing, mountPath)) |
| return 0 |
| } |