Update init command
This commit is contained in:
parent
9500cb7fc7
commit
076703ebc1
906
command/init.go
906
command/init.go
|
@ -1,406 +1,594 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
"github.com/ghodss/yaml"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||||
"github.com/hashicorp/vault/meta"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/hashicorp/vault/physical/consul"
|
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ensure we are implementing the right interfaces.
|
||||||
|
var _ cli.Command = (*InitCommand)(nil)
|
||||||
|
var _ cli.CommandAutocomplete = (*InitCommand)(nil)
|
||||||
|
|
||||||
// InitCommand is a Command that initializes a new Vault server.
|
// InitCommand is a Command that initializes a new Vault server.
|
||||||
type InitCommand struct {
|
type InitCommand struct {
|
||||||
meta.Meta
|
*BaseCommand
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InitCommand) Run(args []string) int {
|
flagStatus bool
|
||||||
var threshold, shares, storedShares, recoveryThreshold, recoveryShares int
|
flagKeyShares int
|
||||||
var pgpKeys, recoveryPgpKeys, rootTokenPgpKey pgpkeys.PubKeyFilesFlag
|
flagKeyThreshold int
|
||||||
var auto, check bool
|
flagPGPKeys []string
|
||||||
var consulServiceName string
|
flagRootTokenPGPKey string
|
||||||
flags := c.Meta.FlagSet("init", meta.FlagSetDefault)
|
|
||||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
||||||
flags.IntVar(&shares, "key-shares", 5, "")
|
|
||||||
flags.IntVar(&threshold, "key-threshold", 3, "")
|
|
||||||
flags.IntVar(&storedShares, "stored-shares", 0, "")
|
|
||||||
flags.Var(&pgpKeys, "pgp-keys", "")
|
|
||||||
flags.Var(&rootTokenPgpKey, "root-token-pgp-key", "")
|
|
||||||
flags.IntVar(&recoveryShares, "recovery-shares", 5, "")
|
|
||||||
flags.IntVar(&recoveryThreshold, "recovery-threshold", 3, "")
|
|
||||||
flags.Var(&recoveryPgpKeys, "recovery-pgp-keys", "")
|
|
||||||
flags.BoolVar(&check, "check", false, "")
|
|
||||||
flags.BoolVar(&auto, "auto", false, "")
|
|
||||||
flags.StringVar(&consulServiceName, "consul-service", consul.DefaultServiceName, "")
|
|
||||||
if err := flags.Parse(args); err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
initRequest := &api.InitRequest{
|
// HSM
|
||||||
SecretShares: shares,
|
flagStoredShares int
|
||||||
SecretThreshold: threshold,
|
flagRecoveryShares int
|
||||||
StoredShares: storedShares,
|
flagRecoveryThreshold int
|
||||||
PGPKeys: pgpKeys,
|
flagRecoveryPGPKeys []string
|
||||||
RecoveryShares: recoveryShares,
|
|
||||||
RecoveryThreshold: recoveryThreshold,
|
|
||||||
RecoveryPGPKeys: recoveryPgpKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(rootTokenPgpKey) {
|
// Consul
|
||||||
case 0:
|
flagConsulAuto bool
|
||||||
case 1:
|
flagConsulService string
|
||||||
initRequest.RootTokenPGPKey = rootTokenPgpKey[0]
|
|
||||||
default:
|
|
||||||
c.Ui.Error("Only one PGP key can be specified for encrypting the root token")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// If running in 'auto' mode, run service discovery based on environment
|
// Deprecations
|
||||||
// variables of Consul.
|
// TODO: remove in 0.9.0
|
||||||
if auto {
|
flagAuto bool
|
||||||
|
flagCheck bool
|
||||||
// Create configuration for Consul
|
|
||||||
consulConfig := consulapi.DefaultConfig()
|
|
||||||
|
|
||||||
// Create a client to communicate with Consul
|
|
||||||
consulClient, err := consulapi.NewClient(consulConfig)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to create Consul client:%v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch Vault's protocol scheme from the client
|
|
||||||
vaultclient, err := c.Client()
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to fetch Vault client: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if vaultclient.Address() == "" {
|
|
||||||
c.Ui.Error("Failed to fetch Vault client address")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
clientURL, err := url.Parse(vaultclient.Address())
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse Vault address: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientURL == nil {
|
|
||||||
c.Ui.Error("Failed to parse Vault client address")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var uninitializedVaults []string
|
|
||||||
var initializedVault string
|
|
||||||
|
|
||||||
// Query the nodes belonging to the cluster
|
|
||||||
if services, _, err := consulClient.Catalog().Service(consulServiceName, "", &consulapi.QueryOptions{AllowStale: true}); err == nil {
|
|
||||||
Loop:
|
|
||||||
for _, service := range services {
|
|
||||||
vaultAddress := &url.URL{
|
|
||||||
Scheme: clientURL.Scheme,
|
|
||||||
Host: fmt.Sprintf("%s:%d", service.ServiceAddress, service.ServicePort),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set VAULT_ADDR to the discovered node
|
|
||||||
os.Setenv(api.EnvVaultAddress, vaultAddress.String())
|
|
||||||
|
|
||||||
// Create a client to communicate with the discovered node
|
|
||||||
client, err := c.Client()
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing client: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the initialization status of the discovered node
|
|
||||||
inited, err := client.Sys().InitStatus()
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error checking initialization status of discovered node: %+q. Err: %v", vaultAddress.String(), err))
|
|
||||||
return 1
|
|
||||||
case inited:
|
|
||||||
// One of the nodes in the cluster is initialized. Break out.
|
|
||||||
initializedVault = vaultAddress.String()
|
|
||||||
break Loop
|
|
||||||
default:
|
|
||||||
// Vault is uninitialized.
|
|
||||||
uninitializedVaults = append(uninitializedVaults, vaultAddress.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export := "export"
|
|
||||||
quote := "'"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
export = "set"
|
|
||||||
quote = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if initializedVault != "" {
|
|
||||||
vaultURL, err := url.Parse(initializedVault)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse Vault address: %+q. Err: %v", initializedVault, err))
|
|
||||||
}
|
|
||||||
c.Ui.Output(fmt.Sprintf("Discovered an initialized Vault node at %+q, using Consul service name %+q", vaultURL.String(), consulServiceName))
|
|
||||||
c.Ui.Output("\nSet the following environment variable to operate on the discovered Vault:\n")
|
|
||||||
c.Ui.Output(fmt.Sprintf("\t%s VAULT_ADDR=%s%s%s", export, quote, vaultURL.String(), quote))
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(uninitializedVaults) {
|
|
||||||
case 0:
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to discover Vault nodes using Consul service name %+q", consulServiceName))
|
|
||||||
return 1
|
|
||||||
case 1:
|
|
||||||
// There was only one node found in the Vault cluster and it
|
|
||||||
// was uninitialized.
|
|
||||||
|
|
||||||
vaultURL, err := url.Parse(uninitializedVaults[0])
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse Vault address: %+q. Err: %v", uninitializedVaults[0], err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the VAULT_ADDR to the discovered node. This will ensure
|
|
||||||
// that the client created will operate on the discovered node.
|
|
||||||
os.Setenv(api.EnvVaultAddress, vaultURL.String())
|
|
||||||
|
|
||||||
// Let the client know that initialization is perfomed on the
|
|
||||||
// discovered node.
|
|
||||||
c.Ui.Output(fmt.Sprintf("Discovered Vault at %+q using Consul service name %+q\n", vaultURL.String(), consulServiceName))
|
|
||||||
|
|
||||||
// Attempt initializing it
|
|
||||||
ret := c.runInit(check, initRequest)
|
|
||||||
|
|
||||||
// Regardless of success or failure, instruct client to update VAULT_ADDR
|
|
||||||
c.Ui.Output("\nSet the following environment variable to operate on the discovered Vault:\n")
|
|
||||||
c.Ui.Output(fmt.Sprintf("\t%s VAULT_ADDR=%s%s%s", export, quote, vaultURL.String(), quote))
|
|
||||||
|
|
||||||
return ret
|
|
||||||
default:
|
|
||||||
// If more than one Vault node were discovered, print out all of them,
|
|
||||||
// requiring the client to update VAULT_ADDR and to run init again.
|
|
||||||
c.Ui.Output(fmt.Sprintf("Discovered more than one uninitialized Vaults using Consul service name %+q\n", consulServiceName))
|
|
||||||
c.Ui.Output("To initialize these Vaults, set any *one* of the following environment variables and run 'vault init':")
|
|
||||||
|
|
||||||
// Print valid commands to make setting the variables easier
|
|
||||||
for _, vaultNode := range uninitializedVaults {
|
|
||||||
vaultURL, err := url.Parse(vaultNode)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse Vault address: %+q. Err: %v", vaultNode, err))
|
|
||||||
}
|
|
||||||
c.Ui.Output(fmt.Sprintf("\t%s VAULT_ADDR=%s%s%s", export, quote, vaultURL.String(), quote))
|
|
||||||
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.runInit(check, initRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InitCommand) runInit(check bool, initRequest *api.InitRequest) int {
|
|
||||||
client, err := c.Client()
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Error initializing client: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if check {
|
|
||||||
return c.checkStatus(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Sys().Init(initRequest)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Error initializing Vault: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, key := range resp.Keys {
|
|
||||||
if resp.KeysB64 != nil && len(resp.KeysB64) == len(resp.Keys) {
|
|
||||||
c.Ui.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, resp.KeysB64[i]))
|
|
||||||
} else {
|
|
||||||
c.Ui.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, key := range resp.RecoveryKeys {
|
|
||||||
if resp.RecoveryKeysB64 != nil && len(resp.RecoveryKeysB64) == len(resp.RecoveryKeys) {
|
|
||||||
c.Ui.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, resp.RecoveryKeysB64[i]))
|
|
||||||
} else {
|
|
||||||
c.Ui.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf("Initial Root Token: %s", resp.RootToken))
|
|
||||||
|
|
||||||
if initRequest.StoredShares < 1 {
|
|
||||||
c.Ui.Output(fmt.Sprintf(
|
|
||||||
"\n"+
|
|
||||||
"Vault initialized with %d keys and a key threshold of %d. Please\n"+
|
|
||||||
"securely distribute the above keys. When the vault is re-sealed,\n"+
|
|
||||||
"restarted, or stopped, you must provide at least %d of these keys\n"+
|
|
||||||
"to unseal it again.\n\n"+
|
|
||||||
"Vault does not store the master key. Without at least %d keys,\n"+
|
|
||||||
"your vault will remain permanently sealed.",
|
|
||||||
initRequest.SecretShares,
|
|
||||||
initRequest.SecretThreshold,
|
|
||||||
initRequest.SecretThreshold,
|
|
||||||
initRequest.SecretThreshold,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
c.Ui.Output(
|
|
||||||
"\n" +
|
|
||||||
"Vault initialized successfully.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(resp.RecoveryKeys) > 0 {
|
|
||||||
c.Ui.Output(fmt.Sprintf(
|
|
||||||
"\n"+
|
|
||||||
"Recovery key initialized with %d keys and a key threshold of %d. Please\n"+
|
|
||||||
"securely distribute the above keys.",
|
|
||||||
initRequest.RecoveryShares,
|
|
||||||
initRequest.RecoveryThreshold,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InitCommand) checkStatus(client *api.Client) int {
|
|
||||||
inited, err := client.Sys().InitStatus()
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Error checking initialization status: %s", err))
|
|
||||||
return 1
|
|
||||||
case inited:
|
|
||||||
c.Ui.Output("Vault has been initialized")
|
|
||||||
return 0
|
|
||||||
default:
|
|
||||||
c.Ui.Output("Vault is not initialized")
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InitCommand) Synopsis() string {
|
func (c *InitCommand) Synopsis() string {
|
||||||
return "Initialize a new Vault server"
|
return "Initializes a server"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InitCommand) Help() string {
|
func (c *InitCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: vault init [options]
|
Usage: vault init [options]
|
||||||
|
|
||||||
Initialize a new Vault server.
|
Initializes a Vault server. Initialization is the process by which Vault's
|
||||||
|
storage backend is prepared to receive data. Since Vault server's share the
|
||||||
|
same storage backend in HA mode, you only need to initialize one Vault to
|
||||||
|
initialize the storage backend.
|
||||||
|
|
||||||
This command connects to a Vault server and initializes it for the
|
During initialization, Vault generates an in-memory master key and applies
|
||||||
first time. This sets up the initial set of master keys and the
|
Shamir's secret sharing algorithm to disassemble that master key into a
|
||||||
backend data store structure.
|
configuration number of key shares such that a configurable subset of those
|
||||||
|
key shares must come together to regenerate the master key. These keys are
|
||||||
|
often called "unseal keys" in Vault's documentation.
|
||||||
|
|
||||||
This command can't be called on an already-initialized Vault server.
|
This command cannot be run against already-initialized Vault cluster.
|
||||||
|
|
||||||
General Options:
|
Start initialization with the default options:
|
||||||
` + meta.GeneralOptionsUsage() + `
|
|
||||||
Init Options:
|
|
||||||
|
|
||||||
-check Don't actually initialize, just check if Vault is
|
$ vault init
|
||||||
already initialized. A return code of 0 means Vault
|
|
||||||
is initialized; a return code of 2 means Vault is not
|
|
||||||
initialized; a return code of 1 means an error was
|
|
||||||
encountered.
|
|
||||||
|
|
||||||
-key-shares=5 The number of key shares to split the master key
|
Initialize, but encrypt the unseal keys with pgp keys:
|
||||||
into.
|
|
||||||
|
|
||||||
-key-threshold=3 The number of key shares required to reconstruct
|
$ vault init \
|
||||||
the master key.
|
-key-shares=3 \
|
||||||
|
-key-threshold=2 \
|
||||||
|
-pgp-keys="keybase:hashicorp,keybase:jefferai,keybase:sethvargo"
|
||||||
|
|
||||||
-stored-shares=0 The number of unseal keys to store. Only used with
|
Encrypt the initial root token using a pgp key:
|
||||||
Vault HSM. Must currently be equivalent to the
|
|
||||||
number of shares.
|
|
||||||
|
|
||||||
-pgp-keys If provided, must be a comma-separated list of
|
$ vault init -root-token-pgp-key="keybase:hashicorp"
|
||||||
files on disk containing binary- or base64-format
|
|
||||||
public PGP keys, or Keybase usernames specified as
|
|
||||||
"keybase:<username>". The output unseal keys will
|
|
||||||
be encrypted and base64-encoded, in order, with the
|
|
||||||
given public keys. If you want to use them with the
|
|
||||||
'vault unseal' command, you will need to base64-
|
|
||||||
decode and decrypt; this will be the plaintext
|
|
||||||
unseal key. When 'stored-shares' are not used, the
|
|
||||||
number of entries in this field must match 'key-shares'.
|
|
||||||
When 'stored-shares' are used, the number of entries
|
|
||||||
should match the difference between 'key-shares'
|
|
||||||
and 'stored-shares'.
|
|
||||||
|
|
||||||
-root-token-pgp-key If provided, a file on disk with a binary- or
|
For a complete list of examples, please see the documentation.
|
||||||
base64-format public PGP key, or a Keybase username
|
|
||||||
specified as "keybase:<username>". The output root
|
|
||||||
token will be encrypted and base64-encoded, in
|
|
||||||
order, with the given public key. You will need
|
|
||||||
to base64-decode and decrypt the result.
|
|
||||||
|
|
||||||
-recovery-shares=5 The number of key shares to split the recovery key
|
` + c.Flags().Help()
|
||||||
into. Only used with Vault HSM.
|
|
||||||
|
|
||||||
-recovery-threshold=3 The number of key shares required to reconstruct
|
|
||||||
the recovery key. Only used with Vault HSM.
|
|
||||||
|
|
||||||
-recovery-pgp-keys If provided, behaves like "pgp-keys" but for the
|
|
||||||
recovery key shares. Only used with Vault HSM.
|
|
||||||
|
|
||||||
-auto If set, performs service discovery using Consul.
|
|
||||||
When all the nodes of a Vault cluster are
|
|
||||||
registered with Consul, setting this flag will
|
|
||||||
trigger service discovery using the service name
|
|
||||||
with which Vault nodes are registered. This option
|
|
||||||
works well when each Vault cluster is registered
|
|
||||||
under a unique service name. Note that, when Consul
|
|
||||||
is serving as Vault's HA backend, Vault nodes are
|
|
||||||
registered with Consul by default. The service name
|
|
||||||
can be changed using 'consul-service' flag. Ensure
|
|
||||||
that environment variables required to communicate
|
|
||||||
with Consul, like (CONSUL_HTTP_ADDR,
|
|
||||||
CONSUL_HTTP_TOKEN, CONSUL_HTTP_SSL, et al) are
|
|
||||||
properly set. When only one Vault node is
|
|
||||||
discovered, it will be initialized and when more
|
|
||||||
than one Vault node is discovered, they will be
|
|
||||||
output for easy selection.
|
|
||||||
|
|
||||||
-consul-service Service name under which all the nodes of a Vault
|
|
||||||
cluster are registered with Consul. Note that, when
|
|
||||||
Vault uses Consul as its HA backend, by default,
|
|
||||||
Vault will register itself as a service with Consul
|
|
||||||
with the service name "vault". This name can be
|
|
||||||
modified in Vault's configuration file, using the
|
|
||||||
"service" option for the Consul backend.
|
|
||||||
`
|
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *InitCommand) Flags() *FlagSets {
|
||||||
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||||
|
|
||||||
|
// Common Options
|
||||||
|
f := set.NewFlagSet("Common Options")
|
||||||
|
|
||||||
|
f.BoolVar(&BoolVar{
|
||||||
|
Name: "status",
|
||||||
|
Target: &c.flagStatus,
|
||||||
|
Default: false,
|
||||||
|
Usage: "Print the current initialization status. An exit code of 0 means " +
|
||||||
|
"the Vault is already initialized. An exit code of 1 means an error " +
|
||||||
|
"occurred. An exit code of 2 means the mean is not initialized.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.IntVar(&IntVar{
|
||||||
|
Name: "key-shares",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Target: &c.flagKeyShares,
|
||||||
|
Default: 5,
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Number of key shares to split the generated master key into. " +
|
||||||
|
"This is the number of \"unseal keys\" to generate.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.IntVar(&IntVar{
|
||||||
|
Name: "key-threshold",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Target: &c.flagKeyThreshold,
|
||||||
|
Default: 3,
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Number of key shares required to reconstruct the master key. " +
|
||||||
|
"This must be less than or equal to -key-shares.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.VarFlag(&VarFlag{
|
||||||
|
Name: "pgp-keys",
|
||||||
|
Value: (*pgpkeys.PubKeyFilesFlag)(&c.flagPGPKeys),
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Comma-separated list of paths to files on disk containing " +
|
||||||
|
"public GPG keys OR a comma-separated list of Keybase usernames using " +
|
||||||
|
"the format \"keybase:<username>\". When supplied, the generated " +
|
||||||
|
"unseal keys will be encrypted and base64-encoded in the order " +
|
||||||
|
"specified in this list. The number of entires must match -key-shares, " +
|
||||||
|
"unless -store-shares are used.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.VarFlag(&VarFlag{
|
||||||
|
Name: "root-token-pgp-key",
|
||||||
|
Value: (*pgpkeys.PubKeyFileFlag)(&c.flagRootTokenPGPKey),
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Path to a file on disk containing a binary or base64-encoded " +
|
||||||
|
"public GPG key. This can also be specified as a Keybase username " +
|
||||||
|
"using the format \"keybase:<username>\". When supplied, the generated " +
|
||||||
|
"root token will be encrypted and base64-encoded with the given public " +
|
||||||
|
"key.",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Consul Options
|
||||||
|
f = set.NewFlagSet("Consul Options")
|
||||||
|
|
||||||
|
f.BoolVar(&BoolVar{
|
||||||
|
Name: "consul-auto",
|
||||||
|
Target: &c.flagConsulAuto,
|
||||||
|
Default: false,
|
||||||
|
Usage: "Perform automatic service discovery using Consul in HA mode. " +
|
||||||
|
"When all nodes in a Vault HA cluster are registered with Consul, " +
|
||||||
|
"enabling this option will trigger automatic service discovery based " +
|
||||||
|
"on the provided -consul-service value. When Consul is Vault's HA " +
|
||||||
|
"backend, this functionality is automatically enabled. Ensure the " +
|
||||||
|
"proper Consul environment variables are set (CONSUL_HTTP_ADDR, etc). " +
|
||||||
|
"When only one Vault server is discovered, it will be initialized " +
|
||||||
|
"automatically. When more than one Vault server is discovered, they " +
|
||||||
|
"will each be output for selection.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.StringVar(&StringVar{
|
||||||
|
Name: "consul-service",
|
||||||
|
Target: &c.flagConsulService,
|
||||||
|
Default: "vault",
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Name of the service in Consul under which the Vault servers are " +
|
||||||
|
"registered.",
|
||||||
|
})
|
||||||
|
|
||||||
|
// HSM Options
|
||||||
|
f = set.NewFlagSet("HSM Options")
|
||||||
|
|
||||||
|
f.IntVar(&IntVar{
|
||||||
|
Name: "recovery-shares",
|
||||||
|
Target: &c.flagRecoveryShares,
|
||||||
|
Default: 5,
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Number of key shares to split the recovery key into. " +
|
||||||
|
"This is only used in HSM mode.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.IntVar(&IntVar{
|
||||||
|
Name: "recovery-threshold",
|
||||||
|
Target: &c.flagRecoveryThreshold,
|
||||||
|
Default: 3,
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Number of key shares required to reconstruct the recovery key. " +
|
||||||
|
"This is only used in HSM mode.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.VarFlag(&VarFlag{
|
||||||
|
Name: "recovery-pgp-keys",
|
||||||
|
Value: (*pgpkeys.PubKeyFilesFlag)(&c.flagRecoveryPGPKeys),
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Behaves like -pgp-keys, but for the recovery key shares. This " +
|
||||||
|
"is only used in HSM mode.",
|
||||||
|
})
|
||||||
|
|
||||||
|
f.IntVar(&IntVar{
|
||||||
|
Name: "stored-shares",
|
||||||
|
Target: &c.flagStoredShares,
|
||||||
|
Default: 0, // No default, because we need to check if was supplied
|
||||||
|
Completion: complete.PredictAnything,
|
||||||
|
Usage: "Number of unseal keys to store on an HSM. This must be equal to " +
|
||||||
|
"-key-shares. This is only used in HSM mode.",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Deprecations
|
||||||
|
// TODO: remove in 0.9.0
|
||||||
|
f.BoolVar(&BoolVar{
|
||||||
|
Name: "check", // prefer -status
|
||||||
|
Target: &c.flagCheck,
|
||||||
|
Default: false,
|
||||||
|
Hidden: true,
|
||||||
|
Usage: "",
|
||||||
|
})
|
||||||
|
f.BoolVar(&BoolVar{
|
||||||
|
Name: "auto", // prefer -consul-auto
|
||||||
|
Target: &c.flagAuto,
|
||||||
|
Default: false,
|
||||||
|
Hidden: true,
|
||||||
|
Usage: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
func (c *InitCommand) AutocompleteArgs() complete.Predictor {
|
func (c *InitCommand) AutocompleteArgs() complete.Predictor {
|
||||||
return complete.PredictNothing
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InitCommand) AutocompleteFlags() complete.Flags {
|
func (c *InitCommand) AutocompleteFlags() complete.Flags {
|
||||||
return complete.Flags{
|
return c.Flags().Completions()
|
||||||
"-check": complete.PredictNothing,
|
}
|
||||||
"-key-shares": complete.PredictNothing,
|
|
||||||
"-key-threshold": complete.PredictNothing,
|
func (c *InitCommand) Run(args []string) int {
|
||||||
"-pgp-keys": complete.PredictNothing,
|
f := c.Flags()
|
||||||
"-root-token-pgp-key": complete.PredictNothing,
|
|
||||||
"-recovery-shares": complete.PredictNothing,
|
if err := f.Parse(args); err != nil {
|
||||||
"-recovery-threshold": complete.PredictNothing,
|
c.UI.Error(err.Error())
|
||||||
"-recovery-pgp-keys": complete.PredictNothing,
|
return 1
|
||||||
"-auto": complete.PredictNothing,
|
}
|
||||||
"-consul-service": complete.PredictNothing,
|
|
||||||
|
// Deprecations
|
||||||
|
// TODO: remove in 0.9.0
|
||||||
|
if c.flagAuto {
|
||||||
|
c.UI.Warn(wrapAtLength("WARNING! -auto is deprecated. Please use " +
|
||||||
|
"-consul-auto instead. This will be removed the next major release " +
|
||||||
|
"of Vault."))
|
||||||
|
c.flagConsulAuto = true
|
||||||
|
}
|
||||||
|
if c.flagCheck {
|
||||||
|
c.UI.Warn(wrapAtLength("WARNING! -check is deprecated. Please use " +
|
||||||
|
"-status instead. This will be removed in the next major release " +
|
||||||
|
"of Vault."))
|
||||||
|
c.flagStatus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the initial init request
|
||||||
|
initReq := &api.InitRequest{
|
||||||
|
SecretShares: c.flagKeyShares,
|
||||||
|
SecretThreshold: c.flagKeyThreshold,
|
||||||
|
PGPKeys: c.flagPGPKeys,
|
||||||
|
RootTokenPGPKey: c.flagRootTokenPGPKey,
|
||||||
|
|
||||||
|
StoredShares: c.flagStoredShares,
|
||||||
|
RecoveryShares: c.flagRecoveryShares,
|
||||||
|
RecoveryThreshold: c.flagRecoveryThreshold,
|
||||||
|
RecoveryPGPKeys: c.flagRecoveryPGPKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := c.Client()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check auto mode
|
||||||
|
switch {
|
||||||
|
case c.flagStatus:
|
||||||
|
return c.status(client)
|
||||||
|
case c.flagConsulAuto:
|
||||||
|
return c.consulAuto(client, initReq)
|
||||||
|
default:
|
||||||
|
return c.init(client, initReq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// consulAuto enables auto-joining via Consul.
|
||||||
|
func (c *InitCommand) consulAuto(client *api.Client, req *api.InitRequest) int {
|
||||||
|
// Capture the client original address and reset it
|
||||||
|
originalAddr := client.Address()
|
||||||
|
defer client.SetAddress(originalAddr)
|
||||||
|
|
||||||
|
// Create a client to communicate with Consul
|
||||||
|
consulClient, err := consulapi.NewClient(consulapi.DefaultConfig())
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to create Consul client:%v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the scheme from the Vault client to determine if the Consul agent
|
||||||
|
// should talk via HTTP or HTTPS.
|
||||||
|
addr := client.Address()
|
||||||
|
clientURL, err := url.Parse(addr)
|
||||||
|
if err != nil || clientURL == nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse Vault address %s: %s", addr, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var uninitedVaults []string
|
||||||
|
var initedVault string
|
||||||
|
|
||||||
|
// Query the nodes belonging to the cluster
|
||||||
|
services, _, err := consulClient.Catalog().Service(c.flagConsulService, "", &consulapi.QueryOptions{
|
||||||
|
AllowStale: true,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
for _, service := range services {
|
||||||
|
// Set the address on the client temporarily
|
||||||
|
vaultAddr := (&url.URL{
|
||||||
|
Scheme: clientURL.Scheme,
|
||||||
|
Host: fmt.Sprintf("%s:%d", service.ServiceAddress, service.ServicePort),
|
||||||
|
}).String()
|
||||||
|
client.SetAddress(vaultAddr)
|
||||||
|
|
||||||
|
// Check the initialization status of the discovered node
|
||||||
|
inited, err := client.Sys().InitStatus()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error checking init status of %q: %s", vaultAddr, err))
|
||||||
|
}
|
||||||
|
if inited {
|
||||||
|
initedVault = vaultAddr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got this far, we communicated successfully with Vault, but it
|
||||||
|
// was not initialized.
|
||||||
|
uninitedVaults = append(uninitedVaults, vaultAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the correct export keywords and quotes for *nix vs Windows
|
||||||
|
export := "export"
|
||||||
|
quote := "\""
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
export = "set"
|
||||||
|
quote = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if initedVault != "" {
|
||||||
|
vaultURL, err := url.Parse(initedVault)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse Vault address %q: %s", initedVault, err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
vaultAddr := vaultURL.String()
|
||||||
|
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Discovered an initialized Vault node at %q with Consul service name "+
|
||||||
|
"%q. Set the following environment variable to target the discovered "+
|
||||||
|
"Vault server:",
|
||||||
|
vaultURL.String(), c.flagConsulService)))
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(fmt.Sprintf(" $ %s VAULT_ADDR=%s%s%s", export, quote, vaultAddr, quote))
|
||||||
|
c.UI.Output("")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(uninitedVaults) {
|
||||||
|
case 0:
|
||||||
|
c.UI.Error(fmt.Sprintf("No Vault nodes registered as %q in Consul", c.flagConsulService))
|
||||||
|
return 2
|
||||||
|
case 1:
|
||||||
|
// There was only one node found in the Vault cluster and it was
|
||||||
|
// uninitialized.
|
||||||
|
vaultURL, err := url.Parse(uninitedVaults[0])
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse Vault address %q: %s", initedVault, err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
vaultAddr := vaultURL.String()
|
||||||
|
|
||||||
|
// Update the client to connect to this Vault server
|
||||||
|
client.SetAddress(vaultAddr)
|
||||||
|
|
||||||
|
// Let the client know that initialization is perfomed on the
|
||||||
|
// discovered node.
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Discovered an initialized Vault node at %q with Consul service name "+
|
||||||
|
"%q. Set the following environment variable to target the discovered "+
|
||||||
|
"Vault server:",
|
||||||
|
vaultURL.String(), c.flagConsulService)))
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(fmt.Sprintf(" $ %s VAULT_ADDR=%s%s%s", export, quote, vaultAddr, quote))
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output("Attempting to initialize it...")
|
||||||
|
c.UI.Output("")
|
||||||
|
|
||||||
|
// Attempt to initialize it
|
||||||
|
return c.init(client, req)
|
||||||
|
default:
|
||||||
|
// If more than one Vault node were discovered, print out all of them,
|
||||||
|
// requiring the client to update VAULT_ADDR and to run init again.
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Discovered %d uninitialized Vault servers with Consul service name "+
|
||||||
|
"%q. To initialize these Vatuls, set any one of the following "+
|
||||||
|
"environment variables and run \"vault init\":",
|
||||||
|
len(uninitedVaults), c.flagConsulService)))
|
||||||
|
c.UI.Output("")
|
||||||
|
|
||||||
|
// Print valid commands to make setting the variables easier
|
||||||
|
for _, node := range uninitedVaults {
|
||||||
|
vaultURL, err := url.Parse(node)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse Vault address %q: %s", initedVault, err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
vaultAddr := vaultURL.String()
|
||||||
|
|
||||||
|
c.UI.Output(fmt.Sprintf(" $ %s VAULT_ADDR=%s%s%s", export, quote, vaultAddr, quote))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UI.Output("")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InitCommand) init(client *api.Client, req *api.InitRequest) int {
|
||||||
|
resp, err := client.Sys().Init(req)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error initializing: %s", err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.flagFormat {
|
||||||
|
case "yaml", "yml":
|
||||||
|
return c.initOutputYAML(req, resp)
|
||||||
|
case "json":
|
||||||
|
return c.initOutputJSON(req, resp)
|
||||||
|
case "table":
|
||||||
|
default:
|
||||||
|
c.UI.Error(fmt.Sprintf("Unknown format: %s", c.flagFormat))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, key := range resp.Keys {
|
||||||
|
if resp.KeysB64 != nil && len(resp.KeysB64) == len(resp.Keys) {
|
||||||
|
c.UI.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, resp.KeysB64[i]))
|
||||||
|
} else {
|
||||||
|
c.UI.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, key := range resp.RecoveryKeys {
|
||||||
|
if resp.RecoveryKeysB64 != nil && len(resp.RecoveryKeysB64) == len(resp.RecoveryKeys) {
|
||||||
|
c.UI.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, resp.RecoveryKeysB64[i]))
|
||||||
|
} else {
|
||||||
|
c.UI.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(fmt.Sprintf("Initial Root Token: %s", resp.RootToken))
|
||||||
|
|
||||||
|
if req.StoredShares < 1 {
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Vault initialized with %d key shares an a key threshold of %d. Please "+
|
||||||
|
"securely distributed the key shares printed above. When the Vault is "+
|
||||||
|
"re-sealed, restarted, or stopped, you must supply at least %d of "+
|
||||||
|
"these keys to unseal it before it can start servicing requests.",
|
||||||
|
req.SecretShares,
|
||||||
|
req.SecretThreshold,
|
||||||
|
req.SecretThreshold)))
|
||||||
|
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Vault does not store the generated master key. Without at least %d "+
|
||||||
|
"key to reconstruct the master key, Vault will remain permanently "+
|
||||||
|
"sealed!",
|
||||||
|
req.SecretThreshold)))
|
||||||
|
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(wrapAtLength(
|
||||||
|
"It is possible to generate new unseal keys, provided you have a quorum " +
|
||||||
|
"of existing unseal keys shares. See \"vault rekey\" for more " +
|
||||||
|
"information."))
|
||||||
|
} else {
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output("Success! Vault is initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.RecoveryKeys) > 0 {
|
||||||
|
c.UI.Output("")
|
||||||
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
||||||
|
"Recovery key initialized with %d key shares and a key threshold of %d. "+
|
||||||
|
"Please securely distribute the key shares printed above.",
|
||||||
|
req.RecoveryShares,
|
||||||
|
req.RecoveryThreshold)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// initOutputYAML outputs the init output as YAML.
|
||||||
|
func (c *InitCommand) initOutputYAML(req *api.InitRequest, resp *api.InitResponse) int {
|
||||||
|
b, err := yaml.Marshal(newMachineInit(req, resp))
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error marshaling YAML: %s", err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return PrintRaw(c.UI, strings.TrimSpace(string(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// initOutputJSON outputs the init output as JSON.
|
||||||
|
func (c *InitCommand) initOutputJSON(req *api.InitRequest, resp *api.InitResponse) int {
|
||||||
|
b, err := json.Marshal(newMachineInit(req, resp))
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error marshaling JSON: %s", err))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return PrintRaw(c.UI, strings.TrimSpace(string(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// status inspects the init status of vault and returns an appropriate error
|
||||||
|
// code and message.
|
||||||
|
func (c *InitCommand) status(client *api.Client) int {
|
||||||
|
inited, err := client.Sys().InitStatus()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error checking init status: %s", err))
|
||||||
|
return 1 // Normally we'd return 2, but 2 means something special here
|
||||||
|
}
|
||||||
|
|
||||||
|
if inited {
|
||||||
|
c.UI.Output("Vault is initialized")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UI.Output("Vault is not initialized")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// machineInit is used to output information about the init command.
|
||||||
|
type machineInit struct {
|
||||||
|
UnsealKeysB64 []string `json:"unseal_keys_b64"`
|
||||||
|
UnsealKeysHex []string `json:"unseal_keys_hex"`
|
||||||
|
UnsealShares int `json:"unseal_shares"`
|
||||||
|
UnsealThreshold int `json:"unseal_threshold"`
|
||||||
|
RecoveryKeysB64 []string `json:"recovery_keys_b64"`
|
||||||
|
RecoveryKeysHex []string `json:"recovery_keys_hex"`
|
||||||
|
RecoveryShares int `json:"recovery_keys_shares"`
|
||||||
|
RecoveryThreshold int `json:"recovery_keys_threshold"`
|
||||||
|
RootToken string `json:"root_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMachineInit(req *api.InitRequest, resp *api.InitResponse) *machineInit {
|
||||||
|
init := &machineInit{}
|
||||||
|
|
||||||
|
init.UnsealKeysHex = make([]string, len(resp.Keys))
|
||||||
|
for i, v := range resp.Keys {
|
||||||
|
init.UnsealKeysHex[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
init.UnsealKeysB64 = make([]string, len(resp.KeysB64))
|
||||||
|
for i, v := range resp.KeysB64 {
|
||||||
|
init.UnsealKeysB64[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
init.UnsealShares = req.SecretShares
|
||||||
|
init.UnsealThreshold = req.SecretThreshold
|
||||||
|
|
||||||
|
init.RecoveryKeysHex = make([]string, len(resp.RecoveryKeys))
|
||||||
|
for i, v := range resp.RecoveryKeys {
|
||||||
|
init.RecoveryKeysHex[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
init.RecoveryKeysB64 = make([]string, len(resp.RecoveryKeysB64))
|
||||||
|
for i, v := range resp.RecoveryKeysB64 {
|
||||||
|
init.RecoveryKeysB64[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
init.RecoveryShares = req.RecoveryShares
|
||||||
|
init.RecoveryThreshold = req.RecoveryThreshold
|
||||||
|
|
||||||
|
init.RootToken = resp.RootToken
|
||||||
|
|
||||||
|
return init
|
||||||
|
}
|
||||||
|
|
|
@ -1,343 +1,361 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"encoding/base64"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||||
"github.com/hashicorp/vault/http"
|
|
||||||
"github.com/hashicorp/vault/meta"
|
|
||||||
"github.com/hashicorp/vault/vault"
|
|
||||||
"github.com/keybase/go-crypto/openpgp"
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
func testInitCommand(tb testing.TB) (*cli.MockUi, *InitCommand) {
|
||||||
ui := new(cli.MockUi)
|
tb.Helper()
|
||||||
c := &InitCommand{
|
|
||||||
Meta: meta.Meta{
|
ui := cli.NewMockUi()
|
||||||
Ui: ui,
|
return ui, &InitCommand{
|
||||||
|
BaseCommand: &BaseCommand{
|
||||||
|
UI: ui,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
core := vault.TestCore(t)
|
|
||||||
ln, addr := http.TestServer(t, core)
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
init, err := core.Initialized()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if init {
|
|
||||||
t.Fatal("should not be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{"-address", addr}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
init, err = core.Initialized()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if !init {
|
|
||||||
t.Fatal("should be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealConf, err := core.SealAccess().BarrierConfig()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
expected := &vault.SealConfig{
|
|
||||||
Type: "shamir",
|
|
||||||
SecretShares: 5,
|
|
||||||
SecretThreshold: 3,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(expected, sealConf) {
|
|
||||||
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInit_Check(t *testing.T) {
|
func TestInitCommand_Run(t *testing.T) {
|
||||||
ui := new(cli.MockUi)
|
t.Parallel()
|
||||||
c := &InitCommand{
|
|
||||||
Meta: meta.Meta{
|
cases := []struct {
|
||||||
Ui: ui,
|
name string
|
||||||
|
args []string
|
||||||
|
out string
|
||||||
|
code int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"pgp_keys_multi",
|
||||||
|
[]string{
|
||||||
|
"-pgp-keys", "keybase:hashicorp",
|
||||||
|
"-pgp-keys", "keybase:jefferai",
|
||||||
|
},
|
||||||
|
"can only be specified once",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"root_token_pgp_key_multi",
|
||||||
|
[]string{
|
||||||
|
"-root-token-pgp-key", "keybase:hashicorp",
|
||||||
|
"-root-token-pgp-key", "keybase:jefferai",
|
||||||
|
},
|
||||||
|
"can only be specified once",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"root_token_pgp_key_multi_inline",
|
||||||
|
[]string{
|
||||||
|
"-root-token-pgp-key", "keybase:hashicorp,keybase:jefferai",
|
||||||
|
},
|
||||||
|
"can only specify one pgp key",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recovery_pgp_keys_multi",
|
||||||
|
[]string{
|
||||||
|
"-recovery-pgp-keys", "keybase:hashicorp",
|
||||||
|
"-recovery-pgp-keys", "keybase:jefferai",
|
||||||
|
},
|
||||||
|
"can only be specified once",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_shares_pgp_less",
|
||||||
|
[]string{
|
||||||
|
"-key-shares", "10",
|
||||||
|
"-pgp-keys", "keybase:jefferai,keybase:sethvargo",
|
||||||
|
},
|
||||||
|
"incorrect number",
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_shares_pgp_more",
|
||||||
|
[]string{
|
||||||
|
"-key-shares", "1",
|
||||||
|
"-pgp-keys", "keybase:jefferai,keybase:sethvargo",
|
||||||
|
},
|
||||||
|
"incorrect number",
|
||||||
|
2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
core := vault.TestCore(t)
|
t.Run("validations", func(t *testing.T) {
|
||||||
ln, addr := http.TestServer(t, core)
|
t.Parallel()
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
// Should return 2, not initialized
|
for _, tc := range cases {
|
||||||
args := []string{"-address", addr, "-check"}
|
tc := tc
|
||||||
if code := c.Run(args); code != 2 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now initialize it
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
args = []string{"-address", addr}
|
t.Parallel()
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should return 0, initialized
|
client, closer := testVaultServer(t)
|
||||||
args = []string{"-address", addr, "-check"}
|
defer closer()
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
init, err := core.Initialized()
|
ui, cmd := testInitCommand(t)
|
||||||
if err != nil {
|
cmd.client = client
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if !init {
|
|
||||||
t.Fatal("should be initialized")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInit_custom(t *testing.T) {
|
code := cmd.Run(tc.args)
|
||||||
ui := new(cli.MockUi)
|
if code != tc.code {
|
||||||
c := &InitCommand{
|
t.Errorf("expected %d to be %d", code, tc.code)
|
||||||
Meta: meta.Meta{
|
}
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
core := vault.TestCore(t)
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
ln, addr := http.TestServer(t, core)
|
if !strings.Contains(combined, tc.out) {
|
||||||
defer ln.Close()
|
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||||
|
}
|
||||||
init, err := core.Initialized()
|
})
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if init {
|
|
||||||
t.Fatal("should not be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-address", addr,
|
|
||||||
"-key-shares", "7",
|
|
||||||
"-key-threshold", "3",
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
init, err = core.Initialized()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if !init {
|
|
||||||
t.Fatal("should be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealConf, err := core.SealAccess().BarrierConfig()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
expected := &vault.SealConfig{
|
|
||||||
Type: "shamir",
|
|
||||||
SecretShares: 7,
|
|
||||||
SecretThreshold: 3,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(expected, sealConf) {
|
|
||||||
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
|
|
||||||
}
|
|
||||||
|
|
||||||
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error compiling regex: %s", err)
|
|
||||||
}
|
|
||||||
matches := re.FindAllStringSubmatch(ui.OutputWriter.String(), -1)
|
|
||||||
if len(matches) != 1 {
|
|
||||||
t.Fatalf("Unexpected number of tokens found, got %d", len(matches))
|
|
||||||
}
|
|
||||||
|
|
||||||
rootToken := matches[0][1]
|
|
||||||
|
|
||||||
client, err := c.Client()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error fetching client: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.SetToken(rootToken)
|
|
||||||
|
|
||||||
re, err = regexp.Compile("\\s*Unseal Key \\d+: (.*)")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error compiling regex: %s", err)
|
|
||||||
}
|
|
||||||
matches = re.FindAllStringSubmatch(ui.OutputWriter.String(), -1)
|
|
||||||
if len(matches) != 7 {
|
|
||||||
t.Fatalf("Unexpected number of keys returned, got %d, matches was \n\n%#v\n\n, input was \n\n%s\n\n", len(matches), matches, ui.OutputWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var unsealed bool
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
decodedKey, err := base64.StdEncoding.DecodeString(strings.TrimSpace(matches[i][1]))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err decoding key %v: %v", matches[i][1], err)
|
|
||||||
}
|
}
|
||||||
unsealed, err = core.Unseal(decodedKey)
|
})
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err during unseal: %v; key was %v", err, matches[i][1])
|
t.Run("status", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, closer := testVaultServerUninit(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ui, cmd := testInitCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
// Verify the non-init response code
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"-status",
|
||||||
|
})
|
||||||
|
if exp := 2; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !unsealed {
|
|
||||||
t.Fatal("expected to be unsealed")
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInfo, err := client.Auth().Token().LookupSelf()
|
// Now init to verify the init response code
|
||||||
if err != nil {
|
if _, err := client.Sys().Init(&api.InitRequest{
|
||||||
t.Fatalf("Error looking up root token info: %v", err)
|
SecretShares: 1,
|
||||||
}
|
SecretThreshold: 1,
|
||||||
|
}); err != nil {
|
||||||
if tokenInfo.Data["policies"].([]interface{})[0].(string) != "root" {
|
t.Fatal(err)
|
||||||
t.Fatalf("expected root policy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInit_PGP(t *testing.T) {
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &InitCommand{
|
|
||||||
Meta: meta.Meta{
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
core := vault.TestCore(t)
|
|
||||||
ln, addr := http.TestServer(t, core)
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
init, err := core.Initialized()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if init {
|
|
||||||
t.Fatal("should not be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, pubFiles, err := getPubKeyFiles(t)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-address", addr,
|
|
||||||
"-key-shares", "2",
|
|
||||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2],
|
|
||||||
"-key-threshold", "2",
|
|
||||||
"-root-token-pgp-key", pubFiles[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should fail, as key-shares does not match pgp-keys size
|
|
||||||
if code := c.Run(args); code == 0 {
|
|
||||||
t.Fatalf("bad (command should have failed): %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
args = []string{
|
|
||||||
"-address", addr,
|
|
||||||
"-key-shares", "4",
|
|
||||||
"-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2] + "," + pubFiles[3],
|
|
||||||
"-key-threshold", "2",
|
|
||||||
"-root-token-pgp-key", pubFiles[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.OutputWriter.Reset()
|
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
init, err = core.Initialized()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if !init {
|
|
||||||
t.Fatal("should be initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealConf, err := core.SealAccess().BarrierConfig()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pgpKeys := []string{}
|
|
||||||
for _, pubFile := range pubFiles {
|
|
||||||
pub, err := pgpkeys.ReadPGPFile(pubFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad: %v", err)
|
|
||||||
}
|
}
|
||||||
pgpKeys = append(pgpKeys, pub)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := &vault.SealConfig{
|
// Verify the init response code
|
||||||
Type: "shamir",
|
ui, cmd = testInitCommand(t)
|
||||||
SecretShares: 4,
|
cmd.client = client
|
||||||
SecretThreshold: 2,
|
code = cmd.Run([]string{
|
||||||
PGPKeys: pgpKeys,
|
"-status",
|
||||||
}
|
})
|
||||||
if !reflect.DeepEqual(expected, sealConf) {
|
if exp := 0; code != exp {
|
||||||
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
|
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")
|
t.Run("default", func(t *testing.T) {
|
||||||
if err != nil {
|
t.Parallel()
|
||||||
t.Fatalf("Error compiling regex: %s", err)
|
|
||||||
}
|
|
||||||
matches := re.FindAllStringSubmatch(ui.OutputWriter.String(), -1)
|
|
||||||
if len(matches) != 1 {
|
|
||||||
t.Fatalf("Unexpected number of tokens found, got %d", len(matches))
|
|
||||||
}
|
|
||||||
|
|
||||||
encRootToken := matches[0][1]
|
client, closer := testVaultServerUninit(t)
|
||||||
privKeyBytes, err := base64.StdEncoding.DecodeString(pgpkeys.TestPrivKey1)
|
defer closer()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error decoding private key: %v", err)
|
|
||||||
}
|
|
||||||
ptBuf := bytes.NewBuffer(nil)
|
|
||||||
entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error parsing private key: %s", err)
|
|
||||||
}
|
|
||||||
var rootBytes []byte
|
|
||||||
rootBytes, err = base64.StdEncoding.DecodeString(encRootToken)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error decoding root token: %s", err)
|
|
||||||
}
|
|
||||||
entityList := &openpgp.EntityList{entity}
|
|
||||||
md, err := openpgp.ReadMessage(bytes.NewBuffer(rootBytes), entityList, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error decrypting root token: %s", err)
|
|
||||||
}
|
|
||||||
ptBuf.ReadFrom(md.UnverifiedBody)
|
|
||||||
rootToken := ptBuf.String()
|
|
||||||
|
|
||||||
parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, false, nil, nil, core)
|
ui, cmd := testInitCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
client, err := c.Client()
|
code := cmd.Run([]string{})
|
||||||
if err != nil {
|
if exp := 0; code != exp {
|
||||||
t.Fatalf("Error fetching client: %v", err)
|
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
client.SetToken(rootToken)
|
init, err := client.Sys().InitStatus()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !init {
|
||||||
|
t.Error("expected initialized")
|
||||||
|
}
|
||||||
|
|
||||||
tokenInfo, err := client.Auth().Token().LookupSelf()
|
re := regexp.MustCompile(`Unseal Key \d+: (.+)`)
|
||||||
if err != nil {
|
output := ui.OutputWriter.String()
|
||||||
t.Fatalf("Error looking up root token info: %v", err)
|
match := re.FindAllStringSubmatch(output, -1)
|
||||||
}
|
if len(match) < 5 || len(match[0]) < 2 {
|
||||||
|
t.Fatalf("no match: %#v", match)
|
||||||
|
}
|
||||||
|
|
||||||
if tokenInfo.Data["policies"].([]interface{})[0].(string) != "root" {
|
keys := make([]string, len(match))
|
||||||
t.Fatalf("expected root policy")
|
for i := range match {
|
||||||
}
|
keys[i] = match[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try unsealing with those keys - only use 3, which is the default
|
||||||
|
// threshold.
|
||||||
|
for i, key := range keys[:3] {
|
||||||
|
resp, err := client.Sys().Unseal(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := (i + 1) % 3 // 1, 2, 0
|
||||||
|
if resp.Progress != exp {
|
||||||
|
t.Errorf("expected %d to be %d", resp.Progress, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := client.Sys().SealStatus()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if status.Sealed {
|
||||||
|
t.Errorf("expected vault to be unsealed: %#v", status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom_shares_threshold", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
keyShares, keyThreshold := 20, 15
|
||||||
|
|
||||||
|
client, closer := testVaultServerUninit(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ui, cmd := testInitCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"-key-shares", strconv.Itoa(keyShares),
|
||||||
|
"-key-threshold", strconv.Itoa(keyThreshold),
|
||||||
|
})
|
||||||
|
if exp := 0; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
init, err := client.Sys().InitStatus()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !init {
|
||||||
|
t.Error("expected initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`Unseal Key \d+: (.+)`)
|
||||||
|
output := ui.OutputWriter.String()
|
||||||
|
match := re.FindAllStringSubmatch(output, -1)
|
||||||
|
if len(match) < keyShares || len(match[0]) < 2 {
|
||||||
|
t.Fatalf("no match: %#v", match)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, len(match))
|
||||||
|
for i := range match {
|
||||||
|
keys[i] = match[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try unsealing with those keys - only use 3, which is the default
|
||||||
|
// threshold.
|
||||||
|
for i, key := range keys[:keyThreshold] {
|
||||||
|
resp, err := client.Sys().Unseal(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := (i + 1) % keyThreshold
|
||||||
|
if resp.Progress != exp {
|
||||||
|
t.Errorf("expected %d to be %d", resp.Progress, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := client.Sys().SealStatus()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if status.Sealed {
|
||||||
|
t.Errorf("expected vault to be unsealed: %#v", status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pgp", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tempDir, pubFiles, err := getPubKeyFiles(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
client, closer := testVaultServerUninit(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ui, cmd := testInitCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"-key-shares", "4",
|
||||||
|
"-key-threshold", "2",
|
||||||
|
"-pgp-keys", fmt.Sprintf("%s,@%s, %s, %s ",
|
||||||
|
pubFiles[0], pubFiles[1], pubFiles[2], pubFiles[3]),
|
||||||
|
"-root-token-pgp-key", pubFiles[0],
|
||||||
|
})
|
||||||
|
if exp := 0; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`Unseal Key \d+: (.+)`)
|
||||||
|
output := ui.OutputWriter.String()
|
||||||
|
match := re.FindAllStringSubmatch(output, -1)
|
||||||
|
if len(match) < 4 || len(match[0]) < 2 {
|
||||||
|
t.Fatalf("no match: %#v", match)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, len(match))
|
||||||
|
for i := range match {
|
||||||
|
keys[i] = match[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try unsealing with one key
|
||||||
|
decryptedKey := testPGPDecrypt(t, pgpkeys.TestPrivKey1, keys[0])
|
||||||
|
if _, err := client.Sys().Unseal(decryptedKey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the root token
|
||||||
|
reToken := regexp.MustCompile(`Root Token: (.+)`)
|
||||||
|
match = reToken.FindAllStringSubmatch(output, -1)
|
||||||
|
if len(match) < 1 || len(match[0]) < 2 {
|
||||||
|
t.Fatalf("no match")
|
||||||
|
}
|
||||||
|
root := match[0][1]
|
||||||
|
decryptedRoot := testPGPDecrypt(t, pgpkeys.TestPrivKey1, root)
|
||||||
|
|
||||||
|
if l, exp := len(decryptedRoot), 36; l != exp {
|
||||||
|
t.Errorf("expected %d to be %d", l, exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("communication_failure", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, closer := testVaultServerBad(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ui, cmd := testInitCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"secret/foo",
|
||||||
|
})
|
||||||
|
if exp := 2; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d", code, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Error initializing: "
|
||||||
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
|
if !strings.Contains(combined, expected) {
|
||||||
|
t.Errorf("expected %q to contain %q", combined, expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no_tabs", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, cmd := testInitCommand(t)
|
||||||
|
assertNoTabs(t, cmd)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue