vault/command/agent/template/template.go
Clint 012c165b02
Vault Agent Template (#7652)
* 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
2019-10-18 16:21:46 -05:00

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
}