Add config CLI command and encrypt/decrypt sub-commands (#77)
This commit is contained in:
1 parent
8de5371499
commit
8afcbd4c8e
12 files changed
+509
-49
No files matched your search
@@ -8,9 +8,11 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/watchtower/api"
|
||||
"github.com/mitchellh/cli"
|
||||
@@ -56,6 +58,38 @@ type Command struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
// New returns a new instance of a base.Command type
|
||||
func New(ui cli.Ui) *Command {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ret := &Command{
|
||||
UI: ui,
|
||||
ShutdownCh: MakeShutdownCh(),
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ret.ShutdownCh
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// MakeShutdownCh returns a channel that can be used for shutdown
|
||||
// notifications for commands. This channel will send a message for every
|
||||
// SIGINT or SIGTERM received.
|
||||
func MakeShutdownCh() chan struct{} {
|
||||
resultCh := make(chan struct{})
|
||||
|
||||
shutdownCh := make(chan os.Signal, 4)
|
||||
signal.Notify(shutdownCh, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-shutdownCh
|
||||
close(resultCh)
|
||||
}()
|
||||
return resultCh
|
||||
}
|
||||
|
||||
// Client returns the HTTP API client. The client is cached on the command to
|
||||
// save performance on future calls.
|
||||
func (c *Command) Client() (*api.Client, error) {
|
||||
|
||||
@@ -314,7 +314,7 @@ func (b *Server) SetupKMSes(ui cli.Ui, config *configutil.SharedConfig, purposes
|
||||
switch purpose {
|
||||
case "":
|
||||
return errors.New("KMS block missing 'purpose'")
|
||||
case "controller", "worker-auth":
|
||||
case "controller", "worker-auth", "config":
|
||||
default:
|
||||
return fmt.Errorf("Unknown KMS purpose %q", kms.Purpose)
|
||||
}
|
||||
|
||||
+24
-38
@@ -1,17 +1,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/config"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/controller"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/dev"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/hosts"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/scopes"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/commands/worker"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
@@ -19,28 +20,12 @@ import (
|
||||
var Commands map[string]cli.CommandFactory
|
||||
|
||||
func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
getBaseCommand := func() *base.Command {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ret := &base.Command{
|
||||
UI: ui,
|
||||
ShutdownCh: MakeShutdownCh(),
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ret.ShutdownCh
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
"controller": func() (cli.Command, error) {
|
||||
return &controller.Command{
|
||||
Server: base.NewServer(&base.Command{
|
||||
UI: serverCmdUi,
|
||||
ShutdownCh: MakeShutdownCh(),
|
||||
ShutdownCh: base.MakeShutdownCh(),
|
||||
}),
|
||||
SighupCh: MakeSighupCh(),
|
||||
SigUSR2Ch: MakeSigUSR2Ch(),
|
||||
@@ -50,7 +35,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
return &worker.Command{
|
||||
Server: base.NewServer(&base.Command{
|
||||
UI: serverCmdUi,
|
||||
ShutdownCh: MakeShutdownCh(),
|
||||
ShutdownCh: base.MakeShutdownCh(),
|
||||
}),
|
||||
SighupCh: MakeSighupCh(),
|
||||
SigUSR2Ch: MakeSigUSR2Ch(),
|
||||
@@ -60,7 +45,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
return &dev.Command{
|
||||
Server: base.NewServer(&base.Command{
|
||||
UI: serverCmdUi,
|
||||
ShutdownCh: MakeShutdownCh(),
|
||||
ShutdownCh: base.MakeShutdownCh(),
|
||||
}),
|
||||
SighupCh: MakeSighupCh(),
|
||||
SigUSR2Ch: MakeSigUSR2Ch(),
|
||||
@@ -68,37 +53,38 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
},
|
||||
"hosts create": func() (cli.Command, error) {
|
||||
return &hosts.CreateCommand{
|
||||
Command: getBaseCommand(),
|
||||
Command: base.New(ui),
|
||||
}, nil
|
||||
},
|
||||
"projects create": func() (cli.Command, error) {
|
||||
return &scopes.CreateProjectCommand{
|
||||
Command: getBaseCommand(),
|
||||
Command: base.New(ui),
|
||||
}, nil
|
||||
},
|
||||
"projects read": func() (cli.Command, error) {
|
||||
return &scopes.ReadProjectCommand{
|
||||
Command: getBaseCommand(),
|
||||
Command: base.New(ui),
|
||||
}, nil
|
||||
},
|
||||
"config": func() (cli.Command, error) {
|
||||
return &config.Command{
|
||||
Command: base.New(ui),
|
||||
}, nil
|
||||
},
|
||||
"config encrypt": func() (cli.Command, error) {
|
||||
return &config.EncryptDecryptCommand{
|
||||
Command: base.New(ui),
|
||||
Encrypt: true,
|
||||
}, nil
|
||||
},
|
||||
"config decrypt": func() (cli.Command, error) {
|
||||
return &config.EncryptDecryptCommand{
|
||||
Command: base.New(ui),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MakeShutdownCh returns a channel that can be used for shutdown
|
||||
// notifications for commands. This channel will send a message for every
|
||||
// SIGINT or SIGTERM received.
|
||||
func MakeShutdownCh() chan struct{} {
|
||||
resultCh := make(chan struct{})
|
||||
|
||||
shutdownCh := make(chan os.Signal, 4)
|
||||
signal.Notify(shutdownCh, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-shutdownCh
|
||||
close(resultCh)
|
||||
}()
|
||||
return resultCh
|
||||
}
|
||||
|
||||
// MakeSighupCh returns a channel that can be used for SIGHUP
|
||||
// reloading. This channel will send a message for every
|
||||
// SIGHUP received.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*Command)(nil)
|
||||
|
||||
type Command struct {
|
||||
*base.Command
|
||||
}
|
||||
|
||||
func (c *Command) Synopsis() string {
|
||||
return "Manage sensitive values in Watchtower's configuration files"
|
||||
}
|
||||
|
||||
func (c *Command) Help() string {
|
||||
helpText := `
|
||||
Usage: watchtower config <subcommand> [options] [args]
|
||||
|
||||
This command groups subcommands for operators interacting with Watchtower's
|
||||
config files. Here are a few examples of config commands:
|
||||
|
||||
Encrypt sensitive values in a config file:
|
||||
|
||||
$ watchtower config encrypt config.hcl
|
||||
|
||||
Decrypt sensitive values in a config file:
|
||||
|
||||
$ watchtower config decrypt config.hcl
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *Command) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*EncryptDecryptCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*EncryptDecryptCommand)(nil)
|
||||
|
||||
type EncryptDecryptCommand struct {
|
||||
*base.Command
|
||||
Encrypt bool
|
||||
|
||||
flagOverwrite bool
|
||||
flagStrip bool
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) Synopsis() string {
|
||||
dir := "Decrypts"
|
||||
if c.Encrypt {
|
||||
dir = "Encrypts"
|
||||
}
|
||||
return fmt.Sprintf("%s sensitive values in Vault's configuration file", dir)
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) Help() string {
|
||||
subCmd := "Decrypt"
|
||||
if c.Encrypt {
|
||||
subCmd = "Encrypt"
|
||||
}
|
||||
helpText := `
|
||||
Usage: watchtower config %s [options] [args]
|
||||
|
||||
%s sensitive values in a Watchtower's configuration file. These values must be marked
|
||||
with {{%s()}} as appropriate. This can only be used with string parameters, and
|
||||
the markers must be inside the quote marks delimiting the string; as an example:
|
||||
|
||||
foo = "{{encrypt(bar)}}"
|
||||
|
||||
By default this will print the new configuration out. To overwrite into the same
|
||||
file use the -overwrite flag.
|
||||
|
||||
$ watchtower config %s -overwrite config.hcl
|
||||
`
|
||||
helpText = fmt.Sprintf(helpText, strings.ToLower(subCmd), subCmd, strings.ToLower(subCmd), strings.ToLower(subCmd))
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) Flags() *base.FlagSets {
|
||||
set := c.FlagSet(0)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.BoolVar(&base.BoolVar{
|
||||
Name: "overwrite",
|
||||
Target: &c.flagOverwrite,
|
||||
Usage: "Overwrite the existing file.",
|
||||
})
|
||||
|
||||
f.BoolVar(&base.BoolVar{
|
||||
Name: "strip",
|
||||
Target: &c.flagStrip,
|
||||
Usage: "Strip the declarations from the file afterwards.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *EncryptDecryptCommand) Run(args []string) (ret int) {
|
||||
op := "decrypt"
|
||||
if c.Encrypt {
|
||||
op = "encrypt"
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
path := ""
|
||||
args = f.Args()
|
||||
switch len(args) {
|
||||
case 1:
|
||||
path = strings.TrimSpace(args[0])
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Incorrect arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
c.UI.Error("A configuration file must be specified")
|
||||
return 1
|
||||
}
|
||||
|
||||
kmses, err := configutil.LoadConfigKMSes(path)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error loading configuration from %s: %w", path, err).Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
var kms *configutil.KMS
|
||||
for _, v := range kmses {
|
||||
if strutil.StrListContains(v.Purpose, "config") {
|
||||
if kms != nil {
|
||||
c.UI.Error("Only one kms block marked for \"config\" purpose is allowed")
|
||||
return 1
|
||||
}
|
||||
kms = v
|
||||
}
|
||||
}
|
||||
if kms == nil {
|
||||
c.UI.Error("No kms block with \"config\" purpose defined in the configuration file")
|
||||
return 1
|
||||
}
|
||||
|
||||
d, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error reading config file: %w", err).Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
raw := string(d)
|
||||
|
||||
wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error creating kms: %w", err).Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
wrapper.Init(c.Context)
|
||||
defer wrapper.Finalize(c.Context)
|
||||
|
||||
raw, err = configutil.EncryptDecrypt(raw, !c.Encrypt, c.flagStrip, wrapper)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error %sing via kms: %w", op, err).Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
if !c.flagOverwrite {
|
||||
c.UI.Output(raw)
|
||||
return 0
|
||||
}
|
||||
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error opening file for writing: %w", err).Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error closing file after writing: %w", err).Error())
|
||||
ret = 1
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := file.WriteString(raw)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error writing to file: %w", err).Error())
|
||||
return 1
|
||||
}
|
||||
if n != len(raw) {
|
||||
c.UI.Error(fmt.Sprintf("Wrong number of bytes written to file, expected %d, wrote %d", len(raw), n))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
configEncryptPath = "./fixtures/configEncrypt.hcl"
|
||||
configDecryptPath = "./fixtures/configDecrypt.hcl"
|
||||
)
|
||||
|
||||
func TestEncryptDecrypt(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
encrypt bool
|
||||
config string
|
||||
exp string
|
||||
}{
|
||||
{
|
||||
encrypt: true,
|
||||
config: configEncryptPath,
|
||||
exp: "",
|
||||
},
|
||||
{
|
||||
encrypt: false,
|
||||
config: configDecryptPath,
|
||||
exp: configEncryptPath,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("Test encrypt %v", c.encrypt), func(t *testing.T) {
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
ui := &cli.BasicUi{
|
||||
Reader: bufio.NewReader(os.Stdin),
|
||||
Writer: &b,
|
||||
ErrorWriter: &b,
|
||||
}
|
||||
|
||||
cmd := &EncryptDecryptCommand{
|
||||
Command: base.New(ui),
|
||||
Encrypt: c.encrypt}
|
||||
|
||||
if err := cmd.Run([]string{c.config}); err != 0 {
|
||||
assert.Equal(t, err, 0)
|
||||
}
|
||||
|
||||
got := b.String()
|
||||
assert.Greater(t, len(got), 0)
|
||||
fmt.Printf("%s\n", got)
|
||||
|
||||
// If it's encrypting, then we can assume that it did the right thing since
|
||||
// there are many tests on the underlying codebase for that. If it's not
|
||||
// encrypting, compare it to the cleartext to verify because we can.
|
||||
if !c.encrypt {
|
||||
expected, err := ioutil.ReadFile(c.exp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, strings.TrimSpace(string(expected)), strings.TrimSpace(string(got)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
kms "aead" {
|
||||
purpose = "config"
|
||||
aead_type = "aes-gcm"
|
||||
key = "c964AJj8VW8w4hKz/Jd8MvuLt0kkcjVuFqMiMvTvvN8="
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "controller"
|
||||
aead_type = "aes-gcm"
|
||||
key ="{{decrypt(CkgsDUoQ7B5JhE1ye2yVAm3Ss_KNbLQgBhKtibtGSvpGOrwQ3zRTF_qoHPHX-xuqLyZ9zsb3cMo6OpphKOj7AeTn1_iiqZ_VaCsqAA)}}"
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "worker"
|
||||
aead_type = "aes-gcm"
|
||||
key ="{{decrypt(CkiRTINwX19TnC3AB-zx5E133TXI9KzBWb8TxfVDrPb9m3Yfm9K99OkuJgRTj1rjmeMF-Kpl-0oouEc8_mNk6oPIqD8nUNvH3FYqAA)}}"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
kms "aead" {
|
||||
purpose = "config"
|
||||
aead_type = "aes-gcm"
|
||||
key = "c964AJj8VW8w4hKz/Jd8MvuLt0kkcjVuFqMiMvTvvN8="
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "controller"
|
||||
aead_type = "aes-gcm"
|
||||
key ="{{encrypt(eb78KqCwowELYnkOOko/XYz01q1ax3g76J1vCAvt5dQ=)}}"
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "worker"
|
||||
aead_type = "aes-gcm"
|
||||
key ="{{encrypt(aA1hxJo0JUAqcIATx/r0QTjAGD/btCPechEsukI2bt0=)}}"
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/mlock"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/watchtower/globals"
|
||||
@@ -36,6 +37,8 @@ type Command struct {
|
||||
Config *config.Config
|
||||
controller *controller.Controller
|
||||
|
||||
configKMS *configutil.KMS
|
||||
|
||||
flagConfig string
|
||||
flagLogLevel string
|
||||
flagLogFormat string
|
||||
@@ -288,6 +291,22 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// preload the KMS for encrypting/decrypting the config parameters
|
||||
kmss, err := configutil.LoadConfigKMSes(c.flagConfig)
|
||||
if err != nil {
|
||||
c.UI.Error("error loading KMS config: " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, kms := range kmss {
|
||||
for _, purpose := range kms.Purpose {
|
||||
if purpose == "config" {
|
||||
c.configKMS = kms
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
if !c.flagDev {
|
||||
switch {
|
||||
@@ -301,7 +320,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
|
||||
c.flagDevAdminPassword = ""
|
||||
}
|
||||
|
||||
c.Config, err = config.LoadFile(c.flagConfig)
|
||||
c.Config, err = config.LoadFile(c.flagConfig, c.configKMS)
|
||||
if err != nil {
|
||||
c.UI.Error("Error parsing config: " + err.Error())
|
||||
return 1
|
||||
@@ -410,7 +429,7 @@ func (c *Command) WaitForInterrupt() int {
|
||||
goto RUNRELOADFUNCS
|
||||
}
|
||||
|
||||
newConf, err = config.LoadFile(c.flagConfig)
|
||||
newConf, err = config.LoadFile(c.flagConfig, c.configKMS)
|
||||
if err != nil {
|
||||
c.Logger.Error("could not reload config", "path", c.flagConfig, "error", err)
|
||||
goto RUNRELOADFUNCS
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/mlock"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/config"
|
||||
@@ -32,6 +33,8 @@ type Command struct {
|
||||
Config *config.Config
|
||||
worker *worker.Worker
|
||||
|
||||
configKMS *configutil.KMS
|
||||
|
||||
flagConfig string
|
||||
flagLogLevel string
|
||||
flagLogFormat string
|
||||
@@ -203,13 +206,29 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// preload the KMS for encrypting/decrypting the config parameters
|
||||
kmss, err := configutil.LoadConfigKMSes(c.flagConfig)
|
||||
if err != nil {
|
||||
c.UI.Error("error loading KMS config: " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, kms := range kmss {
|
||||
for _, purpose := range kms.Purpose {
|
||||
if purpose == "config" {
|
||||
c.configKMS = kms
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
if !c.flagDev {
|
||||
if len(c.flagConfig) == 0 {
|
||||
c.UI.Error("Must supply a config file with -config")
|
||||
return 1
|
||||
}
|
||||
c.Config, err = config.LoadFile(c.flagConfig)
|
||||
c.Config, err = config.LoadFile(c.flagConfig, c.configKMS)
|
||||
if err != nil {
|
||||
c.UI.Error("Error parsing config: " + err.Error())
|
||||
return 1
|
||||
@@ -288,7 +307,7 @@ func (c *Command) WaitForInterrupt() int {
|
||||
goto RUNRELOADFUNCS
|
||||
}
|
||||
|
||||
newConf, err = config.LoadFile(c.flagConfig)
|
||||
newConf, err = config.LoadFile(c.flagConfig, c.configKMS)
|
||||
if err != nil {
|
||||
c.Logger.Error("could not reload config", "path", c.flagConfig, "error", err)
|
||||
goto RUNRELOADFUNCS
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
@@ -113,19 +114,22 @@ func New() *Config {
|
||||
}
|
||||
|
||||
// LoadFile loads the configuration from the given file.
|
||||
func LoadFile(path string) (*Config, error) {
|
||||
// Read the file
|
||||
func LoadFile(path string, kms *configutil.KMS) (*Config, error) {
|
||||
d, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err := Parse(string(d))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
raw := string(d)
|
||||
|
||||
if kms != nil {
|
||||
raw, err = configDecrypt(raw, kms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
return Parse(raw)
|
||||
}
|
||||
|
||||
func Parse(d string) (*Config, error) {
|
||||
@@ -170,3 +174,15 @@ func (c *Config) Sanitized() map[string]interface{} {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func configDecrypt(raw string, kms *configutil.KMS) (string, error) {
|
||||
wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil)
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
|
||||
wrapper.Init(context.Background())
|
||||
defer wrapper.Finalize(context.Background())
|
||||
|
||||
return configutil.EncryptDecrypt(raw, true, true, wrapper)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -76,3 +77,51 @@ func TestDevController(t *testing.T) {
|
||||
|
||||
assert.Equal(t, exp, actual)
|
||||
}
|
||||
|
||||
func TestConfigDecrypt(t *testing.T) {
|
||||
|
||||
const (
|
||||
clr = `
|
||||
kms "aead" {
|
||||
purpose = "config"
|
||||
aead_type = "aes-gcm"
|
||||
key = "c964AJj8VW8w4hKz/Jd8MvuLt0kkcjVuFqMiMvTvvN8="
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "controller"
|
||||
aead_type = "aes-gcm"
|
||||
key ="eb78KqCwowELYnkOOko/XYz01q1ax3g76J1vCAvt5dQ="
|
||||
}`
|
||||
|
||||
enc = `
|
||||
kms "aead" {
|
||||
purpose = "config"
|
||||
aead_type = "aes-gcm"
|
||||
key = "c964AJj8VW8w4hKz/Jd8MvuLt0kkcjVuFqMiMvTvvN8="
|
||||
}
|
||||
|
||||
kms "aead" {
|
||||
purpose = "controller"
|
||||
aead_type = "aes-gcm"
|
||||
key ="{{decrypt(Ckh57d4NA6nsnRKV6DiHTyfwLIakdhN8w7qdPJgo-KWnBdlEKv3NQkUFbouU0eorSGik1Qbca5xEy2NqYT9UYj_GUGo6hHz13MEqAA)}}"
|
||||
}`
|
||||
)
|
||||
|
||||
kmses, err := configutil.ParseKMSes(enc)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var kms = &configutil.KMS{}
|
||||
for _, k := range kmses {
|
||||
for _, p := range k.Purpose {
|
||||
if p == "config" {
|
||||
kms = k
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
got, err := configDecrypt(enc, kms)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, strings.TrimSpace(got), strings.TrimSpace(clr))
|
||||
}
|
||||
Reference in new issue
Block a user