1a324cf347
Functionality is split into ExternalTokenHelper, which is used if a path is given in a configuration file, and InternalTokenHelper which is used otherwise. The internal helper no longer shells out to the same Vault binary, instead performing the same actions with internal code. This avoids problems using dev mode when there are spaces in paths or when the binary is built in a container without a shell. Fixes #850 among others
314 lines
8.2 KiB
Go
314 lines
8.2 KiB
Go
package command
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/command/token"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
// FlagSetFlags is an enum to define what flags are present in the
|
|
// default FlagSet returned by Meta.FlagSet.
|
|
type FlagSetFlags uint
|
|
|
|
const (
|
|
FlagSetNone FlagSetFlags = 0
|
|
FlagSetServer FlagSetFlags = 1 << iota
|
|
FlagSetDefault = FlagSetServer
|
|
)
|
|
|
|
// Meta contains the meta-options and functionality that nearly every
|
|
// Vault command inherits.
|
|
type Meta struct {
|
|
ClientToken string
|
|
Ui cli.Ui
|
|
|
|
// The things below can be set, but aren't common
|
|
ForceAddress string // Address to force for API clients
|
|
ForceConfig *Config // Force a config, don't load from disk
|
|
|
|
// These are set by the command line flags.
|
|
flagAddress string
|
|
flagCACert string
|
|
flagCAPath string
|
|
flagClientCert string
|
|
flagClientKey string
|
|
flagInsecure bool
|
|
|
|
// These are internal and shouldn't be modified or access by anyone
|
|
// except Meta.
|
|
config *Config
|
|
}
|
|
|
|
// Client returns the API client to a Vault server given the configured
|
|
// flag settings for this command.
|
|
func (m *Meta) Client() (*api.Client, error) {
|
|
config := api.DefaultConfig()
|
|
|
|
err := config.ReadEnvironment()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("error reading environment: {{err}}", err)
|
|
}
|
|
|
|
if m.flagAddress != "" {
|
|
config.Address = m.flagAddress
|
|
}
|
|
if m.ForceAddress != "" {
|
|
config.Address = m.ForceAddress
|
|
}
|
|
// If we need custom TLS configuration, then set it
|
|
if m.flagCACert != "" || m.flagCAPath != "" || m.flagClientCert != "" || m.flagClientKey != "" || m.flagInsecure {
|
|
// We may have set items from the environment so start with the
|
|
// existing TLS config
|
|
tlsConfig := config.HttpClient.Transport.(*http.Transport).TLSClientConfig
|
|
|
|
var certPool *x509.CertPool
|
|
var err error
|
|
if m.flagCACert != "" {
|
|
certPool, err = api.LoadCACert(m.flagCACert)
|
|
} else if m.flagCAPath != "" {
|
|
certPool, err = api.LoadCAPath(m.flagCAPath)
|
|
}
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("Error setting up CA path: {{err}}", err)
|
|
}
|
|
|
|
if certPool != nil {
|
|
tlsConfig.RootCAs = certPool
|
|
}
|
|
if m.flagInsecure {
|
|
tlsConfig.InsecureSkipVerify = true
|
|
}
|
|
|
|
if m.flagClientCert != "" && m.flagClientKey != "" {
|
|
tlsCert, err := tls.LoadX509KeyPair(m.flagClientCert, m.flagClientKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
|
} else if m.flagClientCert != "" || m.flagClientKey != "" {
|
|
return nil, fmt.Errorf("Both client cert and client key must be provided")
|
|
}
|
|
}
|
|
|
|
// Build the client
|
|
client, err := api.NewClient(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If we have a token directly, then set that
|
|
token := m.ClientToken
|
|
|
|
// Try to set the token to what is already stored
|
|
if token == "" {
|
|
token = client.Token()
|
|
}
|
|
|
|
// If we don't have a token, check the token helper
|
|
if token == "" {
|
|
// If we have a token, then set that
|
|
tokenHelper, err := m.TokenHelper()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
token, err = tokenHelper.Get()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Set the token
|
|
if token != "" {
|
|
client.SetToken(token)
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
// Config loads the configuration and returns it. If the configuration
|
|
// is already loaded, it is returned.
|
|
func (m *Meta) Config() (*Config, error) {
|
|
if m.config != nil {
|
|
return m.config, nil
|
|
}
|
|
if m.ForceConfig != nil {
|
|
return m.ForceConfig, nil
|
|
}
|
|
|
|
var err error
|
|
m.config, err = LoadConfig("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return m.config, nil
|
|
}
|
|
|
|
// FlagSet returns a FlagSet with the common flags that every
|
|
// command implements. The exact behavior of FlagSet can be configured
|
|
// using the flags as the second parameter, for example to disable
|
|
// server settings on the commands that don't talk to a server.
|
|
func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
|
|
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
|
|
|
// FlagSetServer tells us to enable the settings for selecting
|
|
// the server information.
|
|
if fs&FlagSetServer != 0 {
|
|
f.StringVar(&m.flagAddress, "address", "", "")
|
|
f.StringVar(&m.flagCACert, "ca-cert", "", "")
|
|
f.StringVar(&m.flagCAPath, "ca-path", "", "")
|
|
f.StringVar(&m.flagClientCert, "client-cert", "", "")
|
|
f.StringVar(&m.flagClientKey, "client-key", "", "")
|
|
f.BoolVar(&m.flagInsecure, "insecure", false, "")
|
|
f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "")
|
|
}
|
|
|
|
// Create an io.Writer that writes to our Ui properly for errors.
|
|
// This is kind of a hack, but it does the job. Basically: create
|
|
// a pipe, use a scanner to break it into lines, and output each line
|
|
// to the UI. Do this forever.
|
|
errR, errW := io.Pipe()
|
|
errScanner := bufio.NewScanner(errR)
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
m.Ui.Error(errScanner.Text())
|
|
}
|
|
}()
|
|
f.SetOutput(errW)
|
|
|
|
return f
|
|
}
|
|
|
|
// TokenHelper returns the token helper that is configured for Vault.
|
|
func (m *Meta) TokenHelper() (token.TokenHelper, error) {
|
|
config, err := m.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
path := config.TokenHelper
|
|
if path == "" {
|
|
return &token.InternalTokenHelper{}, nil
|
|
}
|
|
|
|
path, err = token.ExternalTokenHelperPath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &token.ExternalTokenHelper{BinaryPath: path}, nil
|
|
}
|
|
|
|
func (m *Meta) loadCACert(path string) (*x509.CertPool, error) {
|
|
certs, err := m.loadCertFromPEM(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error loading %s: %s", path, err)
|
|
}
|
|
|
|
result := x509.NewCertPool()
|
|
for _, cert := range certs {
|
|
result.AddCert(cert)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Meta) loadCAPath(path string) (*x509.CertPool, error) {
|
|
result := x509.NewCertPool()
|
|
fn := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
certs, err := m.loadCertFromPEM(path)
|
|
if err != nil {
|
|
return fmt.Errorf("Error loading %s: %s", path, err)
|
|
}
|
|
|
|
for _, cert := range certs {
|
|
result.AddCert(cert)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return result, filepath.Walk(path, fn)
|
|
}
|
|
|
|
func (m *Meta) loadCertFromPEM(path string) ([]*x509.Certificate, error) {
|
|
pemCerts, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certs := make([]*x509.Certificate, 0, 5)
|
|
for len(pemCerts) > 0 {
|
|
var block *pem.Block
|
|
block, pemCerts = pem.Decode(pemCerts)
|
|
if block == nil {
|
|
break
|
|
}
|
|
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
|
|
continue
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
}
|
|
|
|
return certs, nil
|
|
}
|
|
|
|
// generalOptionsUsage returns the usage documenation for commonly
|
|
// available options
|
|
func generalOptionsUsage() string {
|
|
general := `
|
|
-address=addr The address of the Vault server.
|
|
Overrides the VAULT_ADDR environment variable if set.
|
|
|
|
-ca-cert=path Path to a PEM encoded CA cert file to use to
|
|
verify the Vault server SSL certificate.
|
|
Overrides the VAULT_CACERT environment variable if set.
|
|
|
|
-ca-path=path Path to a directory of PEM encoded CA cert files
|
|
to verify the Vault server SSL certificate. If both
|
|
-ca-cert and -ca-path are specified, -ca-path is used.
|
|
Overrides the VAULT_CAPATH environment variable if set.
|
|
|
|
-client-cert=path Path to a PEM encoded client certificate for TLS
|
|
authentication to the Vault server. Must also specify
|
|
-client-key. Overrides the VAULT_CLIENT_CERT
|
|
environment variable if set.
|
|
|
|
-client-key=path Path to an unencrypted PEM encoded private key
|
|
matching the client certificate from -client-cert.
|
|
Overrides the VAULT_CLIENT_KEY environment variable
|
|
if set.
|
|
|
|
-tls-skip-verify Do not verify TLS certificate. This is highly
|
|
not recommended. Verification will also be skipped
|
|
if VAULT_SKIP_VERIFY is set.
|
|
`
|
|
return strings.TrimSpace(general)
|
|
}
|