Create a new command/config subpackage. (#4055)
* Create a new command/config subpackage. This PR extracts the functions associated with loading and parsing configs, and the DefaultTokenHelper, into a command/config subpackage, just like TokenHelpers are in the command/token subpackage. The goal is to allow other clients (in this case, the Vault and Nomad Terraform providers, but in theory any client that wants to lean on Vault's default behaviour) to reuse this logic and not drift from Vault, without vendoring the entirety of Vault. To retain backwards compatibility, I didn't remove any functions from the command package; I just copied them into the command/config package, and update the functions in the command package to call through to the config package.
This commit is contained in:
parent
942aa9bbdc
commit
2b2bc2a911
|
@ -2,13 +2,10 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/hashicorp/vault/command/config"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -32,66 +29,32 @@ type DefaultConfig struct {
|
|||
|
||||
// Config loads the configuration and returns it. If the configuration
|
||||
// is already loaded, it is returned.
|
||||
//
|
||||
// Config just calls into config.Config for backwards compatibility purposes.
|
||||
// Use config.Config instead.
|
||||
func Config() (*DefaultConfig, error) {
|
||||
var err error
|
||||
config, err := LoadConfig("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
conf, err := config.Config()
|
||||
return (*DefaultConfig)(conf), err
|
||||
}
|
||||
|
||||
// LoadConfig reads the configuration from the given path. If path is
|
||||
// empty, then the default path will be used, or the environment variable
|
||||
// if set.
|
||||
//
|
||||
// LoadConfig just calls into config.LoadConfig for backwards compatibility
|
||||
// purposes. Use config.LoadConfig instead.
|
||||
func LoadConfig(path string) (*DefaultConfig, error) {
|
||||
if path == "" {
|
||||
path = DefaultConfigPath
|
||||
}
|
||||
if v := os.Getenv(ConfigPathEnv); v != "" {
|
||||
path = v
|
||||
}
|
||||
|
||||
// NOTE: requires HOME env var to be set
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error expanding config path %s: %s", path, err)
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseConfig(string(contents))
|
||||
conf, err := config.LoadConfig(path)
|
||||
return (*DefaultConfig)(conf), err
|
||||
}
|
||||
|
||||
// ParseConfig parses the given configuration as a string.
|
||||
//
|
||||
// ParseConfig just calls into config.ParseConfig for backwards compatibility
|
||||
// purposes. Use config.ParseConfig instead.
|
||||
func ParseConfig(contents string) (*DefaultConfig, error) {
|
||||
root, err := hcl.Parse(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Top-level item should be the object list
|
||||
list, ok := root.Node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to parse config: does not contain a root object")
|
||||
}
|
||||
|
||||
valid := []string{
|
||||
"token_helper",
|
||||
}
|
||||
if err := checkHCLKeys(list, valid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c DefaultConfig
|
||||
if err := hcl.DecodeObject(&c, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
conf, err := config.ParseConfig(contents)
|
||||
return (*DefaultConfig)(conf), err
|
||||
}
|
||||
|
||||
func checkHCLKeys(node ast.Node, valid []string) error {
|
||||
|
|
123
command/config/config.go
Normal file
123
command/config/config.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultConfigPath is the default path to the configuration file
|
||||
DefaultConfigPath = "~/.vault"
|
||||
|
||||
// ConfigPathEnv is the environment variable that can be used to
|
||||
// override where the Vault configuration is.
|
||||
ConfigPathEnv = "VAULT_CONFIG_PATH"
|
||||
)
|
||||
|
||||
// Config is the CLI configuration for Vault that can be specified via
|
||||
// a `$HOME/.vault` file which is HCL-formatted (therefore HCL or JSON).
|
||||
type DefaultConfig struct {
|
||||
// TokenHelper is the executable/command that is executed for storing
|
||||
// and retrieving the authentication token for the Vault CLI. If this
|
||||
// is not specified, then vault's internal token store will be used, which
|
||||
// stores the token on disk unencrypted.
|
||||
TokenHelper string `hcl:"token_helper"`
|
||||
}
|
||||
|
||||
// Config loads the configuration and returns it. If the configuration
|
||||
// is already loaded, it is returned.
|
||||
func Config() (*DefaultConfig, error) {
|
||||
var err error
|
||||
config, err := LoadConfig("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// LoadConfig reads the configuration from the given path. If path is
|
||||
// empty, then the default path will be used, or the environment variable
|
||||
// if set.
|
||||
func LoadConfig(path string) (*DefaultConfig, error) {
|
||||
if path == "" {
|
||||
path = DefaultConfigPath
|
||||
}
|
||||
if v := os.Getenv(ConfigPathEnv); v != "" {
|
||||
path = v
|
||||
}
|
||||
|
||||
// NOTE: requires HOME env var to be set
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error expanding config path %s: %s", path, err)
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseConfig(string(contents))
|
||||
}
|
||||
|
||||
// ParseConfig parses the given configuration as a string.
|
||||
func ParseConfig(contents string) (*DefaultConfig, error) {
|
||||
root, err := hcl.Parse(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Top-level item should be the object list
|
||||
list, ok := root.Node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to parse config: does not contain a root object")
|
||||
}
|
||||
|
||||
valid := []string{
|
||||
"token_helper",
|
||||
}
|
||||
if err := checkHCLKeys(list, valid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c DefaultConfig
|
||||
if err := hcl.DecodeObject(&c, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func checkHCLKeys(node ast.Node, valid []string) error {
|
||||
var list *ast.ObjectList
|
||||
switch n := node.(type) {
|
||||
case *ast.ObjectList:
|
||||
list = n
|
||||
case *ast.ObjectType:
|
||||
list = n.List
|
||||
default:
|
||||
return fmt.Errorf("cannot check HCL keys of type %T", n)
|
||||
}
|
||||
|
||||
validMap := make(map[string]struct{}, len(valid))
|
||||
for _, v := range valid {
|
||||
validMap[v] = struct{}{}
|
||||
}
|
||||
|
||||
var result error
|
||||
for _, item := range list.Items {
|
||||
key := item.Keys[0].Token.Value().(string)
|
||||
if _, ok := validMap[key]; !ok {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"invalid key '%s' on line %d", key, item.Assign.Line))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
49
command/config/config_test.go
Normal file
49
command/config/config_test.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const FixturePath = "../test-fixtures"
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
config, err := LoadConfig(filepath.Join(FixturePath, "config.hcl"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := &DefaultConfig{
|
||||
TokenHelper: "foo",
|
||||
}
|
||||
if !reflect.DeepEqual(expected, config) {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_noExist(t *testing.T) {
|
||||
config, err := LoadConfig("nope/not-once/.never")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if config.TokenHelper != "" {
|
||||
t.Errorf("expected %q to be %q", config.TokenHelper, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfig_badKeys(t *testing.T) {
|
||||
_, err := ParseConfig(`
|
||||
token_helper = "/token"
|
||||
nope = "true"
|
||||
`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "invalid key 'nope' on line 3") {
|
||||
t.Errorf("bad error: %s", err.Error())
|
||||
}
|
||||
}
|
24
command/config/util.go
Normal file
24
command/config/util.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/command/token"
|
||||
)
|
||||
|
||||
// DefaultTokenHelper returns the token helper that is configured for Vault.
|
||||
func DefaultTokenHelper() (token.TokenHelper, error) {
|
||||
config, err := LoadConfig("")
|
||||
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
|
||||
}
|
|
@ -9,27 +9,14 @@ import (
|
|||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/config"
|
||||
"github.com/hashicorp/vault/command/token"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// DefaultTokenHelper returns the token helper that is configured for Vault.
|
||||
func DefaultTokenHelper() (token.TokenHelper, error) {
|
||||
config, err := LoadConfig("")
|
||||
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
|
||||
return config.DefaultTokenHelper()
|
||||
}
|
||||
|
||||
// RawField extracts the raw field from the given data and returns it as a
|
||||
|
|
Loading…
Reference in a new issue