mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-21 06:31:07 +02:00
* Vault Agent Template: parse templates (#7540) * add template config parsing, but it's wrong b/c it's not using mapstructure * parsing consul templates in agent config * add additional test to configuration parsing, to cover basics * another test fixture, rework simple test into table * refactor into table test * rename test * remove flattenKeys and add other test fixture * Update command/agent/config/config.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * return the decode error instead of swallowing it * Update command/agent/config/config_test.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * go mod tidy * change error checking style * Add agent template doc * TemplateServer: render secrets with Consul Template (#7621) * add template config parsing, but it's wrong b/c it's not using mapstructure * parsing consul templates in agent config * add additional test to configuration parsing, to cover basics * another test fixture, rework simple test into table * refactor into table test * rename test * remove flattenKeys and add other test fixture * add template package * WIP: add runner * fix panic, actually copy templates, etc * rework how the config.Vault is created and enable reading from the environment * this was supposed to be a part of the prior commit * move/add methods to testhelpers for converting some values to pointers * use new methods in testhelpers * add an unblock channel to block agent until a template has been rendered * add note * unblock if there are no templates * cleanups * go mod tidy * remove dead code * simple test to starT * add simple, empty templates test * Update package doc, error logs, and add missing close() on channel * update code comment to be clear what I'm referring to * have template.NewServer return a (<- chan) type, even though it's a normal chan, as a better practice to enforce reading only * Update command/agent.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * update with test * Add README and doc.go to the command/agent directory (#7503) * Add README and doc.go to the command/agent directory * Add link to website * address feedback for agent.go * updated with feedback from Calvin * Rework template.Server to export the unblock channel, and remove it from the NewServer function * apply feedback from Nick * fix/restructure rendering test * Add pointerutil package for converting types to their pointers * Remove pointer helper methods; use sdk/helper/pointerutil instead * update newRunnerConfig to use pointerutil and empty strings * only wait for unblock if template server is initialized * drain the token channel in this test * conditionally send on channel
189 lines
5.3 KiB
Go
189 lines
5.3 KiB
Go
// Package template is responsible for rendering user supplied templates to
|
|
// disk. The Server type accepts configuration to communicate to a Vault server
|
|
// and a Vault token for authentication. Internally, the Server creates a Consul
|
|
// Template Runner which manages reading secrets from Vault and rendering
|
|
// templates to disk at configured locations
|
|
package template
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
ctconfig "github.com/hashicorp/consul-template/config"
|
|
"github.com/hashicorp/consul-template/manager"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/vault/command/agent/config"
|
|
"github.com/hashicorp/vault/sdk/helper/pointerutil"
|
|
)
|
|
|
|
// ServerConfig is a config struct for setting up the basic parts of the
|
|
// Server
|
|
type ServerConfig struct {
|
|
Logger hclog.Logger
|
|
// Client *api.Client
|
|
VaultConf *config.Vault
|
|
ExitAfterAuth bool
|
|
|
|
Namespace string
|
|
}
|
|
|
|
// Server manages the Consul Template Runner which renders templates
|
|
type Server struct {
|
|
// UnblockCh is used to block until a template is rendered
|
|
UnblockCh chan struct{}
|
|
|
|
// config holds the ServerConfig used to create it. It's passed along in other
|
|
// methods
|
|
config *ServerConfig
|
|
|
|
// runner is the consul-template runner
|
|
runner *manager.Runner
|
|
|
|
// Templates holds the parsed Consul Templates
|
|
Templates []*ctconfig.TemplateConfig
|
|
|
|
// TODO: remove donech?
|
|
DoneCh chan struct{}
|
|
logger hclog.Logger
|
|
exitAfterAuth bool
|
|
}
|
|
|
|
// NewServer returns a new configured server
|
|
func NewServer(conf *ServerConfig) *Server {
|
|
ts := Server{
|
|
DoneCh: make(chan struct{}),
|
|
logger: conf.Logger,
|
|
UnblockCh: make(chan struct{}),
|
|
config: conf,
|
|
exitAfterAuth: conf.ExitAfterAuth,
|
|
}
|
|
return &ts
|
|
}
|
|
|
|
// Run kicks off the internal Consul Template runner, and listens for changes to
|
|
// the token from the AuthHandler. If Done() is called on the context, shut down
|
|
// the Runner and return
|
|
func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ctconfig.TemplateConfig) {
|
|
latestToken := new(string)
|
|
ts.logger.Info("starting template server")
|
|
defer func() {
|
|
ts.logger.Info("template server stopped")
|
|
close(ts.DoneCh)
|
|
}()
|
|
|
|
if incoming == nil {
|
|
panic("incoming channel is nil")
|
|
}
|
|
|
|
// If there are no templates, close the UnblockCh
|
|
if len(templates) == 0 {
|
|
// nothing to do
|
|
ts.logger.Info("no templates found")
|
|
close(ts.UnblockCh)
|
|
return
|
|
}
|
|
|
|
// construct a consul template vault config based the agents vault
|
|
// configuration
|
|
var runnerConfig *ctconfig.Config
|
|
if runnerConfig = newRunnerConfig(ts.config, templates); runnerConfig == nil {
|
|
ts.logger.Error("template server failed to generate runner config")
|
|
close(ts.UnblockCh)
|
|
return
|
|
}
|
|
|
|
var err error
|
|
ts.runner, err = manager.NewRunner(runnerConfig, false)
|
|
if err != nil {
|
|
ts.logger.Error("template server failed to create", "error", err)
|
|
close(ts.UnblockCh)
|
|
return
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
ts.runner.StopImmediately()
|
|
ts.runner = nil
|
|
return
|
|
|
|
case token := <-incoming:
|
|
if token != *latestToken {
|
|
ts.logger.Info("template server received new token")
|
|
ts.runner.Stop()
|
|
*latestToken = token
|
|
ctv := ctconfig.Config{
|
|
Vault: &ctconfig.VaultConfig{
|
|
Token: latestToken,
|
|
},
|
|
}
|
|
runnerConfig.Merge(&ctv)
|
|
runnerConfig.Finalize()
|
|
var runnerErr error
|
|
ts.runner, runnerErr = manager.NewRunner(runnerConfig, false)
|
|
if runnerErr != nil {
|
|
ts.logger.Error("template server failed with new Vault token", "error", runnerErr)
|
|
continue
|
|
} else {
|
|
go ts.runner.Start()
|
|
}
|
|
}
|
|
case err := <-ts.runner.ErrCh:
|
|
ts.logger.Error("template server error", "error", err.Error())
|
|
close(ts.UnblockCh)
|
|
return
|
|
case <-ts.runner.TemplateRenderedCh():
|
|
// A template has been rendered, unblock
|
|
if ts.exitAfterAuth {
|
|
// if we want to exit after auth, go ahead and shut down the runner
|
|
ts.runner.Stop()
|
|
}
|
|
close(ts.UnblockCh)
|
|
}
|
|
}
|
|
}
|
|
|
|
// newRunnerConfig returns a consul-template runner configuration, setting the
|
|
// Vault and Consul configurations based on the clients configs.
|
|
func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) *ctconfig.Config {
|
|
// TODO only use default Vault config
|
|
conf := ctconfig.DefaultConfig()
|
|
conf.Templates = templates.Copy()
|
|
|
|
// Setup the Vault config
|
|
// Always set these to ensure nothing is picked up from the environment
|
|
conf.Vault.RenewToken = pointerutil.BoolPtr(false)
|
|
conf.Vault.Token = pointerutil.StringPtr("")
|
|
conf.Vault.Address = &sc.VaultConf.Address
|
|
|
|
if sc.Namespace != "" {
|
|
conf.Vault.Namespace = &sc.Namespace
|
|
}
|
|
|
|
conf.Vault.SSL = &ctconfig.SSLConfig{
|
|
Enabled: pointerutil.BoolPtr(false),
|
|
Verify: pointerutil.BoolPtr(false),
|
|
Cert: pointerutil.StringPtr(""),
|
|
Key: pointerutil.StringPtr(""),
|
|
CaCert: pointerutil.StringPtr(""),
|
|
CaPath: pointerutil.StringPtr(""),
|
|
ServerName: pointerutil.StringPtr(""),
|
|
}
|
|
|
|
if strings.HasPrefix(sc.VaultConf.Address, "https") || sc.VaultConf.CACert != "" {
|
|
skipVerify := sc.VaultConf.TLSSkipVerify
|
|
verify := !skipVerify
|
|
conf.Vault.SSL = &ctconfig.SSLConfig{
|
|
Enabled: pointerutil.BoolPtr(true),
|
|
Verify: &verify,
|
|
Cert: &sc.VaultConf.ClientCert,
|
|
Key: &sc.VaultConf.ClientKey,
|
|
CaCert: &sc.VaultConf.CACert,
|
|
CaPath: &sc.VaultConf.CAPath,
|
|
}
|
|
}
|
|
|
|
conf.Finalize()
|
|
return conf
|
|
}
|